12

I'd like avoid loop

I have this :

string s = "AAAA,12,BBBB,34,CCCCC,56";

With Linq, I'd like to have 2 List

In the first : AAAA, BBBB and CCCCC

In the second : 12,34 and 56

It's not based on numeric or not numeric.

Thanks,

5
  • There is a nice tutorial here msdn.microsoft.com/en-us/library/bb397915.aspx Commented Sep 29, 2010 at 12:10
  • 1
    What is the partitioning of the string based on? Position in the list, number of characters, etc.? Commented Sep 29, 2010 at 12:10
  • Yes it's position in the list Commented Sep 29, 2010 at 12:12
  • 2
    I'd like avoid loop You do realise that whatever LINQ solution you decide on will in fact, use at least one loop? Commented Sep 29, 2010 at 12:29
  • @Winston Smith: The Linq solution could use recursive functions calls instead of a loop. But I guess Kris' point might have been that he was looking for a declarative solution instead of an imperative one. (Even if it's ultimately compiled to imperative assembler code.) Commented Sep 29, 2010 at 12:58

8 Answers 8

32

You can use

var str = "AAAA,12,BBBB,34,CCCCC,56";

var spl = str.Split(',');
var l1 = spl.Where((x, y) => y % 2 == 0).ToList();
var l2 = spl.Where((x, y) => y % 2 == 1).ToList();

This is going to check if the index is even or odd.

Sign up to request clarification or add additional context in comments.

Comments

10

Lets use Aggregate for the fun of it (and also, to prove this can be done as a single expression):

"AAAA,12,BBBB,34,CCCC,56".Split(',').Aggregate(
    new { Uneven = new List<string>(), Even = new List<string>() },
    (seed, s) => { 
        if (seed.Uneven.Count > seed.Even.Count) 
            seed.Even.Add(s);
        else
            seed.Uneven.Add(s);
        return seed;
    });

According to LINQPad, the result is this: alt text

Of course I probably wouldn't do it this way, as it's kind of hard to read. And the testing for which list to append to is, well, not nice.

But at least we now have another example of lambda statements - normally the LINQ literature tries to forget them (probably because they won't work with SQL or any other backend that uses expression trees).

One advantage of this method as opposed to the cleaner solutions above is that this only makes one pass through the list. Since we are splitting a string, though, I'd try optimizing somewhere else ;) Wouldn't a IEnumerable<string> Split(this string self, string boundary) be cool?

4 Comments

Somehow it seems like a strange abuse of Aggregate if the accumulator function modifies the the source parameter. It feels like following the letter of functional programming while contradicting the spirit. Am I the only one who thinks that?
@nikie with "source parameter" do you mean TSource (not modified) or TAccumulate (modified, but isn't that always the case?)
@gaearon, yes, this isn't really real-world, until you want to do a weighted average of a property for a list of objects. Then using Aggregate like this can shine (especially since you can add a third parameter for returning/formatting the end result).
I meant, TAccumulate, of course. And I don't think it should be modified. For example, if I used Aggregate to calculate the sum or product of a list of numbers, the aggregateion function should just return the product or sum of it's arguments, without changing one. In this case, the "functional" solution (e.g. if you'd write it in Haskell or *ML) would probably work on linked lists and create a new linked list with a cheap Cons instruction instead of modifying its parameters. (IMO, Linq should have included a linked list type that allowed you to do just that.)
6

Given that the rule is that you want every second string in one list and the others in another list, you can do something like this:

        string s = "AAAA,12,BBBB,34,CCCCC,56";

        var parts = s.Split(',');

        var first = parts.Where((p, i) => i % 2 == 0);
        var second = parts.Where((p, i) => i % 2 == 1);

Comments

2

I'm not sure exactly what your end goal is, but you can try this:

var strings = s.Split(',').Where( (s,p) => p % 2 == 0)

Comments

1

Here is the isnumeric and not numeric filter for those interested... i realise its not needed

 string x =  "AAAA,12,BBBB,34,CCCCC,56";

Regex  _isNumber = new Regex(@"^\d+$");

string[] y = x.Split(',') .Where(a => _isNumber.IsMatch(a)).ToArray();
string[] z  =x.Split(',') .Where(a => !_isNumber.IsMatch(a)).ToArray();

Comments

1

you could group on the position and the extrat you lists from the group, like so:

        public IEnumerable<IEnumerable<T>> ToLists<T>(IEnumerable<T> sequence)
        {
            var res = sequence.Select((item, position) => new { Item = item, Position = position })
                              .GroupBy(pair => pair.Position % 2 == 0,pair => pair.Item);
            return from grouping in res
                   select grouping;
        }

If you want the Lists to be of different types you can iterate through the result. Which is why the return type is not IEnumerable> but IEnumerable>. using ToList will iterate the sequence but if you want to perform some action on each element you might as well merge those actions, make one iteration through the sequnce superflourious

Comments

0

So much fun, without side-effects and no method calls.

"TesteDessaBudega".Aggregate(new List<List<char>>(), 
(l, c) => char.IsUpper(c) ? 
    l.Union(
        new List<List<char>>(){
            new List<char>(){c}
        }
    ).ToList() : 
    l.Take(l.Count - 1).Union(
        new List<List<char>>(){
            l.Last().Union(
                new List<char>(){c}
            ).ToList()
        }
    ).ToList() 
)

Oh, on vbnet just for more fun.

"TesteDessaBudega".Aggregate(New List(Of List(Of Char))(), 
Function(l, c) If(Char.IsUpper(c),
    l.Union(
        New List(Of List(Of Char))(New List(Of Char)(){
            New List(Of Char)(New Char(){c})
        })
    ).ToList(),
    l.Take(l.Count - 1).Union(
        New List(Of List(Of Char))(New List(Of Char)(){
            l.Last().Union(
                New List(Of Char)(New Char(){c})
            ).ToList()
        })
    ).ToList() 
))

linqresult

1 Comment

Really unsure how this has any relevance to the question. I have a feeling you are answering a different question.
-1

If the list is not ordered with every second being a number you could do something like this

var stringList = "AAAA,12,BBBB,34,CCCCC,56".Split(',');

var intsAsStrings = stringList.Where(
        (x) =>
        {
            int i;
            return int.TryParse(x, out i);
        }).ToList();

var strings = stringList.Where(x => !intsAsStrings.Contains(x)).ToList();

2 Comments

Why not simply use Except instead of that Where(x => !intsAsStrings.Contains(x))? Also this is not what the author of the question wanted.
Didn't know the Except method, nice.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.