2017-06-05 92 views
1

鑑於以下三個列表:優化LINQ多個列表組合成新的泛型列表

var FirstNames = new List<string>(){ "Bob", "Sondra", "Avery", "Von", "Randle", "Gwen", "Paisley" }; 
    var LastNames = new List<string>(){ "Anderson", "Carlson", "Vickers", "Black", "Schultz", "Marigold", "Johnson" }; 
    var Birthdates = new List<DateTime>() 
        { 
         Convert.ToDateTime("11/12/1980"), 
         Convert.ToDateTime("09/16/1978"), 
         Convert.ToDateTime("05/18/1985"), 
         Convert.ToDateTime("10/29/1980"), 
         Convert.ToDateTime("01/19/1989"), 
         Convert.ToDateTime("01/14/1972"), 
         Convert.ToDateTime("02/20/1981") 
        }; 

我想將它們合併成一個新的通用類型,其中的關係列表份額是他們在集合中的位置。即FirstNames [0],LastNames [0],生日[0]是相關的。

所以我想出了這個LINQ,匹配指數,這似乎是現在工作得很好:

var students = from fn in FirstNames 
        from ln in LastNames 
        from bd in Birthdates 
        where FirstNames.IndexOf(fn) == LastNames.IndexOf(ln) 
        where FirstNames.IndexOf(fn) == Birthdates.IndexOf(bd) 
        select new { First = fn, Last = ln, Birthdate = bd.Date }; 

但是,我一直強調測試此代碼(每個List<string>List<DateTime>裝載了幾百萬記錄),我遇到了SystemOutOfMemory異常。

是否有任何其他寫出此查詢的方式來更有效地使用Linq來實現相同的結果?

回答

3

也就是說是什麼郵政編碼爲。

var result = FirstNames 
    .Zip(LastNames, (f,l) => new {f,l}) 
    .Zip(BirthDates, (fl, b) => new {First=fl.f, Last = fl.l, BirthDate = b}); 

關於縮放:

int count = 50000000; 
var FirstNames = Enumerable.Range(0, count).Select(x=>x.ToString()); 
var LastNames = Enumerable.Range(0, count).Select(x=>x.ToString()); 
var BirthDates = Enumerable.Range(0, count).Select(x=> DateTime.Now.AddSeconds(x)); 

var sw = new Stopwatch(); 
sw.Start(); 

var result = FirstNames 
    .Zip(LastNames, (f,l) => new {f,l}) 
    .Zip(BirthDates, (fl, b) => new {First=fl.f, Last = fl.l, BirthDate = b}); 

foreach(var r in result) 
{ 
    var x = r; 
} 
sw.Stop(); 
Console.WriteLine(sw.ElapsedMilliseconds); // Returns 69191 on my machine. 

雖然這些炸燬帶出的存儲器:

int count = 50000000; 
var FirstNames = Enumerable.Range(0, count).Select(x=>x.ToString()); 
var LastNames = Enumerable.Range(0, count).Select(x=>x.ToString()); 
var BirthDates = Enumerable.Range(0, count).Select(x=> DateTime.Now.AddSeconds(x)); 

var sw = new Stopwatch(); 
sw.Start(); 

var FirstNamesList = FirstNames.ToList(); // Blows up in 32-bit .NET with out of Memory 
var LastNamesList = LastNames.ToList(); 
var BirthDatesList = BirthDates.ToList(); 

var result = Enumerable.Range(0, FirstNamesList.Count()) 
    .Select(i => new 
       { 
        First = FirstNamesList[i], 
        Last = LastNamesList[i], 
        Birthdate = BirthDatesList[i] 
       }); 

result = BirthDatesList.Select((bd, i) => new 
{ 
    First = FirstNamesList[i], 
    Last = LastNamesList[i], 
    BirthDate = bd 
}); 

foreach(var r in result) 
{ 
    var x = r; 
} 
sw.Stop(); 
Console.WriteLine(sw.ElapsedMilliseconds); 

在較低的值時,將所述可枚舉的列表的費用是昂貴得多而不是附加的對象創建。 Zip比索引版本快大約30%。隨着您添加更多列,Zips的優勢可能會縮小。

性能特徵也有很大不同。 Zip例程幾乎立即開始輸出答案,其他人將只在整個Enumerables已被閱讀並轉換爲列表後纔開始輸出答案,因此如果您使用.Skip(x).Take(y)獲取結果並對其進行分頁,或者檢查是否存在某個問題.Any(...)它會更快,因爲它不需要轉換整個枚舉。

最後,如果它成爲性能的關鍵,你需要實現很多的效果,你可以考慮延長壓縮處理可枚舉像一個任意數(從喬恩斯基特無恥地竊取 - https://codeblog.jonskeet.uk/2011/01/14/reimplementing-linq-to-objects-part-35-zip/):

private static IEnumerable<TResult> Zip<TFirst, TSecond, TThird, TResult>( 
    IEnumerable<TFirst> first, 
    IEnumerable<TSecond> second, 
    IEnumerable<TThird> third, 
    Func<TFirst, TSecond, TThird, TResult> resultSelector) 
{ 
    using (IEnumerator<TFirst> iterator1 = first.GetEnumerator()) 
    using (IEnumerator<TSecond> iterator2 = second.GetEnumerator()) 
    using (IEnumerator<TThird> iterator3 = third.GetEnumerator()) 
    { 
     while (iterator1.MoveNext() && iterator2.MoveNext() && iterator3.MoveNext()) 
     { 
      yield return resultSelector(iterator1.Current, iterator2.Current, iterator3.Current); 
     } 
    } 
} 

然後,你可以這樣做:

var result = FirstNames 
    .Zip(LastNames, BirthDates, (f,l,b) => new {First=f,Last=l,BirthDate=b}); 

現在你甚至不用中間對象被創建的問題,所以你得到了世界上最好的。

或者利用執行這裏處理任何數量一般:Zip multiple/abitrary number of enumerables in C#

+0

我最初在出現這個問題時使用'Zip'進行了研究。我認爲我同意你的看法,但你認爲這個解決方案可以擴展嗎?所有的答案表現非常相似,使用一個簡單的秒錶類。這將創建一個新名單和姓氏,然後是第一個/最後一個姓氏和生日的另一個。如果我們添加另一個說法,年齡列表,它是否會隨着這個方面進行擴展? –

+0

@FusRoDah它將在很大程度上取決於消息來源以及數據的消耗方式。創建臨時對象的確需要一些性能(我相信),但是,如果源和/或消耗將從可枚舉的懶惰評估中受益,它可能會表現更好或更差,特別是因爲在最好的情況下,由於數據流式傳輸會需要更少的內存從一次一個來源,處理然後丟棄,而不是假設來源是可轉位的(或轉換成這樣的)。 –

+0

感謝您詳細說明優缺點,並提供比較代碼。檢查「Any()」正是我們通用列表的最終產品所要做的。因此,我認爲你已經完全回答了我的問題。 –

2

葉氏,使用範圍生成:

var result = Enumerable.Range(0, FirstNames.Count) 
    .Select(i => new 
       { 
        First = FirstNames[i], 
        Last = LastNames[i], 
        Birthdate = Birthdates[i] 
       }); 
+0

我想,如果我們需要添加另一個列表我們可以只需在這個答案中加入一行更短的代碼:'Age = Ages [i]'。支持對初級開發人員友好 –

+0

不客氣=) – eocron

3

另一種選擇是使用選擇過載與所提供的索引:

var result = Birthdates.Select((bd, i) => new 
{ 
    First = FirstNames[i], 
    Last = LastNames[i], 
    Birthdate = bd 
}); 
+0

我真的很想知道這對未來的應用程序是有益的,甚至不僅僅涉及到這個問題。我還有很多要了解LINQ的,所以謝謝 –

+0

你永遠是受歡迎的! –