Iterate dua Daftar atau Array dengan satu pernyataan ForEach di C #


142

Ini hanya untuk pengetahuan umum:

Jika saya punya dua, katakanlah, Daftar , dan saya ingin beralih keduanya dengan loop foreach yang sama, bisakah kita melakukan itu?

Edit

Hanya untuk memperjelas, saya ingin melakukan ini:

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];

Tetapi dengan foreach =)


10
Kata penting di sini adalah "zip".
Mark Byers

3
Apakah Anda ingin mengulang dua daftar secara paralel ? Atau Anda ingin mengulang daftar pertama, dan yang lain (dengan satu pernyataan)?
Pavel Minaev

Saya pikir cara Anda terlihat lebih baik daripada zip
Alexander

Jawaban:


272

Ini dikenal sebagai operasi Zip dan akan didukung di .NET 4.

Dengan itu, Anda akan dapat menulis sesuatu seperti:

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);
}

Sebagai alternatif untuk jenis anonim dengan bidang bernama, Anda juga dapat menghemat kawat gigi dengan menggunakan Tuple dan Tuple.Create helper statisnya:

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


2
Tidak tahu apa-apa tentang operasi Zip itu, saya akan melakukan riset kecil tentang topik itu. Terima kasih!
Hugo

4
@ Hugo: Ini adalah konstruksi standar dalam Pemrograman Fungsional :)
Mark Seemann

Anda juga perlu menggunakan System.Linq;
Jahmic

5
Sejak C # 7, Anda juga bisa menggunakan ValueTuple (lihat stackoverflow.com/a/45617748 ) alih-alih jenis anonim atau Tuple.Create. Yaitu foreach ((var number, var word) in numbers.Zip(words, (n, w) => (n, w))) { ... }.
Erlend Graff

14

Jika Anda tidak ingin menunggu. NET 4.0, Anda bisa menerapkan Zipmetode Anda sendiri . Berikut ini berfungsi dengan .NET 2.0. Anda dapat menyesuaikan implementasi tergantung pada bagaimana Anda ingin menangani kasus di mana kedua enumerasi (atau daftar) memiliki panjang yang berbeda; yang ini berlanjut hingga akhir penghitungan yang lebih lama, mengembalikan nilai default untuk item yang hilang dari penghitungan yang lebih pendek.

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
Metode yang bagus! :) Anda dapat membuat beberapa penyesuaian untuk menggunakan tanda tangan yang sama dengan .NET 4 metode Zip msdn.microsoft.com/en-us/library/dd267698.aspx dan mengembalikan hasil Pemilihan (pertama, kedua) alih-alih KVP.
Martín Coll

Perhatikan bahwa metode ini tidak membuang enumeratornya yang dapat menjadi masalah, misalnya jika digunakan dengan enumerables pada baris file yang terbuka.
Lii

11

Anda dapat menggunakan Union atau Concat, yang pertama menghapus duplikat, yang kemudian tidak

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

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

Masalah lain dengan menggunakan Union adalah bahwa ia dapat membuang instance jika mereka menilai sama. Itu mungkin tidak selalu seperti yang Anda inginkan.
Mark Seemann

1
Saya sulit yang niatnya adalah untuk menggunakan koleksi dengan jenis yang sama,
albertein

@ Mark Seemann, saya sudah menunjuk bahwa, ia juga bisa menggunakan Concat
albertein

Seperti Union, Concat hanya berfungsi jika kedua daftar memiliki tipe yang sama. Tidak tahu apakah ini adalah apa kebutuhan OP atau tidak, meskipun ...
Mark Seemann

Ini membuat daftar baru yang berisi semua elemen. Ini adalah pemborosan memori. Gunakan Linq Concat sebagai gantinya.
Drew Noakes

3

Berikut adalah metode ekstensi IEnumerable <> khusus yang dapat digunakan untuk mengulang dua daftar secara bersamaan.

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

Karena C # 7, Anda dapat menggunakan 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
Apa yang terjadi ketika kedua daftar tidak memiliki panjang yang sama dalam situasi ini?
John

Dengan (x, y) => (x, y)kita bisa menggunakan nama tuple.xdan tuple.yyang elegan. Jadi Bentuk kedua juga bisa(Num, Word) => (Num, Word)
dashesy

2
@JohnAugust Berakhir setelah melintasi urutan yang lebih pendek. Dari dokumen: "Jika urutan tidak memiliki jumlah elemen yang sama, metode menggabungkan urutan hingga mencapai akhir salah satu dari mereka. Misalnya, jika satu urutan memiliki tiga elemen dan yang lainnya memiliki empat elemen, urutan hasil akan hanya memiliki tiga elemen. "
gregsmi

0

Tidak, Anda harus menggunakan for-loop untuk itu.

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

Anda tidak dapat melakukan sesuatu seperti

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

Bagaimana jika mereka memiliki jumlah yang berbeda?
Drew Noakes

Maka sebuah ramalan yang akan menerima daftar enumerable yang sewenang-wenang tidak akan bekerja juga, sehingga menjadikan semuanya tidak berguna.
Maximilian Mayerl

0

Jika Anda ingin satu elemen dengan yang sesuai bisa Anda lakukan

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

Itu akan mengembalikan true jika setiap item sama dengan yang sesuai pada daftar kedua

Jika itu hampir tetapi tidak cukup apa yang Anda inginkan itu akan membantu jika Anda menjelaskan lebih lanjut.


0

Metode ini akan berfungsi untuk implementasi daftar dan dapat diimplementasikan sebagai metode ekstensi.

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

Anda juga bisa menggunakan variabel integer lokal jika daftar memiliki panjang yang sama:

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

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

-1

Anda juga dapat melakukan hal berikut:

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

Catatan: panjang listAharus sama dengan listB.


-3

Saya mengerti / berharap bahwa daftar memiliki panjang yang sama: Tidak, satu-satunya taruhan Anda adalah dengan standar lama polos untuk loop.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.