【问题标题】:helpd need with sorted tuple and linq query帮助需要排序的元组和 linq 查询
【发布时间】:2018-03-14 10:18:14
【问题描述】:

我写了一个小测试用例来解释我的问题。

我能够以某种方式查询我的数据库以获取元组列表。

我想从中提取一个元组列表,没有重复,按 Item1 排序...这很好,但现在我总是想在 Item2 未按降序排序时删除元组。

我可以通过创建一个临时列表然后删除坏元组来做到这一点。

您能帮我直接在 linq 中执行此操作吗(如果可能的话?)?

using System;
using System.Collections.Generic;
using System.Linq;

using NUnit.Framework;

namespace Web.Test
{
    [TestFixture]
    public class ListListTupleTest
    {

        [TestCase]
        public void TestCaseTest_1()
        {
            var input = new List<List<Tuple<int, decimal>>>
            {
                new List<Tuple<int, decimal>>
                {
                    new Tuple<int, decimal>(5, 20),
                    new Tuple<int, decimal>(8, 10)
                },
                new List<Tuple<int, decimal>>
                {
                    new Tuple<int, decimal>(7, 17),
                    new Tuple<int, decimal>(12, 9)
                },
                new List<Tuple<int, decimal>>
                {
                    new Tuple<int, decimal>(7, 17),
                    new Tuple<int, decimal>(15, 10)
                }
            };

            var goal = new List<Tuple<int, decimal>>()
            {
                new Tuple<int, decimal>(5, 20),
                new Tuple<int, decimal>(7, 17),
                new Tuple<int, decimal>(8, 10),
                new Tuple<int, decimal>(12, 9)
            };

            var result = myFunction(input);

            CollectionAssert.AreEqual(result, goal);

        }



        private List<Tuple<int, decimal>> myFunction(List<List<Tuple<int, decimal>>> myList)
        {
            var tmp = myList
                .SelectMany(x => x.ToArray())
                .Distinct()
                .OrderBy(x => x.Item1)
                .ToList();


            var result = new List<Tuple<int, decimal>>();

            if (tmp.Any())
            {
                result.Add(tmp.First());
                decimal current = tmp.First().Item2;

                foreach (var tuple in tmp.Skip(1))
                {
                    if (tuple.Item2 < current)
                    {
                        result.Add(tuple);
                        current = tuple.Item2;
                    }
                }
            }

            return result;
        }
    }
}

【问题讨论】:

  • 如果您需要访问上一个或下一个项目,经典循环通常是更好的方法。 LINQ 方法通常不太可读且效率较低。但是,问题更适合:codereview.stackexchange.com
  • 我同意蒂姆的观点。当您需要比较列表中的项目时,Linq 会变得混乱。最好使用经典循环编写代码。我经常创建一个帮助方法来进行比较,然后将帮助器添加到 linq 中。
  • 问题不仅在于您必须访问上一项,而且您必须保留最后一个最高的项,如果多个项无效,则不一定是上一项。订单可能是10,15,11,那么不应添加最后的 11,因为它仍然高于最后一个有效的 10。循环非常适合此用例。
  • 没错,我的问题是你想如何处理 10,15,111511 都被拒绝了吗?

标签: c# linq tuples


【解决方案1】:

我同意其他人的观点,循环可能是这里最好的解决方案,但如果你真的想使用 LINQ,你可以像这样使用Aggregate

return myList
    .SelectMany(x => x.ToArray())
    .Distinct()
    .OrderBy(x => x.Item1)
    .Aggregate(Enumerable.Empty<Tuple<int, decimal>>(),
        (acc, value) => value.Item2 > acc.LastOrDefault()?.Item2 ? 
                           acc : 
                           acc.Concat(new[] {value}))
    .ToList();

这基本上复制了您的循环:我们从空集 (Enumerable.Empty&lt;Tuple&lt;int, decimal&gt;&gt;()) 开始,然后聚合将值一一提供给我们的回调。根据Item2 的比较,我们要么按原样返回先前的集合,要么将当前项目添加到其中。

您也可以使用List 作为累加器,而不是Enumerable.Empty

return myList
    .SelectMany(x => x.ToArray())
    .Distinct()
    .OrderBy(x => x.Item1)
    .Aggregate(new List<Tuple<int, decimal>>(),
        (acc, value) =>
        {
            var last = acc.Count > 0 ? acc[acc.Count - 1] : null;
            if (last == null || value.Item2 < last.Item2)
                acc.Add(value);
            return acc;
        }); // ToList is not needed - already a list

【讨论】:

    【解决方案2】:

    为此使用 LINQ,我使用了一种基于 APL 扫描运算符的特殊扩展方法 - 它类似于 Aggregate,但返回所有中间结果。在这种情况下,我使用了一个特殊的变体,它自动将结果与 ValueTuple 中的原始数据配对,并在第一个值上使用 Func 初始化状态:

    public static IEnumerable<(TKey Key, T Value)> ScanPair<T, TKey>(this IEnumerable<T> src, Func<T, TKey> fnSeed, Func<(TKey Key, T Value), T, TKey> combine) {
        using (var srce = src.GetEnumerator()) {
            if (srce.MoveNext()) {
                var seed = (fnSeed(srce.Current), srce.Current);
    
                while (srce.MoveNext()) {
                    yield return seed;
                    seed = (combine(seed, srce.Current), srce.Current);
                }
                yield return seed;
            }
        }
    }
    

    现在计算你的结果相对简单——你做的和你说的很像:

    var ans = input.SelectMany(sub => sub, (l, s) => s) // flatten lists to one list
                   .Distinct() // keep only distinct tuples
                   .OrderBy(s => s.Item1) // sort by Item1 ascending
                   .ScanPair(firstTuple => (Item2Desc: true, LastValidItem2: firstTuple.Item2), // set initial state (Is Item2 < previous valid Item2?, Last Valid Item2)
                             (state, cur) => cur.Item2 < state.Key.LastValidItem2 ? (true, cur.Item2) // if still descending, accept Tuple and remember new Item2
                                                                                  : (false, state.Key.LastValidItem2)) // reject Tuple and remember last valid Item2
                   .Where(statekv => statekv.Key.Item2Desc) // filter out invalid Tuples
                   .Select(statekv => statekv.Value); // return just the Tuples
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-08-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多