Lặp lại hai Danh sách hoặc Mảng với một câu lệnh ForEach trong C #


142

Điều này chỉ dành cho kiến ​​thức chung:

Nếu tôi có hai, hãy nói, Liệt kê và tôi muốn lặp lại cả hai với cùng một vòng lặp foreach, chúng ta có thể làm điều đó không?

Biên tập

Chỉ cần làm rõ, tôi muốn làm điều này:

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

Nhưng với một foreach =)


10
Từ quan trọng ở đây là "zip".
Mark Byers

3
Bạn có muốn lặp lại hai danh sách song song không? Hoặc bạn muốn lặp lại một danh sách đầu tiên, và sau đó là một danh sách khác (với một tuyên bố duy nhất)?
Pavel Minaev

Tôi nghĩ rằng cách của bạn trông tốt hơn so với zip
Alexander

Câu trả lời:


271

Đây được gọi là thao tác Zip và sẽ được hỗ trợ trong .NET 4.

Với điều đó, bạn sẽ có thể viết một cái gì đó như:

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

Thay thế cho loại ẩn danh với các trường được đặt tên, bạn cũng có thể lưu vào dấu ngoặc nhọn bằng cách sử dụng Tuple và trình trợ giúp Tuple.Create tĩnh của nó:

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


2
Không biết gì về các hoạt động Zip đó, tôi sẽ thực hiện một nghiên cứu nhỏ về chủ đề đó. Cảm ơn!
Hugo

4
@Hugo: Đó là một cấu trúc tiêu chuẩn trong Lập trình chức năng :)
Mark Seemann

Bạn cũng sẽ cần sử dụng System.Linq;
Jahmic

5
Kể từ C # 7, bạn cũng có thể sử dụng ValueTuple (xem stackoverflow.com/a/45617748 ) thay vì các loại ẩn danh hoặc Tuple.Create. Tức foreach ((var number, var word) in numbers.Zip(words, (n, w) => (n, w))) { ... }.
Erlend Graff

14

Nếu bạn không muốn đợi .NET 4.0, bạn có thể thực hiện Zipphương thức của riêng mình . Sau đây hoạt động với .NET 2.0. Bạn có thể điều chỉnh việc thực hiện tùy thuộc vào cách bạn muốn xử lý trường hợp hai liệt kê (hoặc danh sách) có độ dài khác nhau; cái này tiếp tục đến cuối bảng liệt kê dài hơn, trả về các giá trị mặc định cho các mục bị thiếu từ bảng liệt kê ngắn hơn.

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
Phương pháp tốt đẹp! :). Bạn có thể thực hiện một vài điều chỉnh để sử dụng cùng một chữ ký như phương thức .NET 4 Zip msdn.microsoft.com/en-us/l Library / dd267698.aspx và trả về resultSelector (thứ nhất, thứ hai) thay vì KVP.
Martín Coll

Lưu ý rằng phương pháp này không loại bỏ các điều tra viên có thể trở thành một vấn đề, ví dụ: nếu nó được sử dụng với các liệt kê trên các dòng của các tệp đang mở.
Lii

11

Bạn có thể sử dụng Union hoặc Concat, cái trước loại bỏ trùng lặp, cái sau không

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

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

Một vấn đề khác khi sử dụng Liên minh là nó có thể loại bỏ các trường hợp nếu họ đánh giá là bằng nhau. Đó có thể không phải luôn luôn là những gì bạn muốn.
Mark Seemann

1
Tôi cam kết rằng ý định của anh ta là sử dụng các bộ sưu tập cùng loại,
albertein

@Mark Seemann, tôi đã chỉ ra rằng, anh ấy cũng có thể sử dụng Concat
albertein

Giống như Union, Concat chỉ hoạt động nếu cả hai danh sách cùng loại. Không thể biết đây có phải là thứ OP cần hay không, mặc dù ...
Mark Seemann

Điều này tạo ra một danh sách mới có chứa tất cả các yếu tố. Đây là một sự lãng phí bộ nhớ. Sử dụng Linq Concat thay thế.
vẽ Noakes

3

Đây là một phương thức mở rộng tùy chỉnh IEnumerable <> có thể được sử dụng để lặp qua hai danh sách cùng một lúc.

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

Kể từ C # 7, bạn có thể sử dụng 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
Điều gì xảy ra khi hai danh sách không có cùng độ dài trong tình huống này?
John ngày

Với (x, y) => (x, y)chúng tôi có thể sử dụng được đặt tên tuple.xtuple.yđó là thanh lịch. Vì vậy, hình thức thứ hai cũng có thể là(Num, Word) => (Num, Word)
bảnh bao

2
@JohnAugust Nó chấm dứt sau khi duyệt qua chuỗi ngắn hơn. Từ các tài liệu: "Nếu các chuỗi không có cùng số phần tử, phương thức sẽ hợp nhất các chuỗi cho đến khi nó kết thúc một trong số chúng. Ví dụ: nếu một chuỗi có ba phần tử và một phần tử khác có bốn phần tử, trình tự kết quả sẽ có chỉ có ba yếu tố. "
gregsmi

0

Không, bạn sẽ phải sử dụng một vòng lặp for cho điều đó.

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

Bạn không thể làm một cái gì đó như

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

Nếu họ có số lượng khác nhau thì sao?
Drew Noakes

Sau đó, một foreach sẽ chấp nhận một danh sách liệt kê tùy ý sẽ không hoạt động tốt, do đó làm cho toàn bộ điều vô dụng.
Maximilian Mayerl

0

Nếu bạn muốn một yếu tố với yếu tố tương ứng bạn có thể làm

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

Điều đó sẽ trả về true nếu mọi mục bằng với mục tương ứng trong danh sách thứ hai

Nếu đó là gần như nhưng không hoàn toàn những gì bạn muốn, nó sẽ giúp ích nếu bạn xây dựng nhiều hơn.


0

Phương pháp này sẽ hoạt động để thực hiện danh sách và có thể được thực hiện như một phương pháp mở rộng.

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

Bạn cũng có thể chỉ cần sử dụng một biến số nguyên cục bộ nếu các danh sách có cùng độ dài:

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

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

-1

Bạn cũng có thể làm như sau:

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

Lưu ý: độ dài listAphải giống với listB.


-3

Tôi hiểu / hy vọng rằng các danh sách có cùng độ dài: Không, đặt cược duy nhất của bạn là với một tiêu chuẩn cũ đơn giản cho vòng lặp.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.