2014-11-05 88 views
0

我有一個列表,假設它包含1000個項目。我想用10個乘以100項的列表,以最終的東西,如:將頁面合併到頁面列表

myList.Select(x => x.y).Take(100) (until list is empty) 

所以我想取(100)運行十次,因爲列表中包含1000個項目,並用含10名結束了列出每個包含100個項目的列表。

+0

看看這個問題http://stackoverflow.com/questions/419019/split-list-into-sublists-with-linq – 2014-11-05 13:03:36

+0

@PawełReszka解決方案簡單地分裂整個列表,將拉滿1000項 - OP正在尋找一種*分頁*方法,每次查詢100個項目。 – James 2014-11-05 13:05:13

+0

@詹姆斯這是我要澄清的問題。他說他有一個1000件物品的清單。它在問題的任何地方都沒有說'EF'或'SQL' - 所以我認爲myList已經是一個List hometoast 2014-11-05 13:06:13

回答

0

如已發佈,您可以使用for循環和Skip某些元素和Take某些元素。通過這種方式,您可以在每個for循環中創建一個新查詢。但是,如果您還想要查看每個查詢,就會引發問題,因爲這樣做效率很低。假設你只有50個條目,並且你想在每個循環中用十個元素遍歷你的列表。您將有5圈做

  1. .Skip(0)。取(10)
  2. .Skip(10)。取(10)
  3. .Skip(20)。取(10)
  4. .Skip(30)。取(10)
  5. .Skip(40)。取(10)

這裏有兩個問題提出了。

  1. Skip ing元素仍然可以導致計算。在你的第一個查詢中,你只需計算所需的10個元素,但在第二個循環中,你計算了20個元素並扔掉了10個元素,依此類推。如果將所有5個循環相加在一起,則已經計算出10 + 20 + 30 + 40 + 50 = 150個元素,即使只有50個元素也是如此。這導致了O(n^2)的表現。
  2. 並非每個IEnumerable都能完成上述操作。一些像數據庫這樣的IEnumerable可以優化Skip,例如他們在SQL查詢中使用Offset(MySQL)定義。但是這仍然不能解決問題。您仍然面臨的主要問題是您將創建5個不同的查詢並執行其中的5個查詢。這五個查詢現在將佔用大部分時間。因爲對數據庫的簡單查詢甚至比僅跳過一些內存中的元素或某些計算慢得多。

因爲所有這些問題是很有意義的不使用for循環與多個.Skip(x).Take(y),如果你也想在每一個循環的每個查詢評估。相反,你的算法只應該通過IEnumerable一次,執行一次查詢,並且在第一次迭代時返回前10個元素。下一次迭代返回接下來的10個元素,依此類推,直到元素用完。

下面的擴展方法就是這樣做的。

public static IEnumerable<IReadOnlyList<T>> Combine<T>(this IEnumerable<T> source, int amount) { 
    var combined = new List<T>(); 
    var counter = 0; 
    foreach (var entry in source) { 
     combined.Add(entry); 
     if (++counter >= amount) { 
      yield return combined; 
      combined = new List<T>(); 
      counter = 0; 
     } 
    } 

    if (combined.Count > 0) 
     yield return combined; 
} 

有了這個,你可以做

someEnumerable.Combine(100) 

,你會得到一個新的IEnumerable<IReadOnlyList<T>>,通過你的枚舉去只有一次切片一切成塊最多有100個元素。

只是爲了展示效果會如何太大的區別是:

var numberCount = 100000; 
var combineCount = 100; 

var nums = Enumerable.Range(1, numberCount); 
var count = 0; 

// Bechmark with Combine() Extension 
var swCombine = Stopwatch.StartNew(); 
var sumCombine = 0L; 
var pages  = nums.Combine(combineCount); 
foreach (var page in pages) { 
    sumCombine += page.Sum(); 
    count++; 
} 
swCombine.Stop(); 
Console.WriteLine("Count: {0} Sum: {1} Time Combine: {2}", count, sumCombine, swCombine.Elapsed); 

// Doing it with .Skip(x).Take(y) 
var swTakes = Stopwatch.StartNew(); 
count = 0; 
var sumTaken = 0L; 
var alreadyTaken = 0; 
while (alreadyTaken < numberCount) { 
    sumTaken += nums.Skip(alreadyTaken).Take(combineCount).Sum(); 
    alreadyTaken += combineCount; 
    count++; 
} 
swTakes.Stop(); 
Console.WriteLine("Count: {0} Sum: {1} Time Takes: {2}", count, sumTaken, swTakes.Elapsed); 

該使用多合一(),而for循環已經需要178 milliseconds

我的計算機上 3 milliseconds擴展方法運行(I5 @ 4GHz的)

如果你有更多的元素或切片更小,它會變得更糟糕。例如,如果combineCount設置爲10而不是100運行時更改4 milliseconds1800 milliseconds (1.8 seconds)

現在你可能說你沒有那麼多的元素或者切片永遠不會那麼小。但請記住,在這個例子中,我只是生成了一個幾乎爲零的計算時間的數字序列。從4 milliseconds178 milliseconds的整個開銷僅由重新評估和值引起。如果你在後臺執行一些更復雜的事情,跳過會造成最多的開銷,並且如果IEnumerable可以實現Skip,就像上面解釋的數據庫一樣,那麼這個例子仍然會變得更糟,因爲最大的開銷將是查詢本身的執行。

而查詢的數量可以真的很快。使用100.000個元素和100的分片/分塊,您已經可以執行1.000個查詢。另一方面,上面提供的Combine擴展將始終執行一次查詢。並且永遠不會遭受上述任何問題。


所有這一切並不意味着SkipTake應該避免。他們有自己的位置。但如果你真的打算通過每一個元素,你應該避免使用SkipTake來完成你的切片。

如果您想要的僅僅是將所有內容切分爲包含100個元素的頁面,並且您只想獲取第三頁,例如。你應該計算你需要跳過多少元素。

var pageCount = 100; 
var pageNumberToGet = 3; 
var thirdPage = yourEnumerable.Skip(pageCount * (pageNumberToGet-1)).take(pageCount); 

這樣你會得到200的元素300在一個單一的查詢。另外一個帶數據庫的IEnumerable可以優化這個,你只需要一個單一的查詢。因此,如果您只需要IEnumerable中的某個特定範圍的元素,那麼您應該使用SkipTake,並按照上述方式進行操作,而不是使用我提供的Combine擴展方法。

+0

「*每當您使用.Skip(x).Take(y)時,整個IEnumerable表達式都會被重新評估*」 - 這會帶來一些誤導,如果查詢來自數據庫上下文,然後'Skip' /'Take'肯定不會返回整個結果集,它將被優化爲只返回相關的行。另外,如果'myList'已經是'TList ',那麼就沒有任何東西可以列舉。你的觀點是有效的,但是,答案表明這是* any *'IEnumerable '的情況,事實並非如此。 – James 2014-11-06 10:51:32

+0

@James是的,這是真的,實際上也是一個普通的'IEnumerable''Skip'和'Take'不會立即執行查詢,只有當你開始從查詢中獲取值後。在'for'例子中只有'Sum()'開始執行。但問題仍然是'for'循環導致大量執行。即使數據庫優化「Skip」和「Take」,您仍然會爲每個循環再次執行一次查詢,對於數據庫而言,這更是一個問題,因爲它會丟掉一些值。我研究它以重寫答案以使問題更清楚。 – 2014-11-06 11:01:20

+0

是的,我明白'Skip' /'Take'沒有實現查詢,我只是想解釋一點,即使他們這樣做,他們也不會拉下整個記錄集(因爲你的答案* *建議)。我不確定你的意思是「*數據庫的問題是拋棄一些值*」 - 數據庫不應該拋出*任何東西*,但是,如果意圖是提供*分頁*查詢,那麼你不會自然地使用'for'查詢,如果你需要整個列表,那麼'Skip' /'Take'確實沒有意義,執行1個查詢會更有效率。 – James 2014-11-06 11:13:53

4

你需要Skip你已經採取的記錄數,您可以記下這個號碼的跟蹤,並使用它時,你查詢

alreadyTaken = 0; 
while (alreadyTaken < 1000) { 
    var pagedList = myList.Select(x => x.y).Skip(alreadyTaken).Take(100); 
    ... 
    alreadyTaken += 100; 
} 
1

這可以用一個簡單的尋呼分機的方法來實現。

public static List<T> GetPage<T>(this List<T> dataSource, int pageIndex, int pageSize = 100) 
{ 
    return dataSource.Skip(pageIndex * pageSize) 
     .Take(pageSize) 
     .ToList(); 
} 

當然,你可以擴展它接受和/或返回任何種類的IEnumerable<T>