在VB.Net中使用LINQ将集合拆分为n个部分

时间:2015-03-04 06:00:17

标签: .net vb.net linq ienumerable

问题

在VB.Net中,如果我有这样的集合:

Dim collection As IEnumerable(Of Integer) = Enumerable.Range(0, 99)

我如何将它分成不确定数量的元素的组/ Ienumerables?


条件

使用 LINQ 查询(不是 MORELinq 或任何其他第三方库)

不写Function,只是使用(或附加到集合) LINQ 查询,避免插入自定义和通用过程来分割部分。

未生成Anonymous类型(因为我会尊重VB.Net Option语句)。


研究

我已经阅读过这些问题,但我无法将C# LINQ 查询正确地翻译成VB.Net(甚至在线翻译失败并需要进行大量修改):

Split a collection into `n` parts with LINQ?

How can I split an IEnumerable<String> into groups of IEnumerable<string>

Split List into Sublists with LINQ

Split a entity collection into n parts

这是我尝试从那些S.O.中给出的解决方案中适应的两种不同方法。上面的问题,但我的翻译不起作用,第一个无法编译,因为分组依据的条件和第二个不生成分割集合,它每个生成一个集合元素:

1

    Dim parts As Integer = 4
    Dim i As Integer = 0

    Dim splits As IEnumerable(Of IEnumerable(Of Integer)) =
        From item As Integer In collection
        Group By (Math.Max(Interlocked.Increment(i), i - 1) Mod parts)
        Into Group
        Select Group.AsEnumerable

2

    Dim parts As Integer = 4

    Dim result As IEnumerable(Of IEnumerable(Of Integer)) =
        collection.Select(Function(s, i) New With
            {
                Key .Value = s,
                Key .Index = i
            }
        ).GroupBy(Function(item)
                      Return (item.Index >= (item.Index / parts)) And (item.Index >= item.Value)
                  End Function,
               Function(item) item.Value).Cast(Of IEnumerable(Of Integer))()

预期结果

所以,如果我有这样的源集合:

Dim collection As IEnumerable(Of Integer) = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

假设我有那个想象的LINQ查询,它可以通过变量splits上指定的值拆分成名为parts的变量:

Dim parts as Integer = ...
Dim splits As IEnumerable(Of IEnumerable(Of Integer)) =
   From value As Integer In collection (Linq Query that splits by given 'parts' value)

如果我选择parts 4 ,那么结果将是Enumerable(Of IEnumerable(Of Integer)),其中包含 3 个集合,其中:< / p>

splits(0) = {1, 2, 3, 4}
splits(1) = {5, 6, 7, 8}
splits(2) = {9, 10}

如果我选择parts 5 ,那么结果将是Enumerable(Of IEnumerable(Of Integer)),其中包含 2 个集合,其中:< / p>

splits(0) = {1, 2, 3, 4, 5}
splits(1) = {6, 7, 8, 9, 10}

如果我选择parts值为 1 ,则结果将为I Enumerable(Of IEnumerable(Of Integer)),其中包含相同的源集合,因为我选择拆分为 1 部分:

splits(0) = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

如果我选择parts 10 ,那么结果将是Enumerable(Of IEnumerable(Of Integer)),其中包含 10 个集合,每个集合一个源示例集合的每个值:

splits(0) = {1}
splits(1) = {2}
splits(2) = {3}
and so on...

3 个答案:

答案 0 :(得分:4)

您可以使用RangeSkipTake来实现目标。

Dim collection As IEnumerable(Of Integer) = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
Dim parts As Integer = 4
Dim count As Integer = CInt(Math.Ceiling(collection.Count() / parts)) '= 3
Dim result As IEnumerable(Of IEnumerable(Of Integer)) = Nothing

'Method 1:
result = From i In Enumerable.Range(0, count) Select collection.Skip(i * parts).Take(parts)

'Method 2:
result = Enumerable.Range(0, count).Select(Function(i) collection.Skip(i * parts).Take(parts))

For Each item In result
    Debug.WriteLine(String.Join(", ", item))
Next
  

1,2,3,4   5,6,7,8   9,10

强制性扩展方法:

Public Module Extensions

    <System.Runtime.CompilerServices.Extension()>
    Public Function Split(Of TSource)(collection As IEnumerable(Of TSource), ByVal parts As Integer) As IEnumerable(Of IEnumerable(Of TSource))
        If (collection Is Nothing) Then Throw New ArgumentNullException("collection")
        If (parts < 1) Then Throw New ArgumentOutOfRangeException("parts")
        Dim count As Integer = collection.Count()
        If (count = 0) Then Return {}
        If (parts >= count) Then Return {collection}
        Return Enumerable.Range(0, CInt(Math.Ceiling(count / parts))).Select(Function(i) collection.Skip(i * parts).Take(parts))
    End Function

End Module

result = collection.Split(4)

答案 1 :(得分:2)

您正在寻找的查询可能如下所示:

Dim partitionSize As Integer = 4
Dim collection As IEnumerable(Of Integer) = Enumerable.Range(0, 100)
Dim parts As Integer = collection.Count \ partitionSize
Dim splits = (From i In collection
              Group i By key = i Mod parts Into g = Group
              Select key, g)

这取决于您展示的样本。但是,如果实际数据的排列更随机,这种方式将使用根据索引值分组的索引值:

Dim partitionSize As Integer = 4
Dim collection As IEnumerable(Of Integer) = Enumerable.Range(100, 100)
Dim parts As Integer = collection.Count \ partitionSize
Dim splits = (From i In Enumerable.Range(0, 100)
              Group collection(i) By key = i Mod parts Into g = Group
              Select key, g)

您的帖子说您不想使用匿名类型。但是,Group By使用匿名类型。您可以通过将集合强制转换为Dictionary来使匿名类型成为临时类型:

Dim splits = (From i In Enumerable.Range(0, 100)
              Group collection(i) By key = i Mod parts Into g = Group
              Select key, g).ToDictionary(Function(x) x.key, Function(x) x.g)

答案 2 :(得分:2)

发布的答案很晚但只是想在我看来显示我的版本简单:)以下是查询: -

Dim result4 As IEnumerable(Of IEnumerable(Of Integer)) = collection
                            .Select(Function(v, i) New With {.Value = v, .Index = i}) _
                            .GroupBy(Function(x) x.Index \ 4) _
                            .Select(Function(x) x.Select(Function(z) z.Value))

这是表示: - 1.我使用了Select的{​​{3}}重载来获取索引及其值 2.一旦我们得到索引,步骤2就是简单的数学,因为我们将索引(0,1,2 ---)除以我们想要的部分数量(在这种情况下为4)并简单地将它们组合在一起。
3.由于在步骤1中我们分别预测了valueindex,因此在最后一步中只需选择值。

以下是this,其中包含2个示例。