ทำซ้ำสองรายการหรืออาร์เรย์ด้วยหนึ่งคำสั่ง ForEach ใน C #


142

นี่เป็นเพียงความรู้ทั่วไป:

ถ้าผมมีสองสมมติว่ารายการและผมอยากจะย้ำทั้งที่มีห่วง foreach เดียวกันเราสามารถทำอย่างนั้น?

แก้ไข

เพียงชี้แจงฉันต้องการทำสิ่งนี้:

List<String> listA = new List<string> { "string", "string" };
List<String> listB = new List<string> { "string", "string" };

for(int i = 0; i < listA.Count; i++)
    listB[i] = listA[i];

แต่ด้วย foreach =)


10
คำสำคัญที่นี่คือ "zip"
Mark Byers

3
คุณต้องการที่จะวนซ้ำสองรายการในแบบคู่ขนาน ? หรือคุณต้องการย้ำรายการแรกก่อนจากนั้นอีกรายการหนึ่ง (ด้วยคำสั่งเดียว)?
Pavel Minaev

ฉันคิดว่าวิธีของคุณดูดีกว่า zip
Alexander

คำตอบ:


275

ซึ่งเรียกว่าการดำเนินการZipและจะได้รับการสนับสนุนใน. NET 4

ด้วยสิ่งนี้คุณจะสามารถเขียนสิ่งที่ชอบ:

var numbers = new [] { 1, 2, 3, 4 };
var words = new [] { "one", "two", "three", "four" };

var numbersAndWords = numbers.Zip(words, (n, w) => new { Number = n, Word = w });
foreach(var nw in numbersAndWords)
{
    Console.WriteLine(nw.Number + nw.Word);
}

เป็นทางเลือกแทนชนิดที่ไม่ระบุชื่อด้วยฟิลด์ที่มีชื่อคุณยังสามารถบันทึกวงเล็บปีกกาโดยใช้ Tuple และ Tuple แบบคงที่ผู้ช่วยสร้าง:

foreach (var nw in numbers.Zip(words, Tuple.Create)) 
{
    Console.WriteLine(nw.Item1 + nw.Item2);
}

2
ต่อไปนี้เป็นบทความเกี่ยวกับมันcommunity.bartdesmet.net/blogs/bart/archive/2008/11/03/...
เจมส์ Kolpack

2
ไม่ทราบอะไรเกี่ยวกับการดำเนินการของ Zip เหล่านั้นฉันจะทำวิจัยเล็กน้อยในหัวข้อนั้น ขอบคุณ!
Hugo

4
@Hugo: มันเป็นสร้างมาตรฐานในหน้าที่ Programming :)
มาร์ค Seemann

คุณจะต้องใช้ System.Linq;
Jahmic

5
ตั้งแต่ C # 7 คุณสามารถใช้ ValueTuple (ดูstackoverflow.com/a/45617748 ) แทนประเภทที่ไม่ระบุตัวตนหรือ Tuple.Create foreach ((var number, var word) in numbers.Zip(words, (n, w) => (n, w))) { ... }กล่าวคือ
Erlend Graff

14

หากคุณไม่ต้องการรอ. NET 4.0 คุณสามารถใช้Zipวิธีการของคุณเอง การทำงานต่อไปนี้กับ. NET 2.0 คุณสามารถปรับการใช้งานขึ้นอยู่กับวิธีที่คุณต้องการจัดการกรณีและปัญหาที่การแจกแจงสองรายการ (หรือรายการ) มีความยาวต่างกัน อันนี้ยังคงสิ้นสุดการแจงนับอีกต่อไปคืนค่าเริ่มต้นสำหรับรายการที่หายไปจากการแจงนับที่สั้นลง

static IEnumerable<KeyValuePair<T, U>> Zip<T, U>(IEnumerable<T> first, IEnumerable<U> second)
{
    IEnumerator<T> firstEnumerator = first.GetEnumerator();
    IEnumerator<U> secondEnumerator = second.GetEnumerator();

    while (firstEnumerator.MoveNext())
    {
        if (secondEnumerator.MoveNext())
        {
            yield return new KeyValuePair<T, U>(firstEnumerator.Current, secondEnumerator.Current);
        }
        else
        {
            yield return new KeyValuePair<T, U>(firstEnumerator.Current, default(U));
        }
    }
    while (secondEnumerator.MoveNext())
    {
        yield return new KeyValuePair<T, U>(default(T), secondEnumerator.Current);
    }
}

static void Test()
{
    IList<string> names = new string[] { "one", "two", "three" };
    IList<int> ids = new int[] { 1, 2, 3, 4 };

    foreach (KeyValuePair<string, int> keyValuePair in ParallelEnumerate(names, ids))
    {
        Console.WriteLine(keyValuePair.Key ?? "<null>" + " - " + keyValuePair.Value.ToString());
    }
}

1
เป็นวิธีที่ดี! :) คุณสามารถทำการปรับเปลี่ยนเล็กน้อยเพื่อใช้ลายเซ็นเดียวกับวิธี. NET 4 Zip msdn.microsoft.com/en-us/library/dd267698.aspxและส่งกลับresultsSelector (ครั้งแรกที่สอง) แทน KVP
Martín Coll

โปรดทราบว่าวิธีการนี้จะไม่กำจัดตัวแจงนับซึ่งอาจกลายเป็นปัญหาเช่นหากใช้กับตัวนับบนบรรทัดของไฟล์ที่เปิดอยู่
Lii

11

คุณสามารถใช้ Union หรือ Concat อดีตจะลบรายการที่ซ้ำกัน แต่ภายหลังไม่ได้

foreach (var item in List1.Union(List1))
{
   //TODO: Real code goes here
}

foreach (var item in List1.Concat(List1))
{
   //TODO: Real code goes here
}

ปัญหาอีกประการหนึ่งของการใช้ยูเนี่ยนคือมันอาจจะทิ้งอินสแตนซ์หากพวกเขาประเมินว่าเท่าเทียม นั่นอาจไม่ใช่สิ่งที่คุณต้องการเสมอไป
Mark Seemann

1
ฉันยากที่ความตั้งใจของเขาคือการใช้คอลเลกชันที่มีประเภทเดียวกัน
อัลเบอสตีน

@ Mark Seemann, i แล้วชี้ให้เห็นว่าเขายังสามารถใช้ Concat
albertein

เช่นเดียวกับ Union, Concat ใช้งานได้หากทั้งสองรายการเป็นประเภทเดียวกัน ไม่สามารถบอกได้ว่านี่คือสิ่งที่ตอบสนองความต้องการ OP หรือไม่ว่า ...
มาร์ค Seemann

สิ่งนี้จะสร้างรายการใหม่ที่มีองค์ประกอบทั้งหมด นี่เป็นความทรงจำที่เสียไป ใช้ Linq Concat แทน
Drew Noakes

3

ต่อไปนี้เป็นวิธีการขยาย IEnumerable <> แบบกำหนดเองที่สามารถใช้เพื่อวนซ้ำสองรายการพร้อมกัน

using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApplication1
{
    public static class LinqCombinedSort
    {
        public static void Test()
        {
            var a = new[] {'a', 'b', 'c', 'd', 'e', 'f'};
            var b = new[] {3, 2, 1, 6, 5, 4};

            var sorted = from ab in a.Combine(b)
                         orderby ab.Second
                         select ab.First;

            foreach(char c in sorted)
            {
                Console.WriteLine(c);
            }
        }

        public static IEnumerable<Pair<TFirst, TSecond>> Combine<TFirst, TSecond>(this IEnumerable<TFirst> s1, IEnumerable<TSecond> s2)
        {
            using (var e1 = s1.GetEnumerator())
            using (var e2 = s2.GetEnumerator())
            {
                while (e1.MoveNext() && e2.MoveNext())
                {
                    yield return new Pair<TFirst, TSecond>(e1.Current, e2.Current);
                }
            }

        }


    }
    public class Pair<TFirst, TSecond>
    {
        private readonly TFirst _first;
        private readonly TSecond _second;
        private int _hashCode;

        public Pair(TFirst first, TSecond second)
        {
            _first = first;
            _second = second;
        }

        public TFirst First
        {
            get
            {
                return _first;
            }
        }

        public TSecond Second
        {
            get
            {
                return _second;
            }
        }

        public override int GetHashCode()
        {
            if (_hashCode == 0)
            {
                _hashCode = (ReferenceEquals(_first, null) ? 213 : _first.GetHashCode())*37 +
                            (ReferenceEquals(_second, null) ? 213 : _second.GetHashCode());
            }
            return _hashCode;
        }

        public override bool Equals(object obj)
        {
            var other = obj as Pair<TFirst, TSecond>;
            if (other == null)
            {
                return false;
            }
            return Equals(_first, other._first) && Equals(_second, other._second);
        }
    }

}

3

ตั้งแต่ C # 7 คุณสามารถใช้ Tuples ...

int[] nums = { 1, 2, 3, 4 };
string[] words = { "one", "two", "three", "four" };

foreach (var tuple in nums.Zip(words, (x, y) => (x, y)))
{
    Console.WriteLine($"{tuple.Item1}: {tuple.Item2}");
}

// or...
foreach (var tuple in nums.Zip(words, (x, y) => (Num: x, Word: y)))
{
    Console.WriteLine($"{tuple.Num}: {tuple.Word}");
}

1
จะเกิดอะไรขึ้นเมื่อทั้งสองรายการมีความยาวไม่เท่ากันในสถานการณ์นี้
John August

ด้วย(x, y) => (x, y)เราสามารถใช้ชื่อtuple.xและtuple.yที่สง่างาม ดังนั้นรูปแบบที่สองอาจเป็น(Num, Word) => (Num, Word)
ขีดกลาง

2
@JohnAugust มันจะยุติลงหลังจากผ่านไปตามลำดับที่สั้นกว่า จากเอกสาร: "ถ้าลำดับไม่ได้มีจำนวนองค์ประกอบเท่ากันวิธีการจะรวมลำดับจนกว่าจะถึงจุดสิ้นสุดของหนึ่งในนั้นตัวอย่างเช่นถ้าลำดับหนึ่งมีสามองค์ประกอบและอีกหนึ่งมีสี่ลำดับผลลัพธ์จะ มีเพียงสามองค์ประกอบ "
gregsmi

0

ไม่คุณต้องใช้ for-loop สำหรับสิ่งนั้น

for (int i = 0; i < lst1.Count; i++)
{
    //lst1[i]...
    //lst2[i]...
}

คุณไม่สามารถทำอะไรเช่น

foreach (var objCurrent1 int lst1, var objCurrent2 in lst2)
{
    //...
}

เกิดอะไรขึ้นถ้าพวกเขามีจำนวนแตกต่างกันอย่างไร
Drew Noakes

จากนั้น foreach ที่จะยอมรับรายการที่แจกแจงโดยพลการก็ไม่ได้ผลเช่นกัน
Maximilian Mayerl

0

หากคุณต้องการองค์ประกอบหนึ่งที่มีองค์ประกอบที่เกี่ยวข้องคุณสามารถทำได้

Enumerable.Range(0, List1.Count).All(x => List1[x] == List2[x]);

สิ่งนั้นจะกลับมาเป็นจริงหากทุกรายการเท่ากับหนึ่งในรายการที่สอง

ถ้านั่นเป็นสิ่งที่คุณต้องการ แต่ก็ไม่ได้ช่วยอะไรมากนักถ้าคุณให้รายละเอียดเพิ่มเติม


0

วิธีนี้จะใช้ได้กับการใช้งานรายการและสามารถใช้เป็นวิธีส่วนขยาย

public void TestMethod()
{
    var first = new List<int> {1, 2, 3, 4, 5};
    var second = new List<string> {"One", "Two", "Three", "Four", "Five"};

    foreach(var value in this.Zip(first, second, (x, y) => new {Number = x, Text = y}))
    {
        Console.WriteLine("{0} - {1}",value.Number, value.Text);
    }
}

public IEnumerable<TResult> Zip<TFirst, TSecond, TResult>(List<TFirst> first, List<TSecond> second, Func<TFirst, TSecond, TResult> selector)
{
    if (first.Count != second.Count)
        throw new Exception();  

    for(var i = 0; i < first.Count; i++)
    {
        yield return selector.Invoke(first[i], second[i]);
    }
}

0

คุณสามารถใช้ตัวแปรจำนวนเต็มเฉพาะถ้ารายการมีความยาวเท่ากัน:

List<classA> listA = fillListA();
List<classB> listB = fillListB();

var i = 0;
foreach(var itemA in listA)
{
    Console.WriteLine(itemA  + listB[i++]);
}

-1

คุณยังสามารถทำสิ่งต่อไปนี้:

var i = 0;
foreach (var itemA in listA)
{
  Console.WriteLine(itemA + listB[i++]);
}

หมายเหตุ: ความยาวของจะต้องเหมือนกันด้วยlistAlistB


-3

ฉันเข้าใจ / หวังว่ารายการจะมีความยาวเท่ากัน: ไม่การเดิมพันเดียวของคุณกำลังดำเนินไปด้วยมาตรฐานเก่าแบบธรรมดาสำหรับการวนซ้ำ

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