dan.lavr
dan.lavr

Reputation: 71

Divide list in sublists by elements

I have a list of strings:

{"foo", "str1", "str2", ..., "bar", ..., "baz", ...}

I need to get sublists of strings between "foo", "bar" and "baz".

Is it possible to do this with linq?

EDIT
I need a method without looking trough the list twice.

Upvotes: 4

Views: 461

Answers (3)

p.s.w.g
p.s.w.g

Reputation: 149030

You can do this to fine all elements between any two other elements:

var strings = new[] { "foo", "str1", "str2", ... "bar", ... "baz" };
var between = strings.SkipWhile(s => s != "foo").Skip(1)
                     .TakeWhile(s => s != "bar"); // "str1", "str2", ...

If you want to get everything between "foo" and "baz", except "bar", use this (assuming the order "foo", "bar", "baz"):

var strings = new[] { "foo", "str1", "str2", ... "bar", ... "baz" };
var between = strings.SkipWhile(s => s != "foo").Skip(1)
                     .TakeWhile(s => s != "baz")
                     .Where(s => s != "bar"); // "str1", "str2", ...

Or if your comfortable with using Linq queries with side effects, you can do this to partition your input list by certain 'stop' words:

 var stops = new[] { "foo", "bar", "baz" };
 var strings = new[] { "foo", "str1", "str2", "bar", "str3", "baz" };
 var p = -1;
 var partitions = 
     from s in strings
     let i = Array.IndexOf(stops, s) 
     group s by p = i == -1 ? p : i into g
     where g.Key == 0 || g.Key == 1
     select g.Skip(1); // { "str1", "str2" }, { "str3" }

Or slightly more efficient (since it stops processing after the third stop word):

 var partitions = 
     (from s in strings
      let i = Array.IndexOf(stops, s) 
      group s by p = i == -1 ? p : i)
     .SkipWhile(g => g.Key < 0)
     .Take(2)
     .Select(g => g.Skip(1)); // { "str1", "str2" }, { "str3" }

Now, this method is a little bit rough around the edges, and it's somewhat fiddly when it comes to items before "foo" or after "baz", but if since you're only looking for items between "foo" and "baz", it should work for you. It has the added benefit that the order of the stop words does not affect the results.

Upvotes: 6

user1064248
user1064248

Reputation:

If you want to iterate only once through your extensive data list you can do this:

List<string> longDataList = new List<string> { "foo", "str1", "str2", "str1", "str2", "str1", "str2", "bar", "str1", "str2", "str1", "str2", "str1", "str2", "baz", "str1", "str2", "str1", "str2", "str1", "str2" };
List<string> splitters = new List<string> { "foo", "bar", "baz" };
Dictionary<string, List<string>> resultDict = new Dictionary<string, List<string>>();
List<string> currentList = null;
longDataList.ForEach(s =>
    {
       if (splitters.Contains(s))
          {
            if (resultDict.ContainsKey(s))
                currentList = resultDict[s];
             else
                {
                 currentList = new List<string>();
                  resultDict.Add(s, currentList);
                }
           }
        else
           currentList.Add(s);
     });

Uses at least a little linq but does the trick iterating only once through your extensive data list.

Upvotes: 0

Matten
Matten

Reputation: 17603

var idxFoo = list.IndexOf("foo");
var idxBar = list.IndexOf("bar");
var idxBaz = list.IndexOf("baz");

var subList1 = list.Skip(idxFoo).Take(idxBar - idxFoo);
var subList2 = list.Skip(idxBar).Take(idxBaz - idxBar);

Upvotes: 4

Related Questions