Pergunta

Eu tenho duas matrizes construídas ao analisar um arquivo de texto. O primeiro contém os nomes das colunas, o segundo contém os valores da linha corrente. Eu preciso iterar sobre ambas as listas de uma só vez para construir um mapa. Agora eu tenho o seguinte:

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

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

Isso funciona muito bem, mas não chega a satisfazer a minha sensação de elegância, e ele fica muito peludo, se o número de matrizes é maior do que dois (como eu tenho que fazer de vez em quando). Alguém tem outra, terser idioma?

Foi útil?

Solução

Se há o mesmo número de nomes de coluna como existem elementos em cada linha, você não pode usar um loop 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
}

Outras dicas

Você tem um pseudo-bug não é óbvio em seu código inicial - IEnumerator<T> estende IDisposable assim que você deve descartá-lo. Isto pode ser muito importante, com iteradoras blocos! Não é um problema para arrays, mas seria com outras implementações IEnumerable<T>.

Eu faria isso como este:

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

Depois, você pode reutilizar este sempre que você tem a necessidade de:

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

Alternativamente, você pode criar um tipo de par genérico, e se livrar do parâmetro de projeção no método PairUp.

EDIT:

Com o tipo Par, o código de chamada ficaria assim:

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

Isso parece tão simples como você pode começar. Sim, você precisa colocar o método de utilitário em algum lugar, como código reutilizável. Dificilmente um problema na minha opinião. Agora, para várias matrizes ...

Se as matrizes são de tipos diferentes, temos um problema. Você não pode expressar um número arbitrário de parâmetros de tipo em uma declaração de método / tipo genérico - você poderia escrever versões do PairUp para tantos parâmetros de tipo como você queria, assim como existem Action e Func delegados para até 4 parâmetros de delegado - mas você não pode fazê-lo arbitrário.

Se os valores serão todos do mesmo tipo, no entanto - e se você está feliz em ficar com matrizes - é fácil. (Não-matrizes é bom também, mas você não pode fazer o comprimento verificação antes do tempo.) Você poderia fazer isso:

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

Em seguida, o código de chamada seria:

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

Trata-se de uma certa quantidade de copiar, é claro - você está criando uma matriz de cada vez. Você poderia mudar isso através da introdução de outro tipo como este:

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];
    }
}

Esta provavelmente seria considerado um exagero por mais embora;)

Eu poderia manter chegando com todos os tipos de ideias, para ser honesto ... mas os princípios são:

  • Com um pouco de trabalho reutilizável, você pode fazer o código de chamada mais agradável
  • Para combinações arbitrárias de tipos que você vai ter que fazer cada número de parâmetros (2, 3, 4 ...) separadamente devido às obras maneira genéricos
  • Se você está feliz em usar o mesmo tipo para cada parte, você pode fazer melhor

Em uma linguagem funcional que normalmente seria encontrar uma função "zip", que, esperamos, será parte de um C # 4.0. Bart de Smet fornece uma implementação engraçado de zip com base em funções LINQ existentes:

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

Em seguida, você pode fazer:

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

Se você está realmente usando matrizes, a melhor maneira é provavelmente apenas para usar o loop for convencional com índices. Não tão bom, concedido, mas, tanto quanto eu sei .NET não oferece uma maneira melhor de fazer isso.

Você também pode encapsular seu código em um método chamado zip - esta é uma função comum lista de ordem superior. No entanto, C # falta um tipo Tuple adequado, isso é muito intrincada. Você acabaria retornando um IEnumerable<KeyValuePair<T1, T2>> que não é muito agradável.

A propósito, você está realmente usando IEnumerable vez de IEnumerable<T> ou por que você converter o valor Current?

Use IEnumerator para ambos seria bom

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 criar uma métodos de extensão

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

Uso

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

Em vez de criar duas matrizes distintas que você poderia fazer uma matriz bidimensional, ou um dicionário (o que seria melhor). Mas, realmente, se ele funciona eu não tentar mudá-lo.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top