【问题标题】:transpose 2D array that exists as IEnumerable of IEnumerable转置作为 IEnumerable 的 IEnumerable 存在的二维数组
【发布时间】:2023-03-09 03:56:01
【问题描述】:

如何在 VB .NET 中做到这一点?我尝试在 IEnumerable 上使用 linq Zip 方法,但它不适用于 2 个以上的数组。

这是我正在尝试做的 Python 示例(我得到了 p - 嵌套的 IEnumerable - 并且需要 q - 另一个嵌套的 IEnumerable):

>>> l=['a','b','c']
>>> m=[1,2,3]
>>> n=['x','y','z']
>>> p=[l,m,n]
>>> p
[['a', 'b', 'c'], [1, 2, 3], ['x', 'y', 'z']]
>>> q=zip(*p)
>>> q
[('a', 1, 'x'), ('b', 2, 'y'), ('c', 3, 'z')]

【问题讨论】:

  • 当前在循环中使用 yield 来创建新的 IEnumerable。我仍然想看看是否可以使用嵌套 Zip...

标签: .net vb.net linq


【解决方案1】:

.NET 版本的 Zip 不会像 Python 那样处理任意数量的数组。您需要调用 Zip 两次:

Dim first As String() = { "a", "b", "c" }
Dim second As Integer() = { 1, 2, 3 }
Dim third As String() = { "x", "y", "z" }

Dim query = first.Zip(second, Function(f, s) New With { .First = f, .Second = s }) _
                 .Zip(third, Function(o, t) New With { o.First, o.Second, .Third = t })

For Each item in query
    Console.WriteLine("{0}, {1}, {2}", item.First, item.Second, item.Third)
Next

另一种选择是使用包含索引的重载Enumerable.Select method。这种方法依赖于您正在使用的允许按索引访问的类型。出于性能目的,我不建议使用 ElementAt 方法替换索引访问。此外,这种方法假设所有集合都具有相同的长度,否则会抛出异常。它的工作原理如下:

Dim query2 = first.Select(Function(f, i) New With { .First = f, .Second = second(i), .Third = third(i) })

编辑:一种想法是直接利用 Python 并从 VB.NET 调用它。我不确定这将如何处理,并且会有一个学习曲线来设置它。搜索“从 c# 调用 python”或从“vb.net”获取有关该主题的更多信息。

挑战在于您不能动态创建匿名类型。我想出的最接近的方法是使用 .NET 4.0 的ExpandoObject。要在 VB.NET 中使用 C# 的 dynamic 关键字,您应该能够在不指定类型的情况下初始化对象,例如 Dim o = 5,因为它实际上是下面的 object。您可能需要设置 Option Infer OnOption Strict Off 来实现这一点。

以下代码需要数组作为输入。不幸的是,在尝试访问 Count 时,混合动态类型和其他 IEnumerable<T>s 变得具有挑战性。 Jon Skeet 在这里有一篇相关的文章:Gotchas in dynamic typing。出于这个原因,我坚持使用数组;可以将其更改为List<T> 以使用Count 属性,但绝对不是没有大量工作的混合。

VB.NET

Dim first As String() = { "a", "b", "c" }
Dim second As Integer() = { 1, 2, 3 }
Dim third As String() = { "x", "y", "z" }
Dim fourth As Boolean() = { true, false, true }

Dim list As New List(Of Object) From { first, second, third, fourth }
' ensure the arrays all have the same length '
Dim isValidLength = list.All(Function(c) c.Length = list(0).Length)
If isValidLength
    Dim result As New List(Of ExpandoObject)()
    For i As Integer = 0 To list(i).Length - 1
        Dim temp As New ExpandoObject()
        For j As Integer = 0 To list.Count - 1
            CType(temp, IDictionary(Of string, Object)).Add("Property" + j.ToString(), list(j)(i))
        Next
        result.Add(temp)
    Next

    ' loop over as IDictionary '
    For Each o As ExpandoObject In result
        For Each p in CType(o, IDictionary(Of string, Object))
            Console.WriteLine("{0} : {1}", p.Key, p.Value)
        Next
        Console.WriteLine()
    Next    

    ' or access via property '
    For Each o As Object In result
        Console.WriteLine(o.Property0)
        Console.WriteLine(o.Property1)
        Console.WriteLine(o.Property2)
        Console.WriteLine(o.Property3)
        Console.WriteLine()
    Next
End If

C# 等效项(任何感兴趣的人)

string[] first = { "a", "b", "c" };
int[] second = { 1, 2, 3 };
string[] third = { "x", "y", "z" };
bool[] fourth = { true, false, true };

var list = new List<dynamic> { first, second, third, fourth };
bool isValidLength = list.All(l => l.Length == list[0].Length);
if (isValidLength)
{
    var result = new List<ExpandoObject>();
    for (int i = 0; i < list[i].Length; i++)
    {
        dynamic temp = new ExpandoObject();
        for (int j = 0; j < list.Count; j++)
        {
            ((IDictionary<string, object>)temp).Add("Property" + j, list[j][i]);
        }
        result.Add(temp);
    }

    // loop over as IDictionary
    foreach (ExpandoObject o in result)
    {
        foreach (var p in (IDictionary<string, object>)o)
            Console.WriteLine("{0} : {1}", p.Key, p.Value);

        Console.WriteLine();
    }

    // or access property via dynamic
    foreach (dynamic o in result)
    {
        Console.WriteLine(o.Property0);
        Console.WriteLine(o.Property1);
        Console.WriteLine(o.Property2);
        Console.WriteLine(o.Property3);
        Console.WriteLine();
    }
}

【讨论】:

  • 嵌套的 Zips 也是我尝试过的。但我原来的收藏是开放式的,可以有 3 个以上的 IEnumerable。我试图把这个 Zip 放在一个循环中:first.Zip(second, Function(f, s) {f, s})。问题是返回的 f 是 IEnumerable 而 s 是 IEnumerable 的内容。然后我尝试用 SelectMany 将 f 压平,然后卡在那里……你通过使用 .First 和 .Second 解决了这个问题,但是如果我想运行开放式循环,我就不能这样做。有什么想法吗?
  • 这比迄今为止我想出的任何东西都要好,它确实解决了我的直接问题,同时教了我一些新技巧 - 非常感谢!希望我有更多的代表来支持你......
【解决方案2】:

如果您想要支持特定数量的 IEnumerable,您可以返回某种元组或类似的结构(例如 Ahmad Mageeds answer)。对于一般情况,您将不得不进行某种缓存,最终您将在所有可枚举项中只获得一种类型的项目。像这样的:

Public Function Transpose(Of T)(ByVal source As IEnumerable(Of IEnumerable(Of T))) As IEnumerable(Of IEnumerable(Of T))
    If source is Nothing then Throw New ArgumentNullException("source")
    Return New TransposeEnumerable(Of T)(source)
End Function

Friend NotInheritable Class TransposeEnumerable(Of T)
    Implements IEnumerable(Of IEnumerable(Of T))

    Public Sub New(ByVal base As IEnumerable(Of IEnumerable(Of T)))
        _base = base
    End Sub

    Private ReadOnly _base As IEnumerable(Of IEnumerable(Of T))

    Public Function GetEnumerator() As System.Collections.Generic.IEnumerator(Of IEnumerable(Of T)) Implements System.Collections.Generic.IEnumerable(Of IEnumerable(Of T)).GetEnumerator
        Return New TransposeEnumerator(Me)
    End Function

    Private Function GetObjectEnumerator() As System.Collections.IEnumerator Implements System.Collections.IEnumerable.GetEnumerator
        Return Me.GetEnumerator()
    End Function

    Private NotInheritable Class TransposeEnumerator
        Implements IEnumerator(Of IEnumerable(Of T))

        Public Sub New(ByVal owner As TransposeEnumerable(Of T))
            _owner = owner
            _sources = owner.Select(Function(e) e.GetEnumerator()).ToList()
        End Sub

        Private disposedValue As Boolean
        Public Sub Dispose() Implements IDisposable.Dispose
            If Not Me.disposedValue Then
                If _sources IsNot Nothing Then
                    For Each e In _sources
                        If e IsNot Nothing Then e.Dispose()
                    Next
                End If
            End If
            Me.disposedValue = True
        End Sub

        Private ReadOnly _owner As TransposeEnumerable(Of T)
        Private _sources As New List(Of IEnumerator(Of T))
        Private _current As IEnumerable(Of T)

        Public ReadOnly Property Current() As IEnumerable(Of T) Implements System.Collections.Generic.IEnumerator(Of IEnumerable(Of T)).Current
            Get
                Return _current
            End Get
        End Property

        Private ReadOnly Property CurrentObject() As Object Implements System.Collections.IEnumerator.Current
            Get
                Return Me.Current
            End Get
        End Property

        Public Function MoveNext() As Boolean Implements System.Collections.IEnumerator.MoveNext
            Dim success As Boolean = _sources.All(Function(s) s.MoveNext())
            If success Then
                _current = _sources.Select(Function(s) s.Current).ToList().AsEnumerable()
            End If
            Return success
        End Function

        Public Sub Reset() Implements System.Collections.IEnumerator.Reset
            Throw New InvalidOperationException("This enumerator does not support resetting.")
        End Sub

    End Class
End Class

【讨论】:

  • 呸!这比我坐下来消化的要多。不过这里肯定有一些很酷的概念。我将不得不花一些时间在这...谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多