2010-12-16 187 views
2

Comment faire cela dans VB .NET? J'ai essayé d'utiliser la méthode linq de Zip sur IEnumerable mais cela ne fonctionne pas pour plus de 2 tableaux.Transpose tableau 2D qui existe en tant que IEnumerable de IEnumerable

Voici un exemple en Python de ce que je suis en train de faire (je suis arrivé p - emboîtés IEnumerable - et besoin q - une autre IEnumerable imbriquée):

>>> 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')] 
+0

actuellement en utilisant le rendement dans une boucle pour créer de nouveaux IEnumerable. J'aimerai quand même voir s'il est possible de faire avec Zip imbriqué ... – user544111

Répondre

2

La version .NET de Zip ne sera pas gérer un nombre arbitraire de tableaux comme le ferait Python. Vous aurez besoin d'appeler deux fois 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 

Une autre option serait d'utiliser le Enumerable.Select method surchargé qui inclut l'indice. Cette approche repose sur les types avec lesquels vous travaillez permettant l'accès par index. Je ne recommanderais pas de substituer l'accès à l'index par la méthode ElementAt à des fins de performances. En outre, cette approche suppose que toutes les collections ont la même longueur, sinon elle lèvera une exception. Il fonctionne comme suit:

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

EDIT: Une pensée est de tirer parti de Python directement et appeler à partir VB.NET. Je ne suis pas vraiment sûr de la façon dont cela sera traité, et il y aura une courbe d'apprentissage pour tout mettre en place. Recherchez "appel python de C#" ou de "vb.net" pour plus d'informations sur ce sujet.

Le défi est que vous ne pouvez pas créer dynamiquement un type anonyme. L'approche la plus proche que je suis venu avec est d'utiliser 0. NET 4.0 ExpandoObject. Pour utiliser le mot-clé dynamic de C# dans VB.NET, vous devriez être en mesure d'initialiser un objet sans spécifier le type, tel que Dim o = 5 car c'est vraiment un object en dessous. Vous devrez probablement définir Option Infer On et Option Strict Off pour y parvenir.

Le code suivant attend des tableaux en entrée. Malheureusement, le mélange de types dynamiques et d'autres IEnumerable<T> devient difficile lorsque vous tentez d'accéder au Count. Jon Skeet a un article pertinent à ce sujet ici: Gotchas in dynamic typing. Pour cette raison, je suis resté avec des tableaux; il peut être changé en List<T> pour utiliser la propriété Count, mais certainement pas un mélange sans beaucoup de travail.

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# équivalent (pour toute personne intéressée)

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

Les Zips imbriqués sont ce que j'ai aussi essayé. Mais ma collection originale est ouverte, et peut avoir plus de 3 IEnumerable. J'ai essayé de mettre ce Zip dans une boucle: first.Zip (second, Function (f, s) {f, s}). Le problème est que le f retourné est IEnumerable alors que s est le contenu de IEnumerable.Puis j'ai essayé d'aplatir f avec SelectMany, et je me suis coincé là ... Vous avez résolu ce problème en utilisant .First et .Second, mais je ne peux pas le faire si je veux exécuter une boucle à boucle ouverte. Des idées? – user544111

+0

@ user544111 mis à jour, voir si cela aide. –

+0

c'est mieux que tout ce que j'ai pu trouver jusqu'à présent, résout mon problème immédiat tout en m'apprenant quelques nouvelles astuces - merci beaucoup! Je voudrais avoir plus de rep pour vous upvote ... – user544111

0

Si vous avez un certain nombre de IEnumerables vous voulez soutenir, vous pouvez retourner une sorte de Tuple ou structure similaire (comme avec Ahmad Mageeds answer). Pour le cas général, vous devrez faire une sorte de mise en cache et vous obtiendrez un seul type d'élément dans tous les énumérables. Quelque chose comme ceci:

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 
+0

Ouf! C'est plus que je ne peux digérer en une seule séance. Certainement quelques concepts sympas ici. Je vais devoir passer du temps là-dessus ... Merci! – user544111