7

我正在寻找一种方法来防止列表中的重复项目但仍保留顺序。例如

1, 2, 3, 4, 4, 4, 1, 1, 2, 3, 4, 4 

应该成为

1, 2, 3, 4, 1, 2, 3, 4

我使用for循环非常不雅地完成了它,检查下一项如下

    public static List<T> RemoveSequencialRepeats<T>(List<T> input) 
    {
        var result = new List<T>();

        for (int index = 0; index < input.Count; index++)
        {
            if (index == input.Count - 1)
            {
                result.Add(input[index]);
            }
            else if (!input[index].Equals(input[index + 1]))
            {
                result.Add(input[index]);
            }
        }

        return result;
    }

有没有更优雅的方法来做到这一点,最好是使用 LINQ?

4

9 回答 9

12

您可以创建扩展方法:

public static IEnumerable<T> RemoveSequentialRepeats<T>(
      this IEnumerable<T> source)
{
    using (var iterator = source.GetEnumerator())
    {
        var comparer = EqualityComparer<T>.Default;

        if (!iterator.MoveNext())
            yield break;

        var current = iterator.Current;
        yield return current;

        while (iterator.MoveNext())
        {
            if (comparer.Equals(iterator.Current, current))
                continue;

            current = iterator.Current;
            yield return current;
        }
    }        
}

用法:

var result = items.RemoveSequentialRepeats().ToList();
于 2013-08-01T07:48:28.190 回答
7

您也可以使用纯LINQ

List<int> list = new List<int>{1, 2, 3, 4, 4, 4, 1, 1, 2, 3, 4, 4};
var result = list.Where((x, i) => i == 0 || x != list[i - 1]);
于 2013-08-01T07:58:36.293 回答
4

您可以编写简单的 LINQ:

var l = new int[] { 1, 2, 3, 4, 4, 4, 1, 1, 2, 3, 4, 4 };
var k = new Nullable<int>();
var nl = l.Where(x => { var res = x != k; k = x; return res; }).ToArray();

int[8] { 1, 2, 3, 4, 1, 2, 3, 4 }

或pythonic(嗯,我最好的尝试)方式:

l.Zip(l.Skip(1), (x, y) => new[] { x, y })
   .Where(z => z[0] != z[1]).Select(a => a[0])
   .Concat(new[] { l[l.Length - 1] }).ToArray()

int[8] { 1, 2, 3, 4, 1, 2, 3, 4 }

最简单的一个(编辑:还没有看到King King已经建议过)

l.Where((x, i) => i == l.Length - 1 || x != l[i + 1]).ToArray()
int[8] { 1, 2, 3, 4, 1, 2, 3, 4 }
于 2013-08-01T08:28:56.217 回答
4

如果你真的很讨厌这个世界,纯 LINQ:

var nmbs = new int[] { 1, 2, 3, 4, 4, 4, 1, 1, 2, 3, 4, 4, 5 };
var res = nmbs
              .Take(1)
              .Concat(
                      nmbs.Skip(1)
                          .Zip(nmbs, (p, q) => new { prev = q, curr = p })
                          .Where(p => p.prev != p.curr)
                          .Select(p => p.curr));

但请注意,您需要枚举(至少部分)可枚举的 3 次(Take、 的“左”部分Zip、 的第一个参数Zip)。这种方法比构建yield方法或直接执行要慢。

解释:

  • 你取第一个数字 ( .Take(1))
  • 您从第二个 ( .Skip(1)) 中取出所有数字并将其与所有数字 ( .Zip(nmbs) 配对。我们将调用curr第一个“集合”prev中的数字和第二个“集合”中的数字 ( (p, q) => new { prev = q, curr = p }))。然后,您只取与前一个数字不同的数字 ( .Where(p => p.prev != p.curr)),然后从这些数字中curr取值并丢弃prev值 ( .Select(p => p.curr))
  • 你连接这两个集合 ( .Concat()
于 2013-08-01T08:59:28.907 回答
3

如果您想要不依赖调用中捕获的结果值的 LINQ 语句,您将需要一些带有聚合的构造,因为它是唯一携带值和操作的方法。即基于 Zaheer Ahmed 的代码:

array.Aggregate(new List<string>(), 
     (items, element) => 
     {
        if (items.Count == 0 || items.Last() != element)
        {
            items.Add(element);
        }
        return items;
     });

或者您甚至可以尝试在没有以下内容的情况下构建列表if

 array.Aggregate(Enumerable.Empty<string>(), 
    (items, element) => items.Concat(
       Enumerable.Repeat(element, 
           items.Count() == 0 || items.Last() != element ? 1:0 ))
    );

请注意,要获得上述示例的合理性能,Aggregate您还需要携带最后一个值(Last必须在每个步骤上迭代整个序列),但是{IsEmpty, LastValue, Sequence}在 a 中携带 3 个值的代码Tuple看起来很奇怪。这些示例仅用于娱乐目的。

另一种选择是将Zip自身移位 1 并返回不相等的元素的数组...

更实用的选择是构建过滤值的迭代器:

IEnumerable<string> NonRepeated(IEnumerable<string> values)
{
    string last = null;
    bool lastSet = false;

    foreach(var element in values)
    {
       if (!lastSet || last != element)
       {
          yield return element;
       }
       last = element;
       lastSet = true;
    }
 }
于 2013-08-01T07:57:47.557 回答
2

检查新列表的最后一个和当前项目是否不同,然后添加到新列表:

List<string> results = new List<string>();
results.Add(array.First());
foreach (var element in array)
{
    if(results[results.Length - 1] != element)
        results.Add(element);
}

或使用 LINQ:

List<int> arr=new List<int>(){1, 2, 3, 4, 4, 4, 1, 1, 2, 3, 4, 4 };
List<int> result = new List<int>() { arr.First() };
arr.Select(x =>
               {
                if (result[result.Length - 1] != x) result.Add(x);
                    return x;
               }).ToList();

对空对象进行适当的验证。

于 2013-08-01T07:43:45.033 回答
1

尝试这个:

class Program
{
    static void Main(string[] args)
    {
        var input = "1, 2, 3, 4, 4, 4, 1, 1, 2, 3, 4, 4 ";
        var list = input.Split(',').Select(i => i.Trim());

        var result = list
            .Select((s, i) => 
                (s != list.Skip(i + 1).FirstOrDefault()) ? s : null)
            .Where(s => s != null)
            .ToList();
    }
}
于 2013-08-01T07:54:41.293 回答
1

这是您需要的代码:

public static List<int> RemoveSequencialRepeats(List<int> input)
{
     var result = new List<int>();

     result.Add(input.First());
     result.AddRange(input.Where(p_element => result.Last() != p_element);
     return result;
 }

LINQ 的魔力是:

 result.Add(input.First());
 result.AddRange(input.Where(p_element => result.Last() != p_element);

或者您可以像这样创建扩展方法:

public static class Program
{

    static void Main(string[] args)
    {       
        List<int> numList=new List<int>(){1,2,2,2,4,5,3,2};

        numList = numList.RemoveSequentialRepeats();
    }

    public static List<T> RemoveSequentialRepeats<T>(this List<T> p_input)
    {
        var result = new List<T> { p_input.First() };

        result.AddRange(p_input.Where(p_element => !result.Last().Equals(p_element)));

        return result;
    }
}
于 2013-08-01T08:03:06.700 回答
0

如果您想引用 F# 项目,您可以编写

let rec dedupe = function
  | x::y::rest when x = y -> x::dedupe rest
  | x::rest -> x::dedupe rest
  | _ -> []
于 2014-08-30T15:13:29.037 回答