帮助需要排序元组和 LINQ 查询



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

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

我想从中提取一个元组列表,没有重复项,按 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,你可以使用这样的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<Tuple<int, decimal>>()(开始,然后聚合一个接一个地给我们的回调值。在那里,我们要么按原样返回以前的集合,要么根据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

为此,我使用了一种基于 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

最新更新