【问题标题】:Split a collection into n parts with LINQ, in VB.Net在 VB.Net 中使用 LINQ 将集合拆分为 n 个部分
【发布时间】:2015-03-04 06:00:17
【问题描述】:

问题

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

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

如何将其拆分为数量不确定的元素的组/可枚举?


条件

使用 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.上面的问题,但我的翻译不起作用,第一个无法编译,因为 Group By 的条件和第二个不生成拆分集合,它为每个元素生成一个集合:

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 查询,它可以通过变量 parts 上指定的值拆分为一个名为 splits 的变量:

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,那么结果将是 IEnumerable(Of IEnumerable(Of Integer)),其中包含 3 个集合,其中:

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

如果我选择 parts 的值为 5,那么结果将是 IEnumerable(Of IEnumerable(Of Integer)),其中包含 2 个集合,其中:

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

如果我选择 parts 的值为 1,那么结果将是 IEnumerable(Of IEnumerable(Of Integer)),其中包含相同的源集合,因为我选择仅拆分为 1强>部分:

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

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

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

【问题讨论】:

  • 你能展示一些你期望的输出样本数据吗?
  • @Rahul Singh 是的,我已经更新了这个问题。感谢您的评论

标签: .net vb.net linq ienumerable


【解决方案1】:

您可以使用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)

【讨论】:

    【解决方案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 使用匿名类型。您可以通过将集合转换为字典来使匿名类型成为临时类型:

    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)
    

    【讨论】:

    • 感谢您的回答,非常有解释性,我会记住您的第三种方法
    【解决方案3】:

    晚点发布答案,但只是想向您展示我的版本,因为我认为它很简单:) 这是查询:-

    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. 我使用Selectthis 重载来获取索引及其值。
    2. 一旦我们有了索引,第 2 步就是简单的数学运算,因为我们将索引 (0,1,2---) 除以我们想要的部分数量(在本例中为 4),然后简单地将它们组合在一起。
    3. 由于在第 1 步中我们分别投影了 valueindex,因此在最后一步中只需选择值。

    这是Complete Working Fiddle,里面有2个例子。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-10-01
      • 1970-01-01
      • 1970-01-01
      • 2011-06-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-12
      相关资源
      最近更新 更多