Question

J'ai deux tableaux construits lors de l'analyse d'un fichier texte. Le premier contient les noms de colonne, le second contient les valeurs de la ligne actuelle. Je dois parcourir les deux listes à la fois pour créer une carte. À l'heure actuelle, j'ai les informations suivantes:

var currentValues = currentRow.Split(separatorChar);
var valueEnumerator = currentValues.GetEnumerator();

foreach (String column in columnList)
{
    valueEnumerator.MoveNext();
    valueMap.Add(column, (String)valueEnumerator.Current);
}

Cela fonctionne très bien, mais cela ne satisfait pas vraiment mon sens de l'élégance, et cela devient vraiment poilu si le nombre de tableaux est supérieur à deux (comme je dois le faire de temps en temps). Quelqu'un a-t-il un autre idiotisme?

Était-ce utile?

La solution

s'il y a autant de noms de colonnes que d'éléments dans chaque ligne, ne pouvez-vous pas utiliser une boucle for?

var currentValues = currentRow.Split(separatorChar);

for(var i=0;i<columnList.Length;i++){
   // use i to index both (or all) arrays and build your map
}

Autres conseils

Vous avez un pseudo-bogue non évident dans votre code initial - IEnumerator<T> s'étend IDisposable, vous devez donc le supprimer. Cela peut être très important avec les blocs d'itérateurs! Ce n’est pas un problème pour les tableaux, mais avec d’autres IEnumerable<T> implémentations.

Je le ferais comme ça:

public static IEnumerable<TResult> PairUp<TFirst,TSecond,TResult>
    (this IEnumerable<TFirst> source, IEnumerable<TSecond> secondSequence,
     Func<TFirst,TSecond,TResult> projection)
{
    using (IEnumerator<TSecond> secondIter = secondSequence.GetEnumerator())
    {
        foreach (TFirst first in source)
        {
            if (!secondIter.MoveNext())
            {
                throw new ArgumentException
                    ("First sequence longer than second");
            }
            yield return projection(first, secondIter.Current);
        }
        if (secondIter.MoveNext())
        {
            throw new ArgumentException
                ("Second sequence longer than first");
        }
    }        
}

Vous pouvez ensuite le réutiliser chaque fois que vous en avez besoin:

foreach (var pair in columnList.PairUp(currentRow.Split(separatorChar),
             (column, value) => new { column, value })
{
    // Do something
}

Vous pouvez également créer un type de paire générique et supprimer le paramètre de projection dans la méthode PairUp.

EDIT:

Avec le type Pair, le code d'appel ressemblerait à ceci:

foreach (var pair in columnList.PairUp(currentRow.Split(separatorChar))
{
    // column = pair.First, value = pair.Second
}

Cela a l'air aussi simple que possible. Oui, vous devez placer la méthode utilitaire quelque part, sous forme de code réutilisable. À peine un problème à mon avis. Maintenant pour les tableaux multiples ...

Si les tableaux sont de types différents, nous avons un problème. Vous ne pouvez pas exprimer un nombre arbitraire de paramètres de type dans une déclaration de méthode / type générique - vous pouvez écrire des versions de PairUp pour autant de paramètres de type que vous le souhaitez, tout comme il existe des Action et Func délégués pour un maximum de 4 déléguer des paramètres - mais vous ne pouvez pas le rendre arbitraire.

Cependant, si les valeurs sont du même type (et si vous préférez vous en tenir aux tableaux), rien de plus simple. (Les non-tableaux sont également acceptables, mais vous ne pouvez pas vérifier la longueur à l’avance.) Vous pouvez le faire:

public static IEnumerable<T[]> Zip<T>(params T[][] sources)
{
    // (Insert error checking code here for null or empty sources parameter)

    int length = sources[0].Length;
    if (!sources.All(array => array.Length == length))
    {
        throw new ArgumentException("Arrays must all be of the same length");
    }

    for (int i=0; i < length; i++)
    {
        // Could do this bit with LINQ if you wanted
        T[] result = new T[sources.Length];
        for (int j=0; j < result.Length; j++)
        {
             result[j] = sources[j][i];
        }
        yield return result;
    }
}

Le code d'appel serait alors:

foreach (var array in Zip(columns, row, whatevers))
{
    // column = array[0]
    // value = array[1]
    // whatever = array[2]
}

Cela implique bien sûr un certain nombre de copies: vous créez un tableau à chaque fois. Vous pouvez changer cela en introduisant un autre type comme celui-ci:

public struct Snapshot<T>
{
    readonly T[][] sources;
    readonly int index;

    public Snapshot(T[][] sources, int index)
    {
        this.sources = sources;
        this.index = index;
    }

    public T this[int element]
    {
        return sources[element][index];
    }
}

Cela serait probablement considéré comme excessif par la plupart cependant;)

Pour être honnête, je pourrais continuer à proposer toutes sortes d'idées, mais l'essentiel est:

  • Avec un peu de travail réutilisable, vous pouvez rendre le code d'appel plus agréable
  • Pour des combinaisons de types arbitraires, vous devez définir chaque nombre de paramètres (2, 3, 4 ...) séparément, en raison du fonctionnement des génériques
  • Si vous préférez utiliser le même type pour chaque pièce, vous pouvez faire mieux

Dans un langage fonctionnel, vous trouverez généralement un " zip " fonction qui, espérons-le, fera partie d’un C # 4.0. Bart de Smet fournit une implémentation amusante du fichier zip basée sur les fonctions LINQ existantes:

public static IEnumerable<TResult> Zip<TFirst, TSecond, TResult>(
  this IEnumerable<TFirst> first, 
  IEnumerable<TSecond> second, 
  Func<TFirst, TSecond, TResult> func)
{
  return first.Select((x, i) => new { X = x, I = i })
    .Join(second.Select((x, i) => new { X = x, I = i }), 
    o => o.I, 
    i => i.I, 
    (o, i) => func(o.X, i.X));
}

Ensuite, vous pouvez faire:

  int[] s1 = new [] { 1, 2, 3 };
  int[] s2 = new[] { 4, 5, 6 };
  var result = s1.Zip(s2, (i1, i2) => new {Value1 = i1, Value2 = i2});

Si vous utilisez réellement des tableaux, la meilleure solution consiste probablement simplement à utiliser la boucle classique for avec des index. Pas aussi bien, d'accord, mais pour autant que je sache, .NET n'offre pas une meilleure façon de le faire.

Vous pouvez également encapsuler votre code dans une méthode appelée zip & # 8211; c'est une fonction courante de liste d'ordre supérieur. Cependant, C # manquant d'un type de tuple approprié, c'est assez cruel. Vous finirez par retourner un IEnumerable<KeyValuePair<T1, T2>> qui n’est pas très agréable.

Au fait, utilisez-vous réellement IEnumerable au lieu de IEnumerable<T> ou pourquoi lancez-vous la Current valeur?

Utiliser IEnumerator pour les deux serait bien

var currentValues = currentRow.Split(separatorChar);
using (IEnumerator<string> valueEnum = currentValues.GetEnumerator(), columnEnum = columnList.GetEnumerator()) {
    while (valueEnum.MoveNext() && columnEnum.MoveNext())
        valueMap.Add(columnEnum.Current, valueEnum.Current);
}

Ou créez une méthode d'extension

public static IEnumerable<TResult> Zip<T1, T2, TResult>(this IEnumerable<T1> source, IEnumerable<T2> other, Func<T1, T2, TResult> selector) {
    using (IEnumerator<T1> sourceEnum = source.GetEnumerator()) {
        using (IEnumerator<T2> otherEnum = other.GetEnumerator()) {
            while (sourceEnum.MoveNext() && columnEnum.MoveNext())
                yield return selector(sourceEnum.Current, otherEnum.Current);
        }
    }
}

Utilisation

var currentValues = currentRow.Split(separatorChar);
foreach (var valueColumnPair in currentValues.Zip(columnList, (a, b) => new { Value = a, Column = b }) {
    valueMap.Add(valueColumnPair.Column, valueColumnPair.Value);
}

Au lieu de créer deux tableaux distincts, vous pouvez créer un tableau à deux dimensions ou un dictionnaire (ce qui serait mieux). Mais vraiment, si cela fonctionne, je ne chercherai pas à le changer.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top