How to choose the last value every time you run similar items?

I have a list. I would like to take the last value from each run of similar elements.

What I mean? Let me give you a simple example. Given a list of words

['golf', 'hip', 'hop', 'hotel', 'grass', 'world', 'wee']

And the similarity function "starting with the same letter", the function will return a shorter list

['golf', 'hotel', 'grass', 'wee']

Why? The original list has 1st order of G words, 3 of H words, 1st of G words and 2 round W-words. The function returns the last word from each run.

How can i do this?


C # hypothetical syntax (I actually work with client objects, but I want to share what you could run and check yourself)

> var words = new List<string>{"golf", "hip", "hop", "hotel", "grass", "world", "wee"}; > words.LastDistinct(x => x[0]) ["golf", "hotel", "grass", "wee"] 

Edit: I tried .GroupBy(x => x[0]).Select(g => g.Last()) , but it gives ['grass', 'hotel', 'wee'], which is not what I want. Read the example carefully.


Change Another example.

['apples', 'army', 'black', 'beer', 'bastion', 'cat', 'cart', 'capable', 'art', 'bark']

There are 5 starts (start A, start B, start C, new start A, new start B). Last word from each run:

['army', 'bastion', 'cart', 'art', 'bark']

It is important to understand that each run is independent. Do not mix start A at the beginning with start A near the end.

+6
source share
6 answers

I went with

 /// <summary> /// Given a list, return the last value from each run of similar items. /// </summary> public static IEnumerable<T> WithoutDuplicates<T>(this IEnumerable<T> source, Func<T, T, bool> similar) { Contract.Requires(source != null); Contract.Requires(similar != null); Contract.Ensures(Contract.Result<IEnumerable<T>>().Count() <= source.Count(), "Result should be at most as long as original list"); T last = default(T); bool first = true; foreach (var item in source) { if (!first && !similar(item, last)) yield return last; last = item; first = false; } if (!first) yield return last; } 
0
source

You can use this extension, which can be grouped by adjacent / sequential elements:

 public static IEnumerable<IGrouping<TKey, TSource>> GroupAdjacent<TSource, TKey>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector) { TKey last = default(TKey); bool haveLast = false; List<TSource> list = new List<TSource>(); foreach (TSource s in source) { TKey k = keySelector(s); if (haveLast) { if (!k.Equals(last)) { yield return new GroupOfAdjacent<TSource, TKey>(list, last); list = new List<TSource>(); list.Add(s); last = k; } else { list.Add(s); last = k; } } else { list.Add(s); last = k; haveLast = true; } } if (haveLast) yield return new GroupOfAdjacent<TSource, TKey>(list, last); } public class GroupOfAdjacent<TSource, TKey> : IEnumerable<TSource>, IGrouping<TKey, TSource> { public TKey Key { get; set; } private List<TSource> GroupList { get; set; } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return ((System.Collections.Generic.IEnumerable<TSource>)this).GetEnumerator(); } System.Collections.Generic.IEnumerator<TSource> System.Collections.Generic.IEnumerable<TSource>.GetEnumerator() { foreach (var s in GroupList) yield return s; } public GroupOfAdjacent(List<TSource> source, TKey key) { GroupList = source; Key = key; } } 

Then it’s easy:

 var words = new List<string>{"golf", "hip", "hop", "hotel", "grass", "world", "wee"}; IEnumerable<string> lastWordOfConsecutiveFirstCharGroups = words .GroupAdjacent(str => str[0]) .Select(g => g.Last()); 

Output:

 string.Join(",", lastWordOfConsecutiveFirstCharGroups); // golf,hotel,grass,wee 

Your other example:

 words=new List<string>{"apples", "armies", "black", "beer", "bastion", "cat", "cart", "able", "art", "bark"}; lastWordOfConsecutiveFirstCharGroups = words .GroupAdjacent(str => str[0]) .Select(g => g.Last()); 

Output:

 string.Join(",", lastWordOfConsecutiveFirstCharGroups); // armies,bastion,cart,art,bark 

Demonstration

+1
source

There is nothing complicated in just doing it the old fashioned way:

 Func<string, object> groupingFunction = s => s.Substring(0, 1); IEnumerable<string> input = new List<string>() {"golf", "hip", "..." }; var output = new List<string>(); if (!input.Any()) { return output; } var lastItem = input.First(); var lastKey = groupingFunction(lastItem); foreach (var currentItem in input.Skip(1)) { var currentKey = groupingFunction(str); if (!currentKey.Equals(lastKey)) { output.Add(lastItem); } lastKey = currentKey; lastItem = currentItem; } output.Add(lastItem); 

You can also turn this into a general extension method, as Tim Schmelter did ; I already took a few steps to generalize the code to the goal (using object as the key type and IEnumerable<T> as the input type).

+1
source

You can use the following extension method to split your sequence into groups (i.e., sub-sequnces) using some condition:

 public static IEnumerable<IEnumerable<T>> Split<T, TKey>( this IEnumerable<T> source, Func<T, TKey> keySelector) { var group = new List<T>(); using (var iterator = source.GetEnumerator()) { if (!iterator.MoveNext()) yield break; else { TKey currentKey = keySelector(iterator.Current); var keyComparer = Comparer<TKey>.Default; group.Add(iterator.Current); while (iterator.MoveNext()) { var key = keySelector(iterator.Current); if (keyComparer.Compare(currentKey, key) != 0) { yield return group; currentKey = key; group = new List<T>(); } group.Add(iterator.Current); } } } if (group.Any()) yield return group; } 

And getting the expected results is as follows:

 string[] words = { "golf", "hip", "hop", "hotel", "grass", "world", "wee" }; var result = words.Split(w => w[0]) .Select(g => g.Last()); 

Result:

 golf hotel grass wee 
0
source

Try this algorithm

  var words = new List<string> { "golf", "hip", "hop", "hotel", "grass", "world", "wee" }; var newList = new List<string>(); int i = 0; while (i < words.Count - 1 && i <= words.Count) { if (words[i][0] != words[i+1][0]) { newList.Add(words[i]); i++; } else { var j = i; while ( j < words.Count - 1 && words[j][0] == words[j + 1][0]) { j++; } newList.Add(words[j]); i = j+1; } } 
0
source

Since your input is List <>, so I think this should work for you with acceptable performance and especially very concise:

 var result = words.Where((x, i) => i == words.Count - 1 || words[i][0] != words[i + 1][0]); 

You can add ToList() in the result to get a List<string> if you want.

0
source

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


All Articles