How to select 2 items at the same time?

I wrote PascalCaseParser using Regex.Split and I came to the desire to select two items from the collection at a time.

This code example demonstrates.

 void Main() { string pascalCasedString = "JustLikeYouAndMe"; var words = WordsFromPascalCasedString(pascalCasedString); words.Dump(); } IEnumerable<string> WordsFromPascalCasedString(string pascalCasedString) { var rx = new Regex("([AZ])"); return rx.Split(pascalCasedString) .Where(c => !string.IsNullOrEmpty(c)) // how to select 2 elements at a time? ; } 

The result of the code above:

 IEnumerable<String> (10 items) J ust L ike Y ou A nd M e 

Every two items in the collection give one result: I want the WordsFromPascalCasedString function WordsFromPascalCasedString give.

My questions are: how would you, in general, deal with the requirement to return two items at once. I am curious if there are any interesting approaches without brute force.

+4
source share
4 answers

Personally, I would go with the answer of Simon Belanger in this particular case. But in general, to select consecutive pairs from IEnumerable , you should use this:

 IEnumerable<Tuple<string, string>> WordsFromPascalCasedString(string pascalCasedString) { var rx = new Regex("([AZ])"); var array = rx.Split(pascalCasedString) .Where(c => !string.IsNullOrEmpty(c)) .ToArray(); var items = Enumerable.Range(0, array.Length / 2) .Select(i => Tuple.Create(array[i * 2], array[i * 2 + 1]); } 

Or this, which requires more effort, but it is reusable and more effective:

 IEnumerable<Tuple<T, T>> Pairs<T>(IEnumerable<T> input) { var array = new T[2]; int i = 0; foreach(var x in input) { array[i] = x; i = (i + 1) % 2; if (i == 0) { yield return Tuple.Create(array[0], array[1]); } } } IEnumerable<Tuple<string, string>> WordsFromPascalCasedString(string pascalCasedString) { var rx = new Regex("([AZ])"); var output = rx.Split(pascalCasedString) .Where(c => !string.IsNullOrEmpty(c)); var items = Pairs(output); } 

It can be easily extended to groups of n :

 IEnumerable<IEnumerable<T>> Batches<T>(IEnumerable<T> input, int n) { var array = new T[n]; int i = 0; foreach(var x in input) { array[i] = x; i = (i + 1) % n; if (i == 0) { yield return array.ToArray(); } } if (i != 0) { yield return array.Take(i); } } 

A similar method exists in MoreLINQ .

+5
source

The regular expression must be ([AZ][az]*) . Adjust the last part if you want to also include numbers. Use + instead of * if you need at least one lowercase after the uppercase separator.

Change Regarding the actual question, you will need to materialize and iterate in a for loop for better performance (going through the list once). In your specific problem, you can just use Regex.Matches

 var result = Regex.Matches("([AZ][az]*)([AZ][az]*)?", "AbCdEfGhIj") .OfType<Match>() .Where(m => m.Success) .Select(m => Tuple.Create(m.Groups[1].Value, m.Groups[2].Value)); 
+5
source

The easiest way is to write a function that simply returns pairs.

Sort of:

 IEnumerable<Tuple<T,T>> Pairs<T>(IEnumerable<T> items) { T first = default(T); bool hasFirst = false; foreach(T item in items) { if (hasFirst) yield return Tuple.Create(first, item); else first = item; hasFirst = !hasFirst; } } 

Aggregate is likely to be one single line approach. This is purely entertainment code due to the amount of garbage created in the path, but mutable objects are not used.

 IEnumerable<Tuple<T,T>> Pairs<T>(IEnumerable<T> collection) { return collection .Aggregate( Tuple.Create(false, default(T), Enumerable.Empty<Tuple<T,T>>()), (accumulate, item)=> !accumulate.Item1 ? Tuple.Create(true, item, accumulate.Item3) : Tuple.Create(false, default(T), accumulate.Item3.Concat( Enumerable.Repeat(Tuple.Create(accumulate.Item2, item), 1))), accumulate => accumulate.Item3); } 

Zip odd and even elements ( index %2 ==/!= 0 ) is two-line. Note that iterating the original collection twice.

 IEnumerable<Tuple<T,T>> Pairs<T>(IEnumerable<T> collection) { return collection .Where((item, index)=>index %2 == 0) .Zip(collection.Where((item, index)=>index %2 != 0), (first,second)=> Tuple.Create(first,second)); } 
+2
source

It's just to share, I am throwing a solution that I came up with after you inspired other answers. This is no better than others ...

 void Main() { string pascalCasedString = "JustLikeYouAndMe"; var words = WordsFromPascalCasedString(pascalCasedString); words.Dump(); } IEnumerable<string> WordsFromPascalCasedString(string pascalCasedString) { var rx = new Regex("([AZ])"); return rx.Split(pascalCasedString) .Where(c => !string.IsNullOrEmpty(c)) .InPieces(2) .Select(c => c.ElementAt(0) + c.ElementAt(1)); } static class Ext { public static IEnumerable<IEnumerable<T>> InPieces<T>(this IEnumerable<T> seq, int len) { if(!seq.Any()) yield break; yield return seq.Take(len); foreach (var element in InPieces(seq.Skip(len), len)) yield return element; } } 
+2
source

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


All Articles