转置作为IEnumerable的IEnumerable存在的2D数组

时间:2010-12-16 00:38:07

标签: .net vb.net linq

如何在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')]

2 个答案:

答案 0 :(得分:2)

Zip的.NET版本不会像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 On。您可能需要设置Option Strict OffIEnumerable<T>才能实现这一目标。

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

<强> VB.NET

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();
    }
}

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

{{1}}

答案 1 :(得分:0)

如果你想要支持特定数量的IEnumebles,你可以返回某种元组或类似的结构(比如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