【问题标题】:Split a list into +ve & -ve lists using c#使用 c# 将列表拆分为 +ve 和 -ve 列表
【发布时间】:2011-05-23 19:12:50
【问题描述】:

我有一个 +ve 和 -ve 值列表,我需要:

  • 对它们进行排序
  • 将它们转换为 int,我将它们乘以 100 :)
  • 将它们分成两个列表
  • 取出共同的值,应该是+ve和-ve对

例如

input = 1.00,2.92,-2.92,3.00,7.56,-7.56,8.00, -100.93, -40.56 ......

最终目标:

列表A = 1、3、8 列表B = -4056,-10093

我正在寻找改进以下代码的建议,速度和准确性都很重要。

var results = new List<decimal>(input.Select(x => x*100 ));
results.Sort();
var listA = results.FindAll(x => ((decimal)x > 0));
var listB = results.FindAll(x => ((decimal)x < 0));
decimal[] FromA_NotIn_B = listA.Except(listB.Select(X => (X = X * -1))).ToArray();
decimal[] FromB_NotIn_A = listB.Except(listA.Select(X => (X = X * -1))).ToArray();

【问题讨论】:

  • 将它们分成 2 个列表您是指 -ve 列表和 +ve 列表吗?
  • 公用值是指公用绝对值吗?
  • 这个问题很不清楚,请澄清
  • Reniuz 是的,将 1 个列表分成 2 个,1 个用于正值,一个用于 -ve 值
  • Hogan:我所说的常用值是 + 和 -ve 对,例如+ve 列表有 +2 和 -ve 列表有 -2 所以目标是从这两个列表中删除 +2 和 -2。

标签: c# linq list sorting


【解决方案1】:

您的代码在准确性方面是正确的(十进制在反转其符号时不会降低精度)。通过使用循环编写它并进行手动完全外部合并连接,它的速度肯定可以提高 20 倍。但是,如果性能很关键,我只会推荐这个,因为这会使代码大小增加 5 倍。

编辑:合并连接意味着并排获取两个排序列表,并以可以找到相等元素的方式沿着两个列表遍历,即使两个数组中都有多余的元素。这是合并排序中合并步骤的扩展。完全外部意味着它在数据库中的含义:保留仅在其中一个数组中的值。

唯一的要求,排序列表,是给定的,因为您对结果列表进行排序。

您的代码将像这样工作:

var results = new List<decimal>(input.Select(x => x*100 ));
results.Sort();
var listA = new List<decimal>();
var listB = new List<decimal>();

//now to the merge-join and fill listA and B

merge-join 实现起来会很复杂,但为了让您入门,这里是一个基于迭代器的版本,我在项目中使用它来连接两个我永远无法加载到内存但已排序的巨大文件之间的连接。

    public static IEnumerable<TResult> MergeJoin_OneToOne<TLeft, TRight, TResult>(
        IEnumerable<TLeft> leftCollection,
        IEnumerable<TRight> rightCollection,
        Func<TLeft, TRight, int> comparison,
        Func<TLeft, TResult> onlyLeftSelector,
        Func<TRight, TResult> onlyRightSelector,
        Func<TLeft, TRight, TResult> bothSelector)
    {
        return MergeJoin_OneToOne_Impl(leftCollection, rightCollection, comparison, onlyLeftSelector, onlyRightSelector, bothSelector);
    }

    static IEnumerable<TResult> MergeJoin_OneToOne_Impl<TLeft, TRight, TResult>(
        IEnumerable<TLeft> leftCollection,
        IEnumerable<TRight> rightCollection,
        Func<TLeft, TRight, int> comparison,
        Func<TLeft, TResult> onlyLeftSelector,
        Func<TRight, TResult> onlyRightSelector,
        Func<TLeft, TRight, TResult> bothSelector)
    {
        if (leftCollection == null) throw new ArgumentNullException("leftCollection");
        if (rightCollection == null) throw new ArgumentNullException("rightCollection");
        if (comparison == null) throw new ArgumentNullException("comparison");
        if (onlyLeftSelector == null) throw new ArgumentNullException("onlyLeftSelector");
        if (onlyRightSelector == null) throw new ArgumentNullException("onlyRightSelector");
        if (bothSelector == null) throw new ArgumentNullException("bothSelector");

        using (var leftEnum = leftCollection.GetEnumerator())
        using (var rightEnum = rightCollection.GetEnumerator())
        {
            if (!leftEnum.MoveNext())
            {
                while (rightEnum.MoveNext()) yield return onlyRightSelector(rightEnum.Current);
                yield break;
            }

            if (!rightEnum.MoveNext())
            {
                do
                {
                    yield return onlyLeftSelector(leftEnum.Current);
                } while (leftEnum.MoveNext());
                yield break;
            }

            while (true)
            {
                int cmp = comparison(leftEnum.Current, rightEnum.Current);
                if (cmp == 0)
                {
                    yield return bothSelector(leftEnum.Current, rightEnum.Current);

                    if (!leftEnum.MoveNext())
                    {
                        while (rightEnum.MoveNext())
                        {
                            yield return onlyRightSelector(rightEnum.Current);
                        }
                        yield break;
                    }
                    if (!rightEnum.MoveNext())
                    {
                        do
                        {
                            yield return onlyLeftSelector(leftEnum.Current);
                        } while (leftEnum.MoveNext());
                        yield break;
                    }
                }
                else if (cmp < 0)
                {
                    yield return onlyLeftSelector(leftEnum.Current);

                    if (!leftEnum.MoveNext())
                    {
                        do
                        {
                            yield return onlyRightSelector(rightEnum.Current);
                        } while (rightEnum.MoveNext());
                        yield break;
                    }
                }
                else
                {
                    yield return onlyRightSelector(rightEnum.Current);

                    if (!rightEnum.MoveNext())
                    {
                        do
                        {
                            yield return onlyLeftSelector(leftEnum.Current);
                        } while (leftEnum.MoveNext());
                        yield break;
                    }
                }
            }
        }
    }

出于性能原因,您将此函数专门用于IList&lt;decimal&gt; 元素。踢掉迭代器的东西并使用两个整数来表示列表中的当前位置。遍历 listA 时忽略 lt 0 且 B 相同的元素。

速度提升将来自多项改进:对象分配大大减少;由 except 执行的散列操作消失了;没有 lambda,也没有间接。

【讨论】:

  • usr:我想从不担心 LoC 的代码中获得更好的性能,能否请您澄清一下“手动完全外部合并连接”的含义。
  • 谢谢,我看看这个我在这里查看原始性能,我要使用的列表将有 000 的小数。
【解决方案2】:

作品:

    static void Main(string[] args)
    {
        double[] input = { 1.00, 2.92, -2.92, 3.00, 7.56, -7.56, 8.00, -100.93, -40.56 };
        IEnumerable<int> intsWithouDuplicates = input.Where(d => !input.Contains(-d)).Select(d => (int)(d * 100)).OrderBy(i => i);
        var listA = intsWithouDuplicates.Where(i => i >= 0);
        var listB = intsWithouDuplicates.Where(i => i < 0);

        listA.ToList().ForEach(Console.WriteLine);
        Console.WriteLine();
        listB.ToList().ForEach(Console.WriteLine);
        Console.ReadKey();
    }

【讨论】:

  • 谢谢,我也看看这个我在这里查看原始性能,我要使用的列表将有 000 的小数。
【解决方案3】:

这应该更快,并且可以可靠地删除重复项。
我跳过了“乘以 100”的要求,因为它对概念没有帮助:

1) 按 ABS 值排序
2) 与栈顶比较,如果匹配则弹出,否则推送。
3) 在进程结束时,堆栈只包含没有匹配的元素

      decimal[] input = new decimal[] { -6, 1, 2, 3, -1, 2, 2, -2, 6, 7, -4, 4, -7, 8, -4 };
      Stack<decimal> st = new Stack<decimal>();

      IEnumerable<decimal> sorted = input.OrderBy(X => X > 0 ? X : -X);

      foreach (decimal item in sorted)
        if (st.Any() && st.Peek() == -item)
          st.Pop();
        else
          st.Push(item);

      decimal[] pos = st.Where(X => X >= 0).ToArray();
      decimal[] neg = st.Where(X => X < 0).ToArray();

【讨论】: