Eliminate consecutive duplicates of list elements
Is there a "nice" way to eliminate consecutive duplicates of list elements?
Example:
["red"; "red"; "blue"; "green"; "green"; "red"; "red"; "yellow"; "white"; "white"; "red"; "white"; "white"]
should become
["red"; "blue"; "green"; "red"; "yellow"; "white"; "red"; "white"]
--By "nice" I mean most readable and understandable to a new user and fast execution :)
A simple and very readable solution:
List<string> results = new List<string>();
foreach (var element in array)
{
if(results.Count == 0 || results.Last() != element)
results.Add(element);
}
You can roll your own, linq-style.
// For completeness, this is two methods to ensure that the null check
// is done eagerly while the loop is done lazily. If that's not an issue,
// you can forego the check and just use the main function.
public static IEnumerable<T> NonConsecutive<T>(this IEnumerable<T> input)
{
if (input == null) throw new ArgumentNullException("input");
return NonConsecutiveImpl(input);
}
static IEnumerable<T> NonConsecutiveImpl<T>(this IEnumerable<T> input)
{
bool isFirst = true;
T last = default(T);
foreach (var item in input) {
if (isFirst || !object.Equals(item, last)) {
yield return item;
last = item;
isFirst = false;
}
}
}
And use as
array.NonConsecutive().ToArray()
The advantage is that it's lazily evaluated, so you can use it on any enumeration without having to consume it in its entirety, and chain it with other linq methods (eg: array.Where(i => i != "red").NonConsecutive().Skip(1).ToArray()
). If you don't have that requirement and you just want to work with arrays, something like Simon Bartlett's solution might be slightly more performant.
For more information on why it has to be two methods, see here
You can create simple generic method for this purpose, like below:
[EDIT 2] (great thanks to Eric Lippert)
public static List<T> ExcludeConsecutiveDuplicates<T>(List<T> InputList)
{
object lastItem = null;
List<T> result = new List<T>();
for (int i = 0; i < InputList.Count; i++)
{
if (i==0 || Object.Equals(InputList[i],lastItem) != true)
{
lastItem = InputList[i];
result.Add((T)lastItem);
}
}
return result;
}
You can do it in LINQ:
list.Aggregate(new List<string>(),
(current, next) => {
if (current.Length <= 0 || current[current.Length-1] != next) current.Add(next);
return current;
});
Essentially, this creates an initially-empty list, runs through the entire source list, and only add an item to the target list if it is not the same as the last item of the target list.
You can just as easily (probably easier) do it without LINQ:
var target = new List<string>();
foreach (var item in list) {
if (target.Length <= 0 || target[target.Length-1] != item) target.Add(item);
}
I think this is the simplest it can get with Linq:
colors.Where((color, i) => i == 0 || color != colors[i - 1]);
You can try it in C# Interactive:
> var colors = new[] { "red", "red", "blue", "green", "green", "red", "red", "yellow", "white", "white", "red", "white", "white" };
> colors.Where((color, i) => i == 0 || color != colors[i - 1])
WhereIterator { "red", "blue", "green", "red", "yellow", "white", "red", "white" }
The trick here is to use the Where() overload that accepts a predicate with index, then compare to the previous item in the original array.