C # bir ForEach deyimi ile iki Liste veya Diziler yineleme


142

Bu sadece genel bilgi için:

İki tane varsa, diyelim, Liste , ve her ikisini de aynı foreach döngüsüyle yinelemek istiyorum, bunu yapabilir miyiz?

Düzenle

Sadece açıklığa kavuşturmak için bunu yapmak istedim:

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

Ama bir foreach =) ile


10
Buradaki önemli kelime "zip" tir.
Mark Byers

3
Paralel olarak iki liste yinelemek istiyor musunuz ? Yoksa önce bir listeyi, sonra diğerini (tek bir ifadeyle) yinelemek mi istiyorsunuz?
Pavel Minaev

Sanırım
Alexander

Yanıtlar:


275

Bu Zip işlemi olarak bilinir ve .NET 4'te desteklenir.

Bununla, şöyle bir şey yazabilirsiniz:

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

Adlandırılmış alanlara sahip anonim türe alternatif olarak, bir Tuple ve statik Tuple kullanarak parantezlere de kaydedebilirsiniz.

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


2
Bu Zip işlemleri hakkında hiçbir şey bilmiyordum, bu konuda küçük bir araştırma yapacağım. Teşekkürler!
Hugo

4
Hugo: Fonksiyonel Programlamada standart bir yapı :)
Mark Seemann

Ayrıca System.Linq;
Jahmic

5
C # 7'den beri , anonim türler veya Tuple.Create yerine bir ValueTuple (bkz. Stackoverflow.com/a/45617748 ) kullanabilirsiniz. Yani foreach ((var number, var word) in numbers.Zip(words, (n, w) => (n, w))) { ... }.
Erlend Graff

14

.NET 4.0'ı beklemek istemiyorsanız, kendi Zipyönteminizi uygulayabilirsiniz . Aşağıdaki .NET 2.0 ile çalışır. İki numaralandırmanın (veya listenin) farklı uzunluklara sahip olduğu durumu nasıl ele almak istediğinize bağlı olarak uygulamayı ayarlayabilirsiniz; bu daha uzun numaralandırmanın sonuna kadar devam eder ve daha kısa numaralandırmadan eksik öğeler için varsayılan değerleri döndürür.

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
Güzel yöntem! :). .NET 4 Zip yöntemi msdn.microsoft.com/en-us/library/dd267698.aspx ile aynı imzayı kullanmak için birkaç ayar yapabilirsiniz ve KVP yerine sonuçSeçici (birinci, ikinci) döndürebilirsiniz.
Martín Coll

Bu yöntemin, örneğin açık dosyalar satırları üzerinde numaralandırılabilir öğelerle birlikte kullanılması durumunda, sorun haline gelebilecek numaralandırıcılarını atmadığını unutmayın.
Lii

11

Union veya Concat kullanabilirsiniz, birincisi kopyaları kaldırır, daha sonra

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

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

Birlik kullanımındaki diğer bir sorun da, eşit olarak değerlendirdikleri takdirde örnekleri atabileceğidir. Her zaman istediğin bu olmayabilir.
Mark Seemann

1
Niyetinin aynı tip koleksiyonları kullanmak olduğunu
zorladım

@Mark Seemann, ben zaten, o da Concat kullanabilirsiniz
albertein

Birlik gibi Concat yalnızca her iki liste de aynı türdeyse çalışır. Yine de OP bu ihtiyacı olup olmadığını söyleyemem ...
Mark Seemann

Bu, tüm öğeleri içeren yeni bir liste oluşturur. Bu bir hafıza kaybı. Bunun yerine Linq Concat kullanın.
Drew Noakes

3

İşte aynı anda iki liste arasında geçiş yapmak için kullanılabilecek özel bir IEnumerable <> genişletme yöntemi.

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'den beri, Tuples'ı kullanabilirsiniz ...

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
Bu durumda iki liste aynı uzunlukta olmadığında ne olur?
John Ağustos

İle (x, y) => (x, y)adlandırılmış tuple.xve tuple.yzarif olan kullanabilirsiniz . Yani ikinci form da olabilir(Num, Word) => (Num, Word)
dashesy

2
@JohnAugust Kısa diziyi geçtikten sonra sona erer. Dokümanlardan: "Sekanslar aynı sayıda öğeye sahip değilse, yöntem sekansları bunlardan birinin sonuna ulaşıncaya kadar birleştirir. Örneğin, bir sekansın üç elemanı ve diğerinin dördü varsa, sonuç sekansı sadece üç unsuru var. "
gregsmi

0

Hayır, bunun için bir for-loop kullanmanız gerekir.

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

Gibi bir şey yapamazsın

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

Farklı sayıları varsa ne olur?
Drew Noakes

Sonra keyfi bir numaralandırılabilir listesini kabul edecek bir foreach de işe yaramaz, böylece her şeyi işe yaramaz hale getirir.
Maximilian Mayerl

0

Eğer karşılık gelen bir eleman istiyorsanız

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

Her öğe ikinci listedeki ilgili öğeye eşitse bu doğru olur

Neredeyse ama tam olarak istediğiniz şey değilse, daha fazla ayrıntı vermeniz yardımcı olacaktır.


0

Bu yöntem bir liste uygulaması için kullanılabilir ve bir uzantı yöntemi olarak uygulanabilir.

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

Listeler aynı uzunlukta ise yerel bir tamsayı değişkeni de kullanabilirsiniz:

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

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

-1

Ayrıca aşağıdakileri de yapabilirsiniz:

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

Not: uzunluğu ile listAaynı olmalıdır listB.


-3

Listelerin aynı uzunlukta olduğunu anlıyorum / umuyorum: Hayır, tek bahsiniz döngü için eski bir standartla gidiyor.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.