帮助需要有排序的元组和linq查询

时间:2018-03-14 10:18:15

标签: c# linq tuples

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

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

我想从中提取一个元组列表,没有重复,按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;
        }
    }
}

2 个答案:

答案 0 :(得分:0)

我同意其他人认为循环可能是最好的解决方案,但如果您真的想使用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

答案 1 :(得分:0)

为了使用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