2014-11-05 58 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>

相关问题