2011-08-28 14 views
25

मैं वर्तमान में LINQ के साथ थोड़ा सा प्रयोग कर रहा हूं। मान लीजिए कि मैं समान अवधि की दो संग्रह करते हैं:LINQ के साथ इंटरलीव विलय?

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

मैं एक में उन दो संग्रह मर्ज करना चाहते हैं, लेकिन एक interleaved फैशन में। जिसके परिणामस्वरूप अनुक्रम इस प्रकार होना चाहिए:

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

क्या मैं अब तक लेकर आए हैं Zip का एक संयोजन, एक गुमनाम प्रकार और SelectMany है:

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

किसी को भी एक वैकल्पिक पता है सरल/LINQ के साथ इस तरह के एक interleaved विलय प्राप्त करने के लिए रास्ता?

उत्तर

23

चेतावनी: यदि गणनाओं की अलग-अलग लंबाई होती है तो यह पिछली तत्वों को छोड़ देगा। यदि आप छोटे संग्रह को पैड करने के लिए नल में जगह लेना चाहते हैं, तो नीचे Andrew Shepherd's answer का उपयोग करें।


आप अपनी खुद की Interleave विस्तार विधि लिख सकता है 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

निश्चित रूप से पुन: प्रयोज्यता और पठनीयता दोनों के संबंध में एक अच्छा समाधान है। – TeaWolf

+2

मुझे लगता है कि पहला संग्रह बड़ा है, तो यह कोड अभी भी उन्हें वापस कर देगा, हालांकि यदि दूसरा संग्रह बड़ा है, तो यह उन्हें छोड़ देगा। हो सकता है कि स्थिरता के लिए थोड़ी देर के बाद दूसरे संग्रह के माध्यम से जारी रखें :-) –

+0

@ डैनी, हाँ। एक बार सबसे कम रन आउट होने पर मेरी प्राथमिकता को रोकना होगा, तो आपको अंतराल को भरने के बारे में चिंता करने की आवश्यकता नहीं है।(आम तौर पर मैं अपने उत्तरों में अन्य लोगों का कोड नहीं डालूंगा, लेकिन मैं अकेले में जिरी के कोड के साथ कैओसपैंडियन का संपादन छोड़ दूंगा।) – Douglas

25

उदाहरण आप से कर सकते हैं प्रदान की गुमनाम प्रकार के साथ वितरण से सरल बनाया:

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

सरलीकरण के लिए धन्यवाद, ऐसा लगता है कि मैं चीजों को एक बार फिर से कठिन तरीके से कर रहा था। – TeaWolf

+0

मुझे इस से सरल नहीं दिख रहा है :) – MBen

+1

यदि पहले अनुक्रम में 2 तत्व हैं और दूसरे अनुक्रम में 1 तत्व है तो यह 2 तत्व उत्पन्न करेगा। इस कार्यान्वयन से सावधान रहें - यह 2 सूचियों का सही विलय नहीं है। – Denis

4

आप कर सकते हैं बस पाश और सूचकांक के आधार पर सरणी का चयन करें:

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

स्वीकृत उत्तर में दिए गए कार्यान्वयन में असंगतता है:
परिणामस्वरूप क्रम में हमेशा पहले अनुक्रम के सभी तत्व होंगे (बाहरी while पाश की वजह से), लेकिन यदि दूसरे अनुक्रम में अधिक तत्व होते हैं, तो उन तत्वों की तुलना में जोड़ा नहीं जाएगा।

एक Interleave विधि से

मैं उम्मीद होती है कि जिसके परिणामस्वरूप अनुक्रम

  1. केवल 'जोड़े' (जिसके परिणामस्वरूप अनुक्रम की लंबाई: min(length_1, length_2) * 2)) शामिल हैं, या कि
  2. लम्बा क्रम के शेष तत्व हमेशा जोड़ दिए जाते हैं (परिणामस्वरूप अनुक्रम की लंबाई: length_1 + length_2)।

निम्नलिखित कार्यान्वयन दूसरे दृष्टिकोण का पालन करता है।
शॉर्ट सर्किट मूल्यांकन से बचाता है या तुलना में एकल | नोट करें।

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; 
    } 
    } 
} 
संबंधित मुद्दे