如何根据属性的顺序值和另一个属性值对C#linq进行分组?

时间:2017-01-16 19:06:29

标签: c# linq

这是一个类的IEnumerable。

IEnumerable<Card> cardList = new List<Card> {
            new Card { CardNumber = 1234, Amount = 10m, DisplayText = "" },
            new Card { CardNumber = 1235, Amount = 10m, DisplayText = "" },
            new Card { CardNumber = 1236, Amount = 10m, DisplayText = "" },
            new Card { CardNumber = 1237, Amount = 10m, DisplayText = "" },
            new Card { CardNumber = 1238, Amount = 10m, DisplayText = "" },
            new Card { CardNumber = 1239, Amount = 15m, DisplayText = "" },
            new Card { CardNumber = 1240, Amount = 10m, DisplayText = "" },
            new Card { CardNumber = 1241, Amount = 10m, DisplayText = "" },
            new Card { CardNumber = 1242, Amount = 25m, DisplayText = "" },
            new Card { CardNumber = 1243, Amount = 25m, DisplayText = "" },
            new Card { CardNumber = 1244, Amount = 25m, DisplayText = "" },
            new Card { CardNumber = 1245, Amount = 25m, DisplayText = "" }
        };

我想要完成的是按照金额和顺序卡号对列表进行分组,这些组中至少有4张卡片,或者它们没有分组。 这是我想要实现的一个例子。 结果将是另一个IEnumerable 并包含此

    Card { CardNumber = null, Amount = 10m, DisplayText = "1234 - 1238" },
    Card { CardNumber = null, Amount = 15m, DisplayText = "1239" },
    Card { CardNumber = null, Amount = 10m, DisplayText = "1240" },
    Card { CardNumber = null, Amount = 10m, DisplayText = "1241" },
    Card { CardNumber = null, Amount = 25m, DisplayText = "1242 - 1245" }

希望在我想要做的事情中明确这一点。任何帮助将非常感激。

谢谢,

3 个答案:

答案 0 :(得分:2)

使用纯LINQ运算符是不可能的。但是可以使用混合标准和自定义LINQ之类的扩展方法。

创建一个自定义方法,允许我们根据谓词接收上一个当前元素来拆分序列:

public static class Extensions
{
    public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> source, Func<T, T, bool> splitOn)
    {
        using (var e = source.GetEnumerator())
        {
            for (bool more = e.MoveNext(); more; )
            {
                var last = e.Current;
                var group = new List<T> { last };
                while ((more = e.MoveNext()) && !splitOn(last, e.Current))
                    group.Add(last = e.Current);
                yield return group;
            }
        }
    }
}

现在,您可以使用以下查询来实现目标:

var result = cardList.OrderBy(c => c.CardNumber)
    .Split((prev, next) => prev.Amount != next.Amount || prev.CardNumber + 1 != next.CardNumber)
    .SelectMany(g => g.Count() >= 4 ?
        new [] { new Card { Amount = g.First().Amount, DisplayText = g.First().CardNumber + " - " + g.Last().CardNumber } } :
        g.Select(c => new Card { Amount = c.Amount, DisplayText = c.CardNumber.ToString() }));

OrderBy后跟自定义Split执行初始分组。剩下的棘手部分是如何根据Count条件对元素进行分组/取消分组,这是通过条件SelectMany方法在一种情况下生成单个项目(通过选择单个项目数组)或展平来实现的。在另一种情况下,使用内部Select组。

答案 1 :(得分:0)

我不认为使用LINQ可以完成这样的事情。

但是如果没有LINQ就可以很容易地实现这一点,例如:(我假设cardListCardNumber排序}

List<Card> result = new List<Card>();

Func<List<Card>,List<Card>> bufferToResult = (buf) =>
{
    List<Card> res = new List<Card>();
    if(buf.Count >= 4) {
        string text = buf[0].CardNumber + " - " + buf[buf.Count-1].CardNumber;
        Card newCard = new Card { Amount = buf[0].Amount, DisplayText = text };
        res.Add(newCard);
    } else {
        foreach(Card c in buf) {
            Card newCard = new Card { Amount = c.Amount, DisplayText = c.CardNumber.ToString() };
            res.Add(newCard);
        }
    }
    return res;
};

List<Card> buffer = new List<Card>();
for(int i=0; i<cardList.Count(); ) {
    Card card = cardList.ElementAt(i);
    if(buffer.Count == 0) {
        buffer.Add(card);
        i++;
    } else if(card.Amount == buffer[0].Amount) {
        buffer.Add(card);
        i++;
    } else {
        result.AddRange(bufferToResult(buffer));
        buffer.Clear();
    }
}
if(buffer.Count > 0)
    result.AddRange(bufferToResult(buffer));

答案 2 :(得分:0)

您可以通过将通过约束的项目分组到新列表中,然后根据组大小填充第二个项目来实现这一目标:

IList<Card> group = new List<Card>();
IList<Card> result = new List<Card>();
for (int i = 0, j = 0; i < cardList.Count - 1; i++)
{
    group.Clear();
    group.Add(cardList[i]);
    for (j = i + 1; j < cardList.Count; j++, i++)
    {
        if (cardList[j].Amount == cardList[i].Amount && cardList[j].CardNumber - cardList[i].CardNumber == 1)
        {
            group.Add(cardList[j]);
        }
        else break;
    }

    if (4 > group.Count)
    {
        foreach (var item in group)
        {
            result.Add(new Card { Amount = item.Amount, DisplayText = item.CardNumber.ToString() });
        }
    }
    else
    {
        result.Add(new Card { Amount = group[0].Amount, DisplayText = string.Format("{0} - {1}", group[0].CardNumber, group.Last().CardNumber) });
    }
}

使用LINQPad的结果:

CardNumber  Amount  DisplayText
null        10      1234 - 1238
null        15      1239
null        10      1240
null        10      1241
null        25      1242 - 1245

我比过于复杂的Linq代码更喜欢这个。