วิธีที่หรูหราในการรวมคอลเลกชันขององค์ประกอบต่างๆ


96

สมมติว่าฉันมีคอลเล็กชันตามจำนวนที่กำหนดแต่ละรายการมีวัตถุประเภทเดียวกัน (เช่นList<int> fooและList<int> bar) หากคอลเลกชันเหล่านี้อยู่ในคอลเลกชั่น (เช่นประเภทList<List<int>>ฉันสามารถใช้SelectManyเพื่อรวมทั้งหมดเป็นคอลเลกชั่นเดียว

อย่างไรก็ตามหากคอลเลกชันเหล่านี้ไม่ได้อยู่ในคอลเลกชั่นเดียวกันฉันรู้สึกประทับใจที่ต้องเขียนวิธีการดังนี้:

public static IEnumerable<T> Combine<T>(params ICollection<T>[] toCombine)
{
   return toCombine.SelectMany(x => x);
}

ซึ่งฉันจะเรียกแบบนี้ว่า

var combined = Combine(foo, bar);

มีวิธีที่สะอาดและสวยงามในการรวมคอลเลกชัน (จำนวนเท่าใดก็ได้) โดยไม่ต้องเขียนวิธียูทิลิตี้เหมือนCombineข้างต้นหรือไม่? ดูเหมือนง่ายพอที่จะมีวิธีทำใน LINQ แต่อาจจะไม่ใช่


ที่เกี่ยวข้อง: listt-concatenation-for-x-amount-of-list
nawfal

ระวัง Concat สำหรับการเชื่อมต่อตัวนับจำนวนมากอาจทำให้ stack ของคุณระเบิดได้: programmaticallyspeaking.com/…
nawfal

คำตอบ:


110

ฉันคิดว่าคุณอาจกำลังมองหา LINQ .Concat()?

var combined = foo.Concat(bar).Concat(foobar).Concat(...);

หรือ.Union()จะลบองค์ประกอบที่ซ้ำกัน


2
ขอบคุณ; เหตุผลเดียวที่ฉันไม่ทำสิ่งนี้ตั้งแต่แรกก็คือ (สำหรับฉัน) ดูเหมือนว่าจะทำให้คุณต้องจัดการกับคอลเลกชันมากขึ้น อย่างไรก็ตามสิ่งนี้มีประโยชน์ในการใช้ฟังก์ชัน LINQ ที่มีอยู่ซึ่งนักพัฒนาในอนาคตน่าจะคุ้นเคยอยู่แล้ว
Donut

2
ขอบคุณสำหรับ.Union()เคล็ดลับ โปรดจำไว้ว่าคุณจะต้องใช้ IComparer กับประเภทที่กำหนดเองของคุณในกรณีที่เป็นเช่นนั้น
Gonzo345

30

สำหรับฉันแล้วConcatเนื่องจากวิธีการขยายนั้นไม่สวยงามมากในโค้ดของฉันเมื่อฉันมีลำดับขนาดใหญ่หลายรายการเพื่อเชื่อมต่อ นี่เป็นเพียงปัญหาการเยื้อง / การจัดรูปแบบ codde และเป็นสิ่งที่เป็นส่วนตัวมาก

แน่นอนว่ามันดูดีแบบนี้:

var list = list1.Concat(list2).Concat(list3);

อ่านไม่ออกเมื่ออ่านว่า:

var list = list1.Select(x = > x)
   .Concat(list2.Where(x => true)
   .Concat(list3.OrderBy(x => x));

หรือเมื่อดูเหมือนว่า:

return Normalize(list1, a, b)
    .Concat(Normalize(list2, b, c))
       .Concat(Normalize(list3, c, d));

หรือรูปแบบที่คุณต้องการ สิ่งต่างๆแย่ลงเมื่อมีการต่อกันที่ซับซ้อนมากขึ้น สาเหตุของความไม่สอดคล้องกันทางปัญญาของฉันกับรูปแบบข้างต้นคือลำดับแรกอยู่นอกConcatวิธีการในขณะที่ลำดับที่ตามมาอยู่ข้างใน ฉันชอบที่จะเรียกConcatวิธีการแบบคงที่โดยตรงไม่ใช่สไตล์ส่วนขยาย:

var list = Enumerable.Concat(list1.Select(x => x),
                             list2.Where(x => true));

สำหรับจำนวนลำดับที่มากขึ้นฉันใช้วิธีการแบบคงที่เช่นเดียวกับใน OP:

public static IEnumerable<T> Concat<T>(params IEnumerable<T>[] sequences)
{
    return sequences.SelectMany(x => x);
}

ดังนั้นฉันสามารถเขียน:

return EnumerableEx.Concat
(
    list1.Select(x = > x),
    list2.Where(x => true),
    list3.OrderBy(x => x)
);

ดูดีขึ้น. ชื่อชั้นเรียนพิเศษที่ซ้ำซ้อนที่ฉันต้องเขียนไม่ใช่ปัญหาสำหรับฉันเพราะฉันคิดว่าลำดับของฉันดูสะอาดขึ้นด้วยการConcatโทร ปัญหาในC # 6น้อยกว่า คุณสามารถเขียน:

return Concat(list1.Select(x = > x),
              list2.Where(x => true),
              list3.OrderBy(x => x));

หวังว่าเราจะมีรายการตัวดำเนินการเรียงต่อกันใน C # สิ่งที่ต้องการ:

list1 @ list2 // F#
list1 ++ list2 // Scala

วิธีนั้นสะอาดกว่ามาก


4
ขอขอบคุณสำหรับความกังวลของคุณสำหรับรูปแบบการเข้ารหัส สง่างามกว่านี้มาก
Bryan Rayner

บางทีคำตอบที่มีขนาดเล็กอาจรวมเข้ากับคำตอบด้านบนได้
Bryan Rayner

2
ฉันเช่นนี้การจัดรูปแบบเป็นอย่างดี - ถึงแม้ว่าฉันชอบที่จะดำเนินการการดำเนินงานแต่ละรายการ (เช่นWhere, OrderBy) ก่อนเพื่อความชัดเจนโดยเฉพาะอย่างยิ่งถ้าพวกเขากำลังอะไรที่ซับซ้อนกว่าตัวอย่างนี้
brichins

27

สำหรับกรณีที่เมื่อคุณทำมีการเก็บรวบรวมคอลเลกชันคือหนึ่งList<List<T>>, Enumerable.Aggregateเป็นวิธีที่สง่างามมากขึ้นที่จะรวมทุกรายการเป็นหนึ่ง:

var combined = lists.Aggregate((acc, list) => { return acc.Concat(list); });

1
มาlistsที่นี่ก่อนได้อย่างไร? นั่นเป็นปัญหาแรกที่ OP มี หากเขามีcollection<collection>จุดเริ่มต้นก็SelectManyเป็นวิธีที่ง่ายกว่า
nawfal

ไม่แน่ใจว่าเกิดอะไรขึ้น แต่ดูเหมือนว่าจะตอบคำถามผิด แก้ไขคำตอบเพื่อสะท้อนสิ่งนี้ หลายคนดูเหมือนจะจบลงที่นี่เพราะต้องรวมรายการ <List <T>>
astreltsov



7

คุณสามารถใช้ Aggregate ร่วมกับ Concat ...

        var listOfLists = new List<List<int>>
        {
            new List<int> {1, 2, 3, 4},
            new List<int> {5, 6, 7, 8},
            new List<int> {9, 10}
        };

        IEnumerable<int> combined = new List<int>();
        combined = listOfLists.Aggregate(combined, (current, list) => current.Concat(list)).ToList();

1
ทางออกที่ดีที่สุดจนถึงตอนนี้
Oleg

@ โอเล็กSelectManyนั้นง่ายกว่า
nawfal

6

วิธีเดียวที่ฉันเห็นคือใช้ Concat()

 var foo = new List<int> { 1, 2, 3 };
 var bar = new List<int> { 4, 5, 6 };
 var tor = new List<int> { 7, 8, 9 };

 var result = foo.Concat(bar).Concat(tor);

แต่คุณควรตัดสินใจว่าอะไรดีกว่า:

var result = Combine(foo, bar, tor);

หรือ

var result = foo.Concat(bar).Concat(tor);

จุดหนึ่งเหตุผลที่Concat()จะเป็นทางเลือกที่ดีกว่าก็คือว่ามันจะเห็นได้ชัดมากขึ้นสำหรับการพัฒนาอื่น อ่านง่ายและเรียบง่ายมากขึ้น


3

คุณสามารถใช้ Union ได้ดังนี้:

var combined=foo.Union(bar).Union(baz)...

การดำเนินการนี้จะลบองค์ประกอบที่เหมือนกันออกไปดังนั้นหากคุณมีองค์ประกอบเหล่านั้นคุณอาจต้องการใช้Concatแทน


4
ไม่! Enumerable.Unionเป็นเซตยูเนี่ยนที่ไม่ได้ผลลัพธ์ที่ต้องการ (ให้ผลลัพธ์ที่ซ้ำกันเพียงครั้งเดียว)
สัน

ใช่ฉันต้องการอินสแตนซ์เฉพาะของออบเจ็กต์เพราะฉันต้องทำการประมวลผลพิเศษกับพวกมัน การใช้intในตัวอย่างของฉันอาจทำให้ผู้คนผิดหวังเล็กน้อย แต่Unionจะใช้ไม่ได้กับฉันในกรณีนี้
Donut

2

เทคนิคสองสามข้อโดยใช้ Collection Initializers -

สมมติว่ารายการเหล่านี้:

var list1 = new List<int> { 1, 2, 3 };
var list2 = new List<int> { 4, 5, 6 };

SelectMany ด้วยตัวเริ่มต้นอาร์เรย์ (ไม่ได้สวยหรูสำหรับฉัน แต่ไม่ต้องพึ่งพาฟังก์ชันตัวช่วยใด ๆ ):

var combined = new []{ list1, list2 }.SelectMany(x => x);

กำหนดส่วนขยายรายการสำหรับ Add ที่อนุญาตIEnumerable<T>ในList<T> initializer :

public static class CollectionExtensions
{
    public static void Add<T>(this ICollection<T> collection, IEnumerable<T> items)
    {
        foreach (var item in items) collection.Add(item);
    }
}

จากนั้นคุณสามารถสร้างรายการใหม่ที่มีองค์ประกอบของรายการอื่น ๆ เช่นนี้ (ยังอนุญาตให้ผสมรายการเดียวได้)

var combined = new List<int> { list1, list2, 7, 8 };

1

เนื่องจากคุณเริ่มต้นด้วยคอลเล็กชันแยกต่างหากฉันคิดว่าโซลูชันของคุณค่อนข้างหรูหรา คุณจะต้องทำอะไรบางอย่างเพื่อเย็บเข้าด้วยกัน

มันจะสะดวกกว่าในการสร้างวิธีการขยายจากวิธีการรวมของคุณซึ่งจะทำให้สามารถใช้งานได้ทุกที่ที่คุณไป


ฉันชอบแนวคิดวิธีการขยายฉันไม่ต้องการสร้างวงล้อใหม่หาก LINQ มีวิธีการที่จะทำสิ่งเดียวกันอยู่แล้ว (และให้นักพัฒนารายอื่นเข้าใจได้ทันที)
Donut

1

สิ่งที่คุณต้องการคือสิ่งนี้สำหรับIEnumerable<IEnumerable<T>> lists:

var combined = lists.Aggregate((l1, l2) => l1.Concat(l2));

สิ่งนี้จะรวมรายการทั้งหมดlistsเข้าเป็นหนึ่งเดียวIEnumerable<T>(โดยมีรายการที่ซ้ำกัน) ใช้UnionแทนConcatการลบรายการที่ซ้ำกันดังที่ระบุไว้ในคำตอบอื่น ๆ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.