【问题标题】:Is there a Linq equivalent to the unix command uniq是否有与 unix 命令 uniq 等效的 Linq
【发布时间】:2018-04-05 06:56:19
【问题描述】:

我进行的每个搜索都假定“Distinct()”,但这不是我的要求。我只想删除所有重复。是否有任何使用 linq 的选项(即 Enumerable 扩展)?

例如(在 C# 中)

int[] input = new [] {1,2,3,3,4,5,5,5,6,6,5,4,4,3,2,1,6};

int[] expected = new [] {1,2,3,4,5,6,5,4,3,2,1,6};

【问题讨论】:

  • 所以你只想删除相同的连续数字?那么一个普通的 for 循环会比 linq 快
  • 嗯。没有。 Linq 适用于集合/列表。因此,要执行您想要的操作,您需要首先将您的集合分成许多集合 - 在您选择的任何边界处,然后您可以使用 linq 删除每个较小集合中的重复项..然后重新组合。
  • LINQ 类似于 SQL,而不是 shell 命令。您也不是在询问 unique 条目,而是在询问非重复条目。有很多方法可以做到这一点,例如通过对项目本身进行分组,或者创建一个仅在第一次遇到项目时产生的迭代器
  • 顺便说一句,你为什么要这个?可能有更好的选择,例如 ReactiveX 更擅长处理事件序列,并且已经包含用于去抖动、窗口等操作。它已经有一个 DistinctUntilChanged LINQ 操作符
  • 我建议从GroupAdjacent - github.com/morelinq/MoreLINQ#groupadjacent开始

标签: c# linq ienumerable uniq


【解决方案1】:

您要求的是非重复元素,而不是唯一元素。 LINQ-to-Objects 操作本质上是迭代器。您可以编写自己的迭代器方法,该方法仅在第一次遇到项目时产生,例如:

public static IEnumerable<int> DistinctUntilChanged(this IEnumerable<int> source)
{
    int? previous=null;
    foreach(var item in source)
    {
        if (item!=previous)
        {
            previous=item;
            yield return item;
        }
    }
}

var input = new [] {1,2,3,3,4,5,5,5,6,6,5,4,4,3,2,1,6};
var result=input.DistinctUntilChanged().ToArray();

结果将是:

{1,2,3,4,5,6,5,4,3,2,1,6};

更新

另一种选择是使用 System.Reactive 库中的Observable.DistinctUntilChanged,例如:

var input = new[] { 1, 2, 3, 3, 4, 5, 5, 5, 6, 6, 5, 4, 4, 3, 2, 1, 6 };
var result = input.ToObservable()
                  .DistinctUntilChanged()
                  .ToEnumerable()
                  .ToArray();

System.Reactive 和 Reactive Extensions 旨在使用基本的 LINQ 运算符等来处理事件序列。不过,使用 ToObservable()ToEnumerable() 在 Observable 和 Enumerable 之间转换很容易,因此它们可用于处理任何集合。毕竟,事件序列类似于“无限”序列

更新 2

如果对使用int? 存储前一个数字有任何混淆,即使与源的第一个 元素也可以轻松进行比较,而无需实际调用First()。如果它是,例如int previous=0; 并且第一个元素为0,则比较将过滤掉第一个元素。

通过在 C# 中使用 int? 或在 F# 中使用 int optionMaybe&lt;int&gt; 如果我们有一个 Maybe monad,我们可以区分无初始值和初始值 0。

Observable.DistinctUntilChanged 使用一个标志来检查我们是否正在检查第一个元素。等效代码为:

    public static IEnumerable<int> NonRepeating(this IEnumerable<int> source)
    {
        int previous =0;
        bool isAssigned=false;
        foreach (var item in source)
        {
            if (!isAssigned || item != previous)
            {
                isAssigned = true;
                previous = item;
                yield return item;
            }
        }
    }

更多LINQ

最后,可以使用MoreLinq 库中的 GroupAdjacent 方法将重复项组合在一起。每个组都包含重复的源元素。在这种特殊情况下,尽管我们只需要键值:

var result = input.GroupAdjacent(i => i).Select(i => i.Key).ToArray();

GroupAdjacent 的好处是可以在分组时转换元素,例如:

input.GroupAdjacent(i => i,i=>$"Number {i}")

将返回字符串分组。

【讨论】:

  • 为什么是int? 而不是int
  • 如果第一个数字是 0 会发生什么?
  • 0 仍然是有效数字
  • @SimonPrice。是的。这意味着它不能用作“缺失”值来与 IEnumerable 中的第一个元素进行比较
  • 回应你的观点,'如果第一个数字是 0 会发生什么'这不会是一个问题,因为它仍然是一个有效的整数,只有当传入一个 null 时才需要这样做,即应该是你的问题。但那是 OP 在他的代码中更早处理以确保数组中没有空值
【解决方案2】:

使用 linq 是可能的,但对于性能和可读性而言,简单的 for 循环可能是更好的选择。

int[] input = new[] { 1, 2, 3, 3, 4, 5, 5, 5, 6, 6, 5, 4, 4, 3, 2, 1, 6 };
var result = input.Where((x, i) => i == 0 || x != input[i - 1]).ToArray();

【讨论】:

  • 一个循环会执行与 this 代码执行的相同操作
  • @PanagiotisKanavos,你说得对,循环也可以完成这项工作,但 OP 要求的是 LINQ 以执行此操作。但是,我仍然赞成您的回答,因为这在技术上也是正确的,但这是最好的答案
  • @SimonPrice 否,因为两者都使用 LINQ。 LINQ to Objects 操作只是 iterators
  • @PanagiotisKanavos,我不会和你争论,尽管在技术上是正确的并且给出了答案和方法,但你是答案,不符合 OP 的需求
  • @SimonPrice 恐怕你误解了 LINQ 是什么。 This is the source for Where。它返回一个迭代器。您还可以使用返回 IEnumerable&lt;T&gt; 并使用 yield 的方法编写迭代器
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-02-13
  • 2011-07-12
  • 1970-01-01
  • 1970-01-01
  • 2019-03-23
  • 2013-11-10
  • 2012-04-10
相关资源
最近更新 更多