C# Linq-根据属性将列表分组为对

C# Linq-根据属性将列表分组为对,c#,linq,C#,Linq,我有一个对象列表,其中的属性可用于将对象成对划分。我事先知道每个物体都是一对的一部分 下面是一个例子来说明这种情况: 我有一份我想分组成双的单鞋清单 假设我的清单如下: List<Shoe> shoes = new List<Shoe>(); shoes.Add(new Shoe { Id = 19, Brand = "Nike", LeftOrRight = LeftOrRight.L }); shoes.Add(new Shoe { Id = 29, Brand

我有一个对象列表,其中的属性可用于将对象成对划分。我事先知道每个物体都是一对的一部分

下面是一个例子来说明这种情况:


我有一份我想分组成双的单鞋清单

假设我的清单如下:

List<Shoe> shoes = new List<Shoe>();

shoes.Add(new Shoe { Id = 19, Brand = "Nike", LeftOrRight = LeftOrRight.L });
shoes.Add(new Shoe { Id = 29, Brand = "Nike", LeftOrRight = LeftOrRight.R });
shoes.Add(new Shoe { Id = 11, Brand = "Nike", LeftOrRight = LeftOrRight.L });
shoes.Add(new Shoe { Id = 60, Brand = "Nike", LeftOrRight = LeftOrRight.R });
shoes.Add(new Shoe { Id = 65, Brand = "Asics", LeftOrRight = LeftOrRight.L });
shoes.Add(new Shoe { Id = 82, Brand = "Asics", LeftOrRight = LeftOrRight.R });

哪些语句可用于将这些项分组成对

var shoesByBrand = shoes.GroupBy(s => s.Brand);
foreach (var byBrand in shoesByBrand)
{
    var lefts = byBrand.Where(s => s.LeftOrRight == LeftOrRight.L);
    var rights = byBrand.Where(s => s.LeftOrRight == LeftOrRight.R);
    var pairs = lefts.Zip(rights,(l, r) => new {Left = l, Right = r});

    foreach(var p in pairs)
    {
        Console.WriteLine("Pair:  {{{0}, {1}}}", p.Left.Id, p.Right.Id);
    }

    Console.WriteLine();
}

注意:Zip只能尽可能多地配对。如果您有额外的权限或权限,它们将不会被报告。

纯函数LINQ,使用
SelectMany
Zip
,生成
IEnumerable
元组:

IEnumerable<Tuple<Shoe, Shoe>> pairs = shoes
    .GroupBy(shoe => shoe.Brand)
    .SelectMany(brand=>
        Enumerable.Zip(
            brand.Where(shoe=>shoe.LeftOrRight == LeftOrRight.L),
            brand.Where(shoe=>shoe.LeftOrRight == LeftOrRight.R),
            Tuple.Create
        )
    );
IEnumerable pairs=鞋
.GroupBy(鞋=>shoe.Brand)
.SelectMany(品牌=>
可枚举.Zip(
brand.Where(shoe=>shoe.LeftOrRight==LeftOrRight.L),
brand.Where(shoe=>shoe.LeftOrRight==LeftOrRight.R),
Tuple.Create
)
);
一种方法:

var pairs = shoes.GroupBy(s => s.Brand)
                 .Select(g => g.GroupBy(s => s.LeftOrRight));
                 .SelectMany(Enumerable.Zip(g => g.First(), g => g.Last(),Tuple.Create));
这可能是对我最初想法的一个改进(这个想法很好),对于每个品牌的鞋子,它只需重复收集一次,就可以将它们分成左鞋和右鞋。直觉告诉我们,如果有很多品牌的鞋子,速度应该会更快


它所做的是按品牌对鞋进行分组,然后在每个品牌内按左/右进行分组。然后,它开始随机匹配每个品牌的左鞋和同一品牌的右鞋,依次对所有品牌进行匹配。

是什么阻止了{19,60}双鞋的出现?@AustinSalonen该双鞋也是可以接受的。唯一的限制是这对鞋必须是同一品牌的,有一只左鞋和一只右鞋。@Ryan Kohn您需要另一个属性,例如ProductName,并且每对应该是一对的L和R必须具有相同的ProductName。然后按产品名称分组,而不是按品牌分组,因为按品牌分组,Nike组有4个项目,Asics+1组有2个项目。我需要阅读GroupBy上的内容,并将其压缩,它看起来很强大:)顺便说一句,你是指Console.ReadKey();最后呢?我认为只要品牌是一样的,任何左和右都可以走到一起。OP中写道:“这双鞋也是可以接受的。唯一的限制是,这双鞋必须是同一品牌的,左右各有一双。”@aquinas:如果你开始列出所有可能的鞋款,产量就会激增,鞋款相对较少。问题中有一些含糊不清的地方,但你和我的答案应该包括在内。虽然Thom Smith的答案更简洁,但我最终还是使用了这个答案,因为代码更清晰。这是一个不错的解决方案,但同样,没有任何一对(左右)是同一品牌的可以分组吗?有了这个解决方案,Austin's会根据鞋子在列表中出现的顺序得到不同的配对。所以,如果我加上鞋子11和60,那么它们将形成一对,但如果我先加上11和29,那么它们将形成一对。这就是人们所期望的吗?我们需要OP来衡量。@aquinas:OP已经澄清,在本次讨论中,同一品牌的任何两双左脚鞋都是相同的。我的解释是,每双鞋在输出中应该只出现一次,只要每双鞋都有效,顺序就不相关。OP在这一点上可能有点含糊。
IEnumerable<Tuple<Shoe, Shoe>> pairs = shoes
    .GroupBy(shoe => shoe.Brand)
    .SelectMany(brand=>
        Enumerable.Zip(
            brand.Where(shoe=>shoe.LeftOrRight == LeftOrRight.L),
            brand.Where(shoe=>shoe.LeftOrRight == LeftOrRight.R),
            Tuple.Create
        )
    );
var pairs = shoes.GroupBy(s => s.Brand)
                 .Select(g => g.GroupBy(s => s.LeftOrRight));
                 .SelectMany(Enumerable.Zip(g => g.First(), g => g.Last(),Tuple.Create));