2010-08-17 25 views
15

Actualmente estoy aprendiendo F # y me encanta el operador yield! (yield-bang). No solo por su nombre sino también por lo que hace, por supuesto.F # rendimiento! operador - Implementación y posibles equivalentes de C#

El operador yield! básicamente le permite ceder todos los elementos de una secuencia a partir de una expresión de secuencia. Esto es útil para componer enumeradores. Como encuentro regularmente enumeradores grandes y complicados, me interesan las estrategias que podemos usar para dividirlas y componerlas a partir de enumeradores más simples.

Desafortunadamente, el operador yield! no está disponible en C#. Por lo que yo entiendo, lo que hace es como un foreach (var x in source) yield x; pero el libro que estoy leyendo (Petricek's Real World F# - Manning) sugiere que tiene un mejor rendimiento ...

  • Entonces, ¿qué es exactamente lo que el F # compilador de aquí? (Sí, también puedo verlo usando Reflector pero me gustaría tener una descripción más detallada del mecanismo).

Con el fin de lograr una construcción similar en C# He explorado múltiples maneras, pero ninguna de ellas es tan conciso como el operador yield! y yo también no estoy seguro acerca de la complejidad de los mismos. ¿Podría alguien dar su opinión si mis números de BigO son correctos?

  • Decompose empadronador en múltiples enumeradores privadas y luego producir cada elemento desde el empadronador público:

    foreach (var x in part1()) yield x 
    foreach (var x in part2()) yield x 
    

    Esto dará lugar efectivamente en una "doble rendimiento" en cada elemento. ¿Es eso O (2n) entonces? (¿o posiblemente peor?) De todos modos, el uso de este enfoque me impide usar yield break; desde cualquiera de mis subpartes.

  • Decompose empadronador en varios encuestadores privados y luego concat todos los encuestadores privados desde el empadronador pública:

    return part1().Concat(part2()) 
    

    creo que esto no es diferente de la solución antes mencionada porque Concat() se implementa de la manera que he descrito.

¿Alguna otra opción?

Respuesta

6

En cuanto a cómo el compilador traduce la operación yield!, the paper citado por Thomas Levesque en su respuesta ilustra una técnica de implementación en la sección 4.3 (en particular, su ejemplo que abarca las figuras 7-9 es ilustrativo de la estrategia general). No creo que haya una buena forma de hacerlo desde dentro de un bloque iterador en C#, ya que entiendo sus soluciones propuestas, ambas podrían dar como resultado un comportamiento cuadrático cuando se usan recursivamente. Siempre se puede crear manualmente una subclase NestedEnumerable<T> para lograr los beneficios de rendimiento, pero esto será bastante desagradable en comparación con el uso de un bloque iterador normal.

7

En la versión actual de C#, no creo que tenga otras opciones que foreach... yield return y Concat. Estoy de acuerdo en que sería bueno tener el operador yield! en C#, haría ciertas construcciones mucho más elegantes, pero dudo que esta característica alguna vez llegue a la lista "imprescindible", ya que podemos prescindir fácilmente de ella.

Usted puede estar interesado en este MS research paper, que introduce un nuevo constructo yield foreach:

IEnumerable<XmlNode> Traverse(XmlNode n) 
{ 
    yield return n; 
    foreach (XmlNode c in n.ChildNodes) 
     yield foreach Traverse(c); 
} 

cuanto a su pregunta sobre la complejidad: en ambos casos es O (n). O (2n) no se utiliza, porque denota la misma complejidad que O (n) (lineal). No creo que puedas hacer nada mejor que eso con las características actuales de C# ...

+1

Como señala el documento que cita, cuando se usa de forma recursiva hay casos en los que 'yield!' (Que es equivalente al hipotético 'yield foreach' en C#) es O (n) pero' foreach ... yield return. ..' es O (n^2). – kvb

+0

@kvb: Correcto, ese fue el tema del que estoy hablando, lo siento si esto no estaba claro. –

3

No hay contraparte directa a yield! en C#. Actualmente tiene una combinación de foreach y yield return.

Sin embargo, IIRC, LINQ ofrece algo similar, es decir, el operador de consulta SelectMany, que se traduce en C# como múltiples from .. in .. cláusulas.

(estoy esperando no estoy mezclando hasta dos conceptos diferentes, pero IIRC, tanto yield! y SelectMany son esencialmente "aplanar" proyecciones;. Es decir, una jerarquía de objetos es "aplanado" en una lista.)

Cuestiones relacionadas