Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How take each two items from IEnumerable as a pair?

I have IEnumerable<string> which looks like {"First", "1", "Second", "2", ... }.

I need to iterate through the list and create IEnumerable<Tuple<string, string>> where Tuples will look like:

"First", "1"

"Second", "2"

So I need to create pairs from a list I have to get pairs as mentioned above.

like image 442
Nebojsa Veron Avatar asked Nov 27 '22 15:11

Nebojsa Veron


2 Answers

A lazy extension method to achieve this is:

public static IEnumerable<Tuple<T, T>> Tupelize<T>(this IEnumerable<T> source)
{
    using (var enumerator = source.GetEnumerator())
        while (enumerator.MoveNext())
        {
            var item1 = enumerator.Current;

            if (!enumerator.MoveNext())
                throw new ArgumentException();

            var item2 = enumerator.Current;

            yield return new Tuple<T, T>(item1, item2);
        }
}

Note that if the number of elements happens to not be even this will throw. Another way would be to use this extensions method to split the source collection into chunks of 2:

public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> list, int batchSize)
{

    var batch = new List<T>(batchSize);

    foreach (var item in list)
    {
        batch.Add(item);
        if (batch.Count == batchSize)
        {
            yield return batch;
            batch = new List<T>(batchSize);
        }
    }

    if (batch.Count > 0)
        yield return batch;
}

Then you can do:

var tuples = items.Chunk(2)
    .Select(x => new Tuple<string, string>(x.First(), x.Skip(1).First()))
    .ToArray();

Finally, to use only existing extension methods:

var tuples = items.Where((x, i) => i % 2 == 0)
    .Zip(items.Where((x, i) => i % 2 == 1), 
                     (a, b) => new Tuple<string, string>(a, b))
    .ToArray();
like image 193
eulerfx Avatar answered Dec 05 '22 03:12

eulerfx


You could do something like:

var pairs = source.Select((value, index) => new {Index = index, Value = value})
                  .GroupBy(x => x.Index / 2)
                  .Select(g => new Tuple<string, string>(g.ElementAt(0).Value, 
                                                         g.ElementAt(1).Value));

This will get you an IEnumerable<Tuple<string, string>>. It works by grouping the elements by their odd/even positions and then expanding each group into a Tuple. The benefit of this approach over the Zip approach suggested by BrokenGlass is that it only enumerates the original enumerable once.

It is however hard for someone to understand at first glance, so I would either do it another way (ie. not using linq), or document its intention next to where it is used.

like image 42
adrianbanks Avatar answered Dec 05 '22 04:12

adrianbanks