เป็นไปได้ที่จะวนซ้ำไปข้างหลังผ่าน foreach?


130

ฉันรู้ว่าฉันสามารถใช้ไฟล์ forคำสั่งและให้ได้ผลเช่นเดียวกัน แต่ฉันสามารถวนย้อนกลับผ่านforeachลูปใน C # ได้หรือไม่


6
คุณสามารถย้อนกลับองค์ประกอบ (list.Reverse ()) ในรายการก่อนที่คุณจะทำสำหรับแต่ละรายการ
Akaanthan Ccoder

1
ดูเพิ่มเติมว่า why-is-there-no-reverseenumerator-in-c
nawfal

คุณจะต้องสร้างอินสแตนซ์ขององค์ประกอบทั้งหมดในการแจงนับเพื่อที่คุณจะสามารถย้อนกลับลำดับได้ มันอาจเป็นไปไม่ได้ พิจารณาลำดับ: IEnumerable<int> Infinity() { int i = 1; while (true) yield return i++; }คุณจะย้อนกลับได้อย่างไร?
Suncat2000

คำตอบ:


85

เมื่อทำงานกับรายการ (การจัดทำดัชนีโดยตรง) คุณไม่สามารถทำได้อย่างมีประสิทธิภาพเท่ากับการใช้forลูป

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


4
ฉันพบว่าคำสั่งสุดท้ายของคุณกว้างเกินไป แน่นอนว่ามีบางกรณีที่ตัวอย่างเช่น IEnumerable บางประเภทจำเป็นต้องทำซ้ำตามลำดับ? คุณจะไม่ใช้ foreach ในกรณีนั้นหรือ? คุณจะใช้อะไร?
avl_sweden

1
อัปเดต: ดู [คำตอบของไบรอันสำหรับคำถามที่คล้ายกัน [( stackoverflow.com/a/3320924/199364 ) สำหรับคำตอบที่ทันสมัยยิ่งขึ้นโดยใช้ Linq และการอภิปรายทั้งสองรายการและการแจงนับอื่น ๆ
ToolmakerSteve

อัปเดต: Jon Skeetมีคำตอบที่สวยงามยิ่งขึ้นซึ่งตอนนี้เป็นไปได้โดยใช้ " yield return"
ToolmakerSteve

3
ฉันมีปฏิกิริยาเริ่มต้นเช่นเดียวกับ @avl_sweden - "การทำซ้ำตามคำสั่งไม่ควรใช้ foreach" ฟังดูกว้างเกินไป ใช่ foreach เหมาะสำหรับการแสดงงานคู่ขนานที่ไม่ขึ้นกับคำสั่ง แต่มันก็เป็นส่วนสำคัญของการเขียนโปรแกรมแบบวนซ้ำสมัยใหม่ด้วย บางทีประเด็นก็คือมันจะชัดเจน / ปลอดภัยกว่าถ้ามีคำหลักสองคำที่แตกต่างกันเพื่อชี้แจงว่ามีคำหนึ่งที่ยืนยันว่าการทำซ้ำนั้นไม่ขึ้นกับคำสั่งหรือไม่ [ด้วยภาษาเช่น Eiffel ที่สามารถเผยแพร่สัญญาการยืนยันดังกล่าวอาจเป็นจริงหรือเท็จสำหรับรหัสที่ระบุ]
ToolmakerSteve

3
แนวคิดที่ว่า foreach "สร้างขึ้นสำหรับการแสดงลูปที่ไม่ขึ้นกับดัชนีองค์ประกอบและลำดับการวนซ้ำ" นั้นไม่ถูกต้อง ข้อกำหนดภาษา c # ต้องการองค์ประกอบของกระบวนการ foreach ตามลำดับ ไม่ว่าจะโดยใช้ MoveNext บนตัววนซ้ำหรือโดยการประมวลผลดัชนีเริ่มต้นที่ศูนย์และเพิ่มทีละหนึ่งในการวนซ้ำแต่ละอาร์เรย์
Donald Rich

145

หากคุณใช้. NET 3.5 คุณสามารถทำได้:

IEnumerable<int> enumerableThing = ...;
foreach (var x in enumerableThing.Reverse())

มันไม่ได้มีประสิทธิภาพมากนักเนื่องจากต้องผ่านตัวแจงนับไปข้างหน้าโดยวางทุกอย่างไว้ในสแต็กจากนั้นจะเปิดทุกอย่างกลับออกมาในลำดับที่กลับกัน

หากคุณมีคอลเลกชันที่จัดทำดัชนีได้โดยตรง (เช่น IList) คุณควรใช้ไฟล์ forลูปแทน

หากคุณใช้. NET 2.0 และไม่สามารถใช้ for loop ได้ (เช่นคุณมี IEnumerable) คุณจะต้องเขียนฟังก์ชัน Reverse ของคุณเอง สิ่งนี้ควรใช้งานได้:

static IEnumerable<T> Reverse<T>(IEnumerable<T> input)
{
    return new Stack<T>(input);
}

สิ่งนี้อาศัยพฤติกรรมบางอย่างซึ่งอาจไม่ชัดเจนนัก เมื่อคุณส่งผ่าน IE ไปยังตัวสร้างสแต็กไม่ได้มันจะวนซ้ำผ่านมันและดันรายการไปยังสแต็ก เมื่อคุณทำซ้ำในสแต็กมันจะปรากฏสิ่งต่างๆกลับออกมาในลำดับย้อนกลับ

วิธีนี้และReverse()วิธีการขยาย. NET 3.5 จะระเบิดอย่างเห็นได้ชัดหากคุณป้อน IEnumerable ซึ่งไม่เคยหยุดส่งคืนรายการ


ฉันลืมพูดถึง. net v2 เท่านั้น
JL.

4
โซลูชัน. NET 2.0 ที่น่าสนใจ
RichardOD

11
ฉันขาดอะไรไปหรือโซลูชัน. Net 3.5 ของคุณใช้งานไม่ได้จริงหรือ Reverse () ย้อนกลับรายการในตำแหน่งและไม่ส่งคืน น่าเสียดายที่ฉันหวังว่าจะได้รับการแก้ไขเช่นนั้น
user12861

2
ผู้ดูแลระบบบางคนทำเครื่องหมายคำตอบนี้ว่าผิดโปรด Reverse () เป็นโมฆะ [1] และตัวอย่างข้างต้นทำให้เกิดข้อผิดพลาดในการคอมไพล์ [1] msdn.microsoft.com/en-us/library/b0axc2h2(v=vs.110).aspx
MickyD

6
@ user12861, Micky Duncan: คำตอบไม่ผิดคุณขาดอะไรไป มีวิธี Reverse บน System.Collections.Generic.List <T> ซึ่งจะทำการย้อนกลับในสถานที่ ใน. Net 3.5 มีวิธีการขยายบน IEnumerable <T> ชื่อ Reverse ฉันได้เปลี่ยนตัวอย่างจาก var เป็น IEnumerable <int> เพื่อให้ชัดเจนยิ่งขึ้น
Matt Howells

56

ดังที่ 280Z28 กล่าวสำหรับIList<T>คุณสามารถใช้ดัชนีได้ คุณสามารถซ่อนสิ่งนี้ในวิธีการขยาย:

public static IEnumerable<T> FastReverse<T>(this IList<T> items)
{
    for (int i = items.Count-1; i >= 0; i--)
    {
        yield return items[i];
    }
}

ซึ่งจะเร็วกว่าEnumerable.Reverse()ที่บัฟเฟอร์ข้อมูลทั้งหมดก่อน (ฉันไม่เชื่อว่าReverseมีการใช้การเพิ่มประสิทธิภาพในลักษณะCount()นี้) โปรดทราบว่าการบัฟเฟอร์นี้หมายความว่าข้อมูลจะถูกอ่านอย่างสมบูรณ์เมื่อคุณเริ่มทำซ้ำในขณะที่FastReverseจะ "เห็น" การเปลี่ยนแปลงใด ๆ ที่เกิดขึ้นกับรายการในขณะที่คุณทำซ้ำ (นอกจากนี้ยังจะแตกหากคุณลบหลายรายการระหว่างการทำซ้ำ)

สำหรับลำดับทั่วไปไม่มีทางวนซ้ำในทางกลับกัน - ลำดับอาจไม่มีที่สิ้นสุดตัวอย่างเช่น:

public static IEnumerable<T> GetStringsOfIncreasingSize()
{
    string ret = "";
    while (true)
    {
        yield return ret;
        ret = ret + "x";
    }
}

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


แค่อยากรู้ทำไมใช้ "> = 0" แทน "> -1"
Chris S

16
> เหตุใดจึงใช้ "> = 0" แทน "> -1" เพราะ> = 0 สื่อสารเจตนาให้มนุษย์อ่านโค้ดได้ดีกว่า คอมไพเลอร์ควรจะสามารถปรับให้เหมาะสมเท่ากับ> -1 หากทำเช่นนั้นจะช่วยเพิ่มประสิทธิภาพ
Mark Maslar

1
FastReverse (รายการ IList <T> นี้) ควรเป็น FastReverse <T> (รายการ IList <T> นี้ :)
Rob

การแยกไม้ 0 <= i ยิ่งดี หลังจากสอนคณิตศาสตร์เด็ก ๆ มาหลายปีและช่วยเหลือผู้คนในการเขียนโค้ดฉันพบว่ามันช่วยลดข้อผิดพลาดที่ผู้คนทำได้มากหากพวกเขามักจะสลับ a> b เป็น b <a เป็นสิ่งต่างๆแล้วเรียงตามลำดับตามธรรมชาติของบรรทัด ของตัวเลข (หรือแกน X ของระบบพิกัด) - ข้อเสียเปรียบเพียงประการเดียวคือหากคุณต้องการวางสมการลงใน XML ให้พูดว่า a .config
Eske Rahn

13

ก่อนที่จะใช้foreachสำหรับการวนซ้ำให้ย้อนกลับรายการโดยreverseวิธีการ:

    myList.Reverse();
    foreach( List listItem in myList)
    {
       Console.WriteLine(listItem);
    }


คำเตือนเกี่ยวกับสิ่งที่myListจะเป็นประโยชน์ IEnumerable กลับไม่ทำงานที่นี่
nawfal

2
@ MA-Maddin ไม่ทั้งสองต่างกัน ฉันเดาว่าผู้ตอบกำลังอาศัย List <T> ย้อนกลับซึ่งอยู่ในสถานที่
nawfal

จริงๆแล้วฉันไม่รู้ว่าทำไมฉันถึงพูดแบบนั้น ฉันควรจะเพิ่มรายละเอียดเพิ่มเติม ... อย่างไรก็ตามนี่เป็นรหัสที่สับสนเนื่องจากmyListดูเหมือนว่าจะเป็นประเภทSystem.Collections.Generic.List<System.Windows.Documents.List>(หรือListประเภทกำหนดเองอื่น ๆ) มิฉะนั้นรหัสนี้จะไม่ทำงาน: P
Martin Schneider

5

บางครั้งคุณไม่มีความหรูหราในการจัดทำดัชนีหรือบางทีคุณอาจต้องการย้อนกลับผลลัพธ์ของแบบสอบถาม Linq หรือบางทีคุณอาจไม่ต้องการแก้ไขคอลเลกชันแหล่งที่มาหากสิ่งเหล่านี้เป็นจริง Linq สามารถช่วยคุณได้

วิธีการขยาย Linq โดยใช้ชนิดที่ไม่ระบุชื่อด้วย Linq Select เพื่อจัดเตรียมคีย์การเรียงลำดับสำหรับ Linq OrderByDescending

    public static IEnumerable<T> Invert<T>(this IEnumerable<T> source)
    {
        var transform = source.Select(
            (o, i) => new
            {
                Index = i,
                Object = o
            });

        return transform.OrderByDescending(o => o.Index)
                        .Select(o => o.Object);
    }

การใช้งาน:

    var eable = new[]{ "a", "b", "c" };

    foreach(var o in eable.Invert())
    {
        Console.WriteLine(o);
    }

    // "c", "b", "a"

มีชื่อว่า "Invert" เนื่องจากมีความหมายเหมือนกันกับ "Reverse" และเปิดใช้งานการลดความสับสนด้วยการใช้งาน List Reverse

เป็นไปได้ที่จะย้อนกลับช่วงบางช่วงของคอลเลกชันด้วยเช่นกันเนื่องจาก Int32.MinValue และ Int32 MaxValue อยู่นอกช่วงของดัชนีคอลเลกชันใด ๆ เราสามารถใช้ประโยชน์จากช่วงเหล่านี้สำหรับกระบวนการสั่งซื้อ หากดัชนีองค์ประกอบอยู่ต่ำกว่าช่วงที่กำหนดจะมีการกำหนด Int32.MaxValue เพื่อให้ลำดับของมันไม่เปลี่ยนแปลงเมื่อใช้ OrderByDescending ในทำนองเดียวกันองค์ประกอบที่ดัชนีมากกว่าช่วงที่กำหนดจะถูกกำหนด Int32 MinValue เพื่อให้พวกเขา ดูเหมือนว่าจะสิ้นสุดกระบวนการสั่งซื้อ องค์ประกอบทั้งหมดภายในช่วงที่กำหนดจะได้รับการกำหนดดัชนีปกติและจะกลับรายการตามนั้น

    public static IEnumerable<T> Invert<T>(this IEnumerable<T> source, int index, int count)
    {
        var transform = source.Select(
            (o, i) => new
            {
                Index = i < index ? Int32.MaxValue : i >= index + count ? Int32.MinValue : i,
                Object = o
            });

        return transform.OrderByDescending(o => o.Index)
                        .Select(o => o.Object);
    }

การใช้งาน:

    var eable = new[]{ "a", "b", "c", "d" };

    foreach(var o in eable.Invert(1, 2))
    {
        Console.WriteLine(o);
    }

    // "a", "c", "b", "d"

ฉันไม่แน่ใจถึงประสิทธิภาพที่ยอดเยี่ยมของการใช้งาน Linq เหล่านี้เทียบกับการใช้รายการชั่วคราวเพื่อรวมคอลเลคชันสำหรับการย้อนกลับ


ในขณะที่เขียนฉันไม่ทราบถึงการใช้งาน Reverse ของ Linq แต่ก็ยังสนุกกับการทำสิ่งนี้ https://msdn.microsoft.com/en-us/library/vstudio/bb358497(v=vs.100).aspx


4

หากคุณใช้รายการ <T> คุณสามารถใช้รหัสนี้:

List<string> list = new List<string>();
list.Add("1");
list.Add("2");
list.Add("3");
list.Reverse();

นี่เป็นวิธีที่เขียนรายการย้อนกลับในตัวเอง

ตอนนี้ foreach:

foreach(string s in list)
{
    Console.WriteLine(s);
}

ผลลัพธ์คือ:

3
2
1

3

เป็นไปได้ถ้าคุณสามารถเปลี่ยนรหัสคอลเลกชันที่ใช้ IEnumerable หรือ IEnumerable (เช่นการใช้งาน IList ของคุณเอง)

สร้างIterator ที่ทำงานนี้ให้คุณเช่นการใช้งานต่อไปนี้ผ่านอินเทอร์เฟซIEnumerable (สมมติว่า 'items' เป็นฟิลด์ List ในตัวอย่างนี้):

public IEnumerator<TObject> GetEnumerator()
{
    for (var i = items.Count - 1; i >= 0; i--)
    { 
        yield return items[i];
    }
}

IEnumerator IEnumerable.GetEnumerator()
{
    return GetEnumerator();
}

ด้วยเหตุนี้รายการของคุณจะวนซ้ำในลำดับย้อนกลับผ่านรายการของคุณ

เพียงคำใบ้: คุณควรระบุลักษณะการทำงานพิเศษของรายการของคุณอย่างชัดเจนในเอกสารประกอบ (ดีกว่าโดยเลือกชื่อคลาสที่อธิบายตัวเองเช่น Stack หรือ Queue ด้วย)


2

ไม่ ForEach จะวนซ้ำผ่านคอลเลกชันสำหรับแต่ละรายการและคำสั่งซื้อขึ้นอยู่กับว่าใช้ IEnumerable หรือ GetEnumerator ()


1
ดีมีมีการค้ำประกันของการสั่งซื้อขึ้นอยู่กับชนิดคอลเลกชัน
Jon Skeet

1

การอธิบายอย่างละเอียดเล็กน้อยเกี่ยวกับคำตอบที่ดีของJon Skeetสิ่งนี้อาจเป็นประโยชน์:

public static IEnumerable<T> Directional<T>(this IList<T> items, bool Forwards) {
    if (Forwards) foreach (T item in items) yield return item;
    else for (int i = items.Count-1; 0<=i; i--) yield return items[i];
}

แล้วใช้เป็น

foreach (var item in myList.Directional(forwardsCondition)) {
    .
    .
}

-1

ฉันใช้รหัสนี้ซึ่งได้ผล

                if (element.HasAttributes) {

                    foreach(var attr in element.Attributes().Reverse())
                    {

                        if (depth > 1)
                        {
                            elements_upper_hierarchy_text = "";
                            foreach (var ancest  in element.Ancestors().Reverse())
                            {
                                elements_upper_hierarchy_text += ancest.Name + "_";
                            }// foreach(var ancest  in element.Ancestors())

                        }//if (depth > 1)
                        xml_taglist_report += " " + depth  + " " + elements_upper_hierarchy_text+ element.Name + "_" + attr.Name +"(" + attr.Name +")" + "   =   " + attr.Value + "\r\n";
                    }// foreach(var attr in element.Attributes().Reverse())

                }// if (element.HasAttributes) {

2
สิ่งนี้ไม่ดีเนื่องจาก.Reverse()กำลังแก้ไขรายการจริงๆ
Colin Basnett

-8

ทำงานได้ดี

List<string> list = new List<string>();

list.Add("Hello");
list.Add("Who");
list.Add("Are");
list.Add("You");

foreach (String s in list)
{
    Console.WriteLine(list[list.Count - list.IndexOf(s) - 1]);
}

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