2012-09-19 11 views
12

¿Ya hay una función en C# que puede realizar una "Zip condicional"?¿Ya existe una función Zip condicional en C#?

I.e.

¿Hay una función que permita diferentes entradas de longitud y toma un predicado que determina cuándo incrementar el enumerador de fuentes más pequeñas, de modo que se vean todos los elementos en la fuente más grande?

Como ejemplo inventado, supongamos que tenemos un enumerable de números primos y un enumerable de enteros (ambos ordenados ascendentemente). Queremos producir un nuevo enumerable que contenga el primo y todos los enteros desde el primo anterior.

{2, 3, 5, 7, 11} 

{1, 2, 3, 4, 5, 6, 7, 8, 9, 10,} 

{2, [1]}, {3,[]}, {5, [4]}, {7, [6]}, {11, [8,9,10]} 
+6

suena interesante, pero también lo suficientemente nicho que dudo se encuentra una aplicación ya hecha. – Jon

+0

Nada fuera de la caja. –

Respuesta

4

Mi solución:

public static IEnumerable<Tuple<T1, IEnumerable<T2>>> ConditionalZip<T1, T2>(
    this IEnumerable<T1> src1, 
    IEnumerable<T2> src2, 
    Func<T1, T2, bool> check) 
{ 
    var list = new List<T2>(); 
    using(var enumerator = src2.GetEnumerator()) 
    { 
     foreach(var item1 in src1) 
     { 
      while(enumerator.MoveNext()) 
      { 
       var pickedItem = enumerator.Current; 
       if(check(item1, pickedItem)) 
       { 
        list.Add(pickedItem); 
       } 
       else 
       { 
        break; 
       } 
      } 
      var items = list.ToArray(); 
      list.Clear(); 
      yield return new Tuple<T1, IEnumerable<T2>>(item1, items); 
     } 
    } 
} 

Garantiza que ambas enumeraciones se enumerarán sólo una vez.

Uso:

var src1 = new int[] { 2, 3, 5, 7, 11 }; 
var src2 = Enumerable.Range(1, 11); 
Func<int, int, bool> predicate = (i1, i2) => i1 > i2; 
var result = src1.ConditionalZip(src2, predicate); 
+0

Esto es válido y se compara con mi respuesta que parece ser O (n) (n: src2.Count()), así que mejor. –

+0

@EvrenKuzucuoglu Aunque '.ToArray();' es una operación O (n) y también lo es '.Clear()'. – Magnus

+0

Tomé esta idea y la fuente de Zip e hice una solución. Marcó esto como la respuesta. –

4

Eso es bueno. No creo que encuentres una función preconfigurada directamente en .net, pero si la operación que deseas es una acción estándar en matemáticas, estoy seguro de que hay una biblioteca en alguna parte que lo hace. Si quisieras hacerlo tú mismo, puedes usar group by. En este escenario particular:

var primes = new List<int> {2, 3, 5, 7, 11}; 
var numbers = new List<int> {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 

var groups = 
    from number in numbers 
    group number by primes.First(prime => prime >= number) into gnumber 
    select new { 
     prime = gnumber.Key, 
     numbers = gnumber.Where(n => n != gnumber.Key) 
    }; 

Parece una solución bastante simple. Creará un enumerable de un tipo anónimo con dos miembros en él. Puede transformarlo en un diccionario:

var dict = groups.ToDictionary(g => g.prime, g=> g.numbers); 

Editar: números primos tiene que ser ordenado para que esto funcione.

+0

¡Gracias, no sabía que pudieras hacer eso! Sin embargo, necesito algo como Zip, que se enumera solo una vez. No puedo garantizar que los enumerables se puedan enumerar más de una vez. –

+1

De hecho, en este caso, el algoritmo es algo así como O (n * p), por lo que, instintivamente, diría que no es óptimo. Si conocía un algoritmo óptimo específico, no hay ninguna razón por la cual no pueda implementarlo con bucles estándar. –

0

Esto es lo que fui con (fea aplicación), pero enumera las enumerables sólo una vez.

/// <summary> 
    /// Merges two sequences by using the specified predicate function to determine when to iterate the second enumerbale. 
    /// </summary> 
    /// 
    /// <returns> 
    /// An <see cref="T:System.Collections.Generic.IEnumerable`1"/> that contains merged elements of two input sequences. 
    /// </returns> 
    /// <param name="larger">The first sequence to merge.</param><param name="smaller">The second sequence to merge.</param> 
    /// <param name="resultSelector">A function that specifies how to merge the elements from the two sequences (a flag is passed into the dunction to notify when elements of the second source are exhausted.</param> 
    /// <typeparam name="TFirst">The type of the elements of the first input sequence.</typeparam> 
    /// <typeparam name="TSecond">The type of the elements of the second input sequence.</typeparam> 
    /// <typeparam name="TResult">The type of the elements of the result sequence.</typeparam> 
    public static IEnumerable<TResult> ConditionalZip<TFirst, TSecond, TResult>(this IEnumerable<TFirst> larger, IEnumerable<TSecond> smaller, Func<TFirst, TSecond, bool> predicate, Func<TFirst, TSecond, bool, TResult> resultSelector) 
    { 
     if (larger == null) 
      throw new ArgumentNullException("larger"); 
     if (smaller == null) 
      throw new ArgumentNullException("smaller"); 
     if (resultSelector == null) 
      throw new ArgumentNullException("resultSelector"); 
     else 
      return ConditionalZipIterator(larger, smaller, predicate, resultSelector); 
    } 

    private static IEnumerable<TResult> ConditionalZipIterator<TFirst, TSecond, TResult>(IEnumerable<TFirst> first, IEnumerable<TSecond> second, Func<TFirst, TSecond, bool> predicate, Func<TFirst, TSecond, bool, TResult> resultSelector) 
    { 
     using (IEnumerator<TFirst> enumerator1 = first.GetEnumerator()) 
     { 
      using (IEnumerator<TSecond> enumerator2 = second.GetEnumerator()) 
      { 
       if (!enumerator2.MoveNext()) 
       { 
        secondIsFinished = true; 
       } 
       currentSecond = secondIsFinished ? default(TSecond) : enumerator2.Current; 

       while (enumerator1.MoveNext()) 
       { 

        while (!secondIsFinished && !predicate(enumerator1.Current, currentSecond)) 
        { 
         if (!enumerator2.MoveNext()) 
         { 
          secondIsFinished = true; 
         } 
         currentSecond = secondIsFinished ? default(TSecond) : enumerator2.Current; 
        } 


        yield return resultSelector(enumerator1.Current, currentSecond, secondIsFinished); 
       } 
      } 
     } 
    } 

El usuage

primos var = new int [] {2, 3, 5, 7, 11} .ThrowIfEnumeratedMoreThan (1); var ints = Enumerable.Range (1, 20) .ThrowIfEnumeratedMoreThan (1);

 var results = ints.ConditionalZip(primes, (i, prime) => i <= prime, (i, prime, isEmpty) => new {i, prime, wasMatched=!isEmpty}) 
      .Where(x => x.wasMatched) 
      .GroupBy(x => x.prime) 
      .Select(x => new {Prime = x.Key, Values = x.Where(n => n.i != n.prime).Select(n=>n.i).ToArray()}) 
      .ToArray(); 
Cuestiones relacionadas