3

Is there a function to remove consequent values (i.e. 14, 14 -> 14, 12, 12 -> 12)?

The following list ([12, 14, 14, 12, 12, 14]):

List<string> foo = new List<string> { 12, 14, 14, 12, 12, 14 };

to the list [12, 14, 12, 14]?

Dmitry Bychenko
  • 180,369
  • 20
  • 160
  • 215
X89i
  • 65
  • 6
  • Surely this has been asked before. – Micha Wiedenmann Apr 05 '19 at 09:37
  • @Micha Probably you are right, but could't find it with my own words. – X89i Apr 05 '19 at 09:39
  • You could iterate through the list, and keep track of the last seen element. If the last seen element matches the current element, continue, else update the last seen element to the current value and yield the current value to be further used. – Aman Agnihotri Apr 05 '19 at 09:39

3 Answers3

5

Approach with foreach

public static IEnumerable<T> DistinctByPrevious<T>(List<T> source)
{
    if (source != null && source.Any())
    {
        T prev = source.First();
        yield return prev;
        foreach (T item in source.Skip(1))
        {
            if (!EqualityComparer<T>.Default.Equals(item, prev))
            {
                yield return item;
            }
            prev = item;
        }
    }
}
fubo
  • 44,811
  • 17
  • 103
  • 137
  • 2
    Nice, but in *general case*, if `IEnumerable` is not a *materialized collection*, but, say, a file - `File.ReadLines("c:\myFile.txt")` you can have *incosistent* results of `source.Any()` - first materialization; `source.First()` - second materialization and `foreach(...source.Skip(1))` - third one. – Dmitry Bychenko Apr 05 '19 at 10:14
  • @DmitryBychenko Can you please point me at the relevant part of the specification (if three is such a thing). I'd like to understand in more detail why there might be a problem, and ideally based on a specification. – Micha Wiedenmann Apr 08 '19 at 07:20
  • @Micha Wiedenmann: imagine that `source` is a *file* e.g. `File.ReadLines("c:\MyFile.csv")`. Then `source.Any()` will open the file and try to read the 1st line it'll return `true` if the first line exists. Next we call `source.First()`, but file can have been *changed* (e.g. cleared) and now we have an exception thrown `First()` on empty sequence – Dmitry Bychenko Apr 08 '19 at 07:30
  • @DmitryBychenko I've changed my answer to `List` to avoid this special case - SQLDataReader has the same problem – fubo Apr 08 '19 at 07:32
  • Based on this level of rigorosity, not even List is fine, since it can also be altered in parallel. In fact apart from arrays and immutable types I don't see any chance of using LINQ while satisfying this constraint. – Micha Wiedenmann 35 mins ago – Micha Wiedenmann Apr 08 '19 at 09:38
4

Linq without additional libraries, but with side effects is a quick and dirty (prior side effect is ugly) solution:

  List<string> foo = new List<string> { "12", "14", "14", "12", "12", "14" };

  string prior = null;

  List<string> result = foo
    .Where((v, i) => i == 0 || v != prior)
    .Select(v => prior = v)
    .ToList();

In general case, you may want to implement an extension method:

  public static partial class EnumerableExtensions {  
    public static IEnumerable<T> DistinctSuccessive<T>(
      this IEnumerable<T> source, 
           IEqualityComparer<T> comparer = null) {
      // public method arguments validation
      if (null == source)
        throw new ArgumentNullException(nameof(source));

      // equals: either default or custom one 
      Func<T, T, bool> equals = (left, right) => null == comparer 
        ? object.Equals(left, right) 
        : comparer.Equals(left, right);

      bool first = true;
      T prior = default(T);

      foreach (var item in source) {
        if (first || !equals(item, prior))
          yield return item;

        first = false;
        prior = item;
      }
    }
  }

Then

  List<string> result = foo
    .DistinctSuccessive()
    .ToList();
Dmitry Bychenko
  • 180,369
  • 20
  • 160
  • 215
-1

I personally prefere @fubo answer, but just to show that there is more variants:

var data = new[] { 12, 14, 14, 12, 12, 14 };
var result = data.Aggregate(new List<int>(), (a, e) => { if (a.FirstOrDefault() != e) a.Insert(0, e); return a; });
result.Reverse(); // we builded a list in a back order, so reverse it!
vasily.sib
  • 3,871
  • 2
  • 23
  • 26