Is there a way to use LINQ to select a range of elements based on an inclusion condition (i.e. Not a simple WHERE clause)?

You have List<Foo>objects, but Foohas a property IsSelectedlike this ...

public class Foo
{
    public string Name{ get; set; }
    public bool IsSelected{ get; set; }
}

List<Foo> sourceItems = new List<Foo>
{
    new Foo(){ Name="First",   IsSelected=false},
    new Foo(){ Name="Second",  IsSelected=true },
    new Foo(){ Name="Third",   IsSelected=false},
    new Foo(){ Name="Fourth",  IsSelected=true },
    new Foo(){ Name="Fifth",   IsSelected=false},
    new Foo(){ Name="Sixth",   IsSelected=true },
    new Foo(){ Name="Seventh", IsSelected=true },
    new Foo(){ Name="Eighth",  IsSelected=false},
    new Foo(){ Name="Ninth",   IsSelected=false},
    new Foo(){ Name="Tenth",   IsSelected=false}
};

Using the Where clause, of course, I can only get the elements that are selected, for example ...

var results = sourceItems.Where(item => item.IsSelected);

... but what if I want all the elements between the first and last element, where IsSelected is true? (i.e., second to seventh)

I know that I can use SkipWhile, since it skips to the first true operator, and then returns everything after ...

// Returns from Second on
var results = sourceItems.SkipWhile(item => !item.IsSelected);

... and I know that I can undo and do the same again, but then I would have to rediscover it at the end, and double treatment just seems like it would be unnecessarily expensive.

- Select , IsSelected , Where , , , .

int lastSelectedIndex = -1;
var results = sourceItems
    .SkipWhile(item => !item.IsSelected)
    .Select( (item, itemIndex) => 
    {
        if(item.IsSelected)
            lastSelectedIndex = index;

        return new {item, index};
    })
    .Where(anonObj => anonObj.index <= lastSelectedIndex)
    .Select(anonObj => anonObj.Item);

, , Where Take , , , lastSelectedIndex , , Select , , .

.Take(lastSelectedIndex + 1);

, ?

+4
6

, -, .

List<Foo> sourceItems = new List<Foo>
{
    new Foo(){ Name="First",   IsSelected=false},
    new Foo(){ Name="Second",  IsSelected=true },
    new Foo(){ Name="Third",   IsSelected=false},
    new Foo(){ Name="Fourth",  IsSelected=true },
    new Foo(){ Name="Fifth",   IsSelected=false},
    new Foo(){ Name="Sixth",   IsSelected=true },
    new Foo(){ Name="Seventh", IsSelected=true },
    new Foo(){ Name="Eighth",  IsSelected=false},
    new Foo(){ Name="Ninth",   IsSelected=false},
    new Foo(){ Name="Tenth",   IsSelected=false}
};

int startIndex = sourceItems.FindIndex(x => x.IsSelected);
int endIndex   = sourceItems.FindLastIndex(x => x.IsSelected);

var items = new List<Foo>();

for (int i = startIndex; i <= endIndex; i++)
    items.Add(sourceItems[i]);    

1 000 000 13 , .

+3

, .Aggregte, , :

var lists = sourceItems.Aggregate(Tuple.Create(new List<Foo>(), new List<Foo>()), (acc, foo) =>
{
    if (foo.IsSelected)
    {
        acc.Item1.AddRange(acc.Item2);
        acc.Item2.Clear();
        acc.Item2.Add(foo);
    }
    else if (acc.Item2.Any())
    {
        acc.Item2.Add(foo);
    }
    return acc;
});

if (lists.Item2.Any()) lists.Item1.Add(lists.Item2.First());

, , ( # , )

: : , , selected - .

, selected.

static IEnumerable<Foo> BetweenSelected(List<Foo> foos)
{
    var lastSelected = foos.Count;

    for (var i = 0; i < foos.Count; i++)
    {
        var foo = foos[i];
        if (foo.IsSelected)
        {
            for (var j = lastSelected; j < i; j++)
                yield return foos[j];
            lastSelected = i+1;
            yield return foo;
        }
    }
}

, yield List.Add, .

+2

Where .

var result = sourceItems.Where((ele, index) => 
             index > 0 && index < 7 && ele.IsSelected);

, IsSelected true Min Max, , Where .

var indexes = Enumerable.Range(0, sourceItems.Count)
                        .Where(i => sourceItems[i].IsSelected);
var result1 = sourceItems.Where((el, idx)=>idx >= indexes.Min() &&  idx <= indexes.Max());
+1

. :

var results =
    sourceItems
        .SkipWhile(x => x.IsSelected == false)
        .Reverse()
        .SkipWhile(x => x.IsSelected == false)
        .Reverse();

, .

1 000 000 163 . 10 000 000 1.949 .

+1

Dimi Toulakis

If someone needs this often in his code, this is an extension

public static class ListExtension
{
    public static List<T> FindGroup<T>(this List<T> mylist, Predicate<T> pred)
    {
        var first = mylist.FindIndex(pred);
        var last = mylist.FindLastIndex(pred);
        last += 1; // to get the Last Element

        return mylist.GetRange(first, last - first);
    }
}
+1
source

Combining the Answer of Dimi Tulakis and Adil Answer :

List<Foo> sourceItems = new List<Foo>{
    new Foo(){ Name="First",   IsSelected=false},
    new Foo(){ Name="Second",  IsSelected=true },
    new Foo(){ Name="Third",   IsSelected=false},
    new Foo(){ Name="Fourth",  IsSelected=true },
    new Foo(){ Name="Fifth",   IsSelected=false},
    new Foo(){ Name="Sixth",   IsSelected=true },
    new Foo(){ Name="Seventh", IsSelected=true },
    new Foo(){ Name="Eighth",  IsSelected=false},
    new Foo(){ Name="Ninth",   IsSelected=false},
    new Foo(){ Name="Tenth",   IsSelected=false}
};

int startIndex = sourceItems.FindIndex(item => item.IsSelected);
int endIndex   = sourceItems.FindLastIndex(item => item.IsSelected);

var result = sourceItems.Where((item, itemIndex) => itemIndex >= startIndex && itemIndex <= endIndex);

We can also use GetRange () , but this is not LINQ.

Update

Another way that seems more efficient is to use TakeWhile () along with SkipWhile () :

int endIndex = sourceItems.FindLastIndex(item => item.IsSelected);

var result = sourceItems
    .TakeWhile((item, itemIndex) => itemIndex <= endIndex) // Take all before (and including) index of last match (must be before SkipWhile as that would change the index)
    .SkipWhile(item => !item.IsSelected); // Skip up until the first item where IsSelected is true

It also saves us from having to find startIndex.

+1
source

Source: https://habr.com/ru/post/1547359/


All Articles