C # and LINQ, select two (consecutive) elements at once

Using LINQ for an ordered set (array, list), is there a way to select or otherwise use two consecutive elements? I present the syntax:

list.SelectTwo((x, y) => ...) 

Where x and y are the elements at index i and i + 1 in the list / array. There can be no way to do this, which I take as an opportunity, but at least I would like to say that I was trying to find the answer.

I know that for this I could use something else and LINQ.

Thanks in advance.

+5
source share
6 answers

Another answer is a good and clean solution using LINQ Skip and Zip .

This is absolutely correct, but I would like to point out that it lists the source twice. This may or may not matter, depending on each particular use case. If it matters to your case, here is a longer alternative that is functionally equivalent, but lists the source once:

 static class EnumerableUtilities { public static IEnumerable<TResult> SelectTwo<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TSource, TResult> selector) { if (source == null) throw new ArgumentNullException(nameof(source)); if (selector == null) throw new ArgumentNullException(nameof(selector)); return SelectTwoImpl(source, selector); } private static IEnumerable<TResult> SelectTwoImpl<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TSource, TResult> selector) { using (var iterator = source.GetEnumerator()) { var item2 = default(TSource); var i = 0; while (iterator.MoveNext()) { var item1 = item2; item2 = iterator.Current; i++; if (i >= 2) { yield return selector(item1, item2); } } } } } 

Example:

 var seq = new[] {"A", "B", "C", "D"}.SelectTwo((a, b) => a + b); 

The resulting sequence contains "AB" , "BC" , "CD" .

+10
source

You can do

 list.Skip(i).Take(2) 

This will return an IEnumerable<T> with only two consecutive elements.

+3
source

System.Linq.Enumerable.Zip combines two IEnumerable by combining the i -th element for each i . So you just need to Zip your list with a shifted version.

As a good extension method:

 using System.Collections.Generic; using System.Linq; static class ExtMethods { public static IEnumerable<TResult> SelectTwo<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TSource, TResult> selector) { return Enumerable.Zip(source, source.Skip(1), selector); } } 

Example:

 Enumerable.Range(1,5).SelectTwo((a,b) => $"({a},{b})"); 

Results in:

 (1,2) (2,3) (3,4) (4,5) 
+2
source

I think you can select the data of the element and the next element in an ordered list as follows:

 var theList = new List<T>(); theList .Select((item, index) => new { CurrIndex = index, item.Prop1, item.Prop2, theList[index + 1].Prop1 }) .Where(newItem => {some condition on the item}); 

However, the index for the selected items must be less than the size of the list - 1.

+1
source

If the source sequence has an index, i.e. minimum IReadOnlyList<T> (array, list, as mentioned in the question), and the idea is to split the sequence into consecutive pairs (which is not entirely clear from the question), this can be done simply like this:

 var pairs = Enumerable.Range(0, list.Count / 2) .Select(i => Tuple.Create(list[2 * i], list[2 * i + 1])); 
+1
source

You can use the special Select overload, which allows you to use the element index and the GroupBy method to split the list into groups. Each group will have two elements. Here is an extension method that does this:

 public static class ExtensionMethods { public static IEnumerable<TResult> SelectTwo<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TSource, TResult> selector) { return source.Select((item, index) => new {item, index}) .GroupBy(x => x.index/2) .Select(g => g.Select(i => i.item).ToArray()) .Select(x => selector(x[0], x[1])); } } 

And you can use it as follows:

 var list = new[] {1, 2, 3, 4, 5, 6}; var result = list.SelectTwo((x, y) => x + y).ToList(); 

This will return {3,7,11}

Note that the above method groups the data in memory before proceeding with the results. If you have large data sets, you might want to use the streaming approach (exit data, since the data from the source is being listed), here is an example:

 public static class ExtensionMethods { public static IEnumerable<TResult> SelectTwo<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TSource, TResult> selector) { bool first_item_got = false; TSource first_item = default(TSource); foreach (var item in source) { if (first_item_got) { yield return selector(first_item, item); } else { first_item = item; } first_item_got = !first_item_got; } } } 
0
source

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


All Articles