2011-08-28 31 views
25

Tôi hiện đang thử nghiệm một chút với LINQ. Hãy nói rằng tôi có hai bộ sưu tập có độ dài giống hệt nhau:Hợp nhất xen kẽ với LINQ?

var first = new string[] { "1", "2", "3" }; 
var second = new string[] { "a", "b", "c" }; 

Tôi muốn sáp nhập hai bộ sưu tập thành một, nhưng trong một thời trang xen kẽ. do đó chuỗi kết quả nên là:

"1", "a", "2", "b", "3", "c" 

Những gì tôi đã đi lên với cho đến nay là sự kết hợp của Zip, một loại vô danh và SelectMany:

var result = first.Zip(second, (f, s) => new { F = f, S = s }) 
        .SelectMany(fs => new string[] { fs.F, fs.S }); 

Có ai biết yêu cầu thay/đơn giản hơn cách để đạt được như một interleaved hợp nhất với LINQ?

Trả lời

23

Cảnh báo: điều này sẽ bỏ qua phần tử theo dõi nếu các bảng liệt kê có độ dài khác nhau. Nếu bạn muốn thay thế bằng số trống để loại bỏ bộ sưu tập ngắn hơn, hãy sử dụng Andrew Shepherd's answer bên dưới.


Bạn có thể viết phương thức mở rộng Interleave của riêng mình, như trong this example.

internal static IEnumerable<T> InterleaveEnumerationsOfEqualLength<T>(
    this IEnumerable<T> first, 
    IEnumerable<T> second) 
{ 
    using (IEnumerator<T> 
     enumerator1 = first.GetEnumerator(), 
     enumerator2 = second.GetEnumerator()) 
    { 
     while (enumerator1.MoveNext() && enumerator2.MoveNext()) 
     { 
      yield return enumerator1.Current; 
      yield return enumerator2.Current; 
     } 
    } 
} 
+0

Chắc chắn là giải pháp tốt liên quan đến cả khả năng sử dụng lại và khả năng đọc. – TeaWolf

+2

Tôi nghĩ nếu bộ sưu tập đầu tiên lớn hơn, mã này sẽ vẫn trả lại tất cả, tuy nhiên nếu bộ sưu tập thứ hai lớn hơn, nó sẽ bỏ qua chúng. Có lẽ giá trị tiếp tục thông qua bộ sưu tập thứ hai sau khi vòng lặp trong khi nhất quán :-) –

+0

@Danny, vâng. Sở thích của tôi sẽ dừng lại một khi chạy nhanh nhất, sau đó bạn không cần phải lo lắng về cách điền vào các khoảng trống.(Thông thường tôi sẽ không đưa mã của người khác vào câu trả lời của tôi, nhưng tôi sẽ để lại bản chỉnh sửa của ChaosPandion với mã của Jiri trong nó một mình.) – Douglas

25

Ví dụ bạn cung cấp có thể bởi thực hiện đơn giản bằng cách phân phát với các loại vô danh:

var result = first.Zip(second, (f, s) => new[] { f, s }) 
         .SelectMany(f => f); 
+0

Cảm ơn đơn giản hóa, có vẻ như tôi đã làm mọi thứ theo cách khó khăn hơn một lần nữa. – TeaWolf

+0

Tôi không thấy đơn giản hơn thế này :) – MBen

+1

nếu chuỗi đầu tiên có 2 phần tử và chuỗi thứ hai có 1 phần tử thì điều này sẽ tạo ra 2 phần tử. Hãy cẩn thận với việc triển khai này - đây không phải là sự hợp nhất thực sự của 2 danh sách. – Denis

4

Bạn có thể chỉ là vòng lặp và chọn mảng phụ thuộc vào chỉ số:

var result = 
    Enumerable.Range(0, first.Length * 2) 
    .Select(i => (i % 2 == 0 ? first : second)[i/2]); 
1
var result = first.SelectMany((f, i) => new List<string> { f, second[ i ] }); 
5

Việc triển khai đã cho trong câu trả lời được chấp nhận có sự không thống nhất:
Chuỗi kết quả sẽ luôn luôn chứa tất cả các phần tử của chuỗi đầu tiên (vì vòng lặp bên ngoài while), nhưng nếu chuỗi thứ hai chứa nhiều phần tử hơn, các phần tử đó sẽ không được nối thêm.

Từ một phương pháp Interleave Tôi hy vọng rằng chuỗi kết quả chứa

  1. chỉ 'cặp' (chiều dài của quả trình tự: min(length_1, length_2) * 2)), hoặc là
  2. các yếu tố còn lại của dãy còn luôn được nối (chiều dài của chuỗi kết quả: length_1 + length_2).

Triển khai sau đây theo cách tiếp cận thứ hai.
Lưu ý đơn | trong hoặc so sánh tránh được đánh giá ngắn mạch.

public static IEnumerable<T> Interleave<T> (
    this IEnumerable<T> first, IEnumerable<T> second) 
{ 
    using (var enumerator1 = first.GetEnumerator()) 
    using (var enumerator2 = second.GetEnumerator()) 
    { 
    bool firstHasMore; 
    bool secondHasMore; 

    while ((firstHasMore = enumerator1.MoveNext()) 
     | (secondHasMore = enumerator2.MoveNext())) 
    { 
     if (firstHasMore) 
     yield return enumerator1.Current; 

     if (secondHasMore) 
     yield return enumerator2.Current; 
    } 
    } 
} 
Các vấn đề liên quan