我有一个列表,假设它包含1000个项目。我希望最终得到10个100项的列表,其中包括:
myList.Select(x => x.y).Take(100) (until list is empty)
所以我希望Take(100)运行十次,因为该列表包含1000个项目,最后是包含10个列表的列表,每个列表包含100个项目。
答案 0 :(得分:4)
您需要Skip已经拍摄的记录数量,您可以跟踪此数字并在查询时使用它
alreadyTaken = 0;
while (alreadyTaken < 1000) {
var pagedList = myList.Select(x => x.y).Skip(alreadyTaken).Take(100);
...
alreadyTaken += 100;
}
答案 1 :(得分: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>
。
答案 2 :(得分:0)
如上所述,您可以使用for
循环和Skip
部分元素以及Take
部分元素。通过这种方式,您可以在每个for
循环中创建一个新查询。但是,如果您还想查看每个查询,则会出现问题,因为这样效率非常低。让我们假设您只有50个条目,并且您希望在每个循环中查看包含10个元素的列表。你将有5个循环
这里引发了两个问题。
Skip
元素仍然可以导致计算。在您的第一个查询中,您只需计算所需的10个元素,但在第二个循环中,您计算了20个元素并抛出10个元素,依此类推。如果你把所有5个循环加在一起你已经计算了10 + 20 + 30 + 40 + 50 = 150个元素,即使你只有50个元素。这导致O(n ^ 2)性能。Skip
,例如它们在SQL查询中使用Offset
(MySQL)定义。但这仍然无法解决问题。您仍然遇到的主要问题是您将创建5个不同的查询并执行所有5个查询。这五个查询现在将占用大部分时间。因为对数据库的简单查询甚至比仅跳过一些内存中元素或某些计算要慢得多。由于所有这些问题,如果您还想在每个循环中评估每个查询,则不使用具有多个for
的{{1}}循环是有意义的。相反,您的算法应该只执行一次IEnumerable,执行一次查询,并在第一次迭代时返回前10个元素。下一次迭代返回接下来的10个元素,依此类推,直到元素用完为止。
以下扩展方法就是这样做的。
.Skip(x).Take(y)
这样你可以做到
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)
,只需将一切切成最多包含100个元素的块,就会通过你的枚举。
只是为了表明性能有多大差异:
IEnumerable<IReadOnlyList<T>>
使用Combine()扩展方法的用法在我的计算机上运行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);
(i5 @ 4Ghz),而3 milliseconds
循环已经需要for
如果你有更多的元素或切片更小,它会变得更糟。例如,如果178 milliseconds
设置为combineCount
而不是10
,则运行时更改为100
和4 milliseconds
现在你可能会说你没有那么多元素,或者你的切片从未变得如此之小。但请记住,在这个例子中,我只生成了一个数字序列,其计算时间几乎为零。从1800 milliseconds (1.8 seconds)
到4 milliseconds
的整个开销仅由重新评估和178 milliseconds
值引起。如果你在幕后有一些更复杂的东西,Skipping会产生最多的开销,而且如果IEnumerable可以实现Skip
,就像上面解释的数据库一样,那个例子仍会变得更糟,因为最多开销将是查询本身的执行。
查询量可以快速增加。使用100.000个元素和100个切片/分块,您已经执行了1.000个查询。另一方面,上面提供的Skip
扩展程序将始终执行您的查询一次。并且永远不会遭受上述任何问题。
所有这些并不意味着应该避免使用Combine
和Skip
。他们有自己的位置。但如果您真的打算浏览每个元素,则应避免使用Take
和Skip
来完成切片。
如果您想要的只是将所有内容切片为包含100个元素的页面,并且您只想获取第三页,例如。你应该计算跳过所需的元素数量。
Take
通过这种方式,您可以在单个查询中获取var pageCount = 100;
var pageNumberToGet = 3;
var thirdPage = yourEnumerable.Skip(pageCount * (pageNumberToGet-1)).take(pageCount);
到200
的元素。另外一个带有数据库的IEnumerable可以优化它,你只需要一个查询。因此,如果您只想要使用300
中特定范围的元素而不是IEnumerable
和Skip
,并按照上述方式执行,而不是使用Take
扩展方法我提供了。