2012-04-20 16 views
7

Tengo el siguiente código:¿Puedo almacenar en caché las consultas LINQ parcialmente ejecutadas?

IEnumerable<KeyValuePair<T, double>> items = 
    sequence.Select(item => new KeyValuePair<T, double>(item, weight(item))); 
if (items.Any(pair => pair.Value<0)) 
    throw new ArgumentException("Item weights cannot be less than zero."); 

double sum = items.Sum(pair => pair.Value); 
foreach (KeyValuePair<T, double> pair in items) {...} 

Dónde weight es una Func<T, double>.

El problema es que quiero que weight se ejecute las veces que sea posible. Esto significa que debe ejecutarse como máximo una vez para cada elemento. Podría lograr esto guardándolo en una matriz. Sin embargo, si un peso devuelve un valor negativo, no quiero continuar la ejecución.

¿Hay alguna manera de lograr esto fácilmente dentro del marco de LINQ?

Respuesta

15

Claro, eso es totalmente factible:

public static Func<A, double> ThrowIfNegative<A, double>(this Func<A, double> f) 
{ 
    return a=> 
    { 
     double r = f(a); 
     // if r is NaN then this will throw. 
     if (!(r >= 0.0)) 
     throw new Exception(); 
     return r; 
    }; 
} 

public static Func<A, R> Memoize<A, R>(this Func<A, R> f) 
{ 
    var d = new Dictionary<A, R>(); 
    return a=> 
    { 
     R r; 
     if (!d.TryGetValue(a, out r)) 
     { 
      r = f(a); 
      d.Add(a, r); 
     } 
     return r; 
    }; 
} 

Y ahora ...

Func<T, double> weight = whatever; 
weight = weight.ThrowIfNegative().Memoize(); 

y ya está.

+0

¿Por qué? Memoization es el término correcto para este concepto: http://en.wikipedia.org/wiki/Memoization – mellamokb

+0

@ L.B: OK, ¿qué nombre preferirías para un * function memoizer *? –

+0

@EricLippert No creo que ninguna de las respuestas esté dentro del marco LINQ (la pregunta original). Si, por otro lado, Memoize() ya era parte de LINQ, entonces THAT (imho) sería la respuesta a la pregunta del OP. Creo que su respuesta debería comenzar con: "No, pero puede extender la funcionalidad de esta manera:" Quizás esta no sea la intención literal del OP. – payo

2

Una forma de hacerlo es mover la excepción en la función weight, o al menos simular hacerlo, haciendo algo como:

Func<T, double> weightWithCheck = i => 
    { 
     double result = weight(i); 
     if (result < 0) 
     { 
      throw new ArgumentException("Item weights cannot be less than zero."); 
     } 
     return result; 
    }; 

IEnumerable<KeyValuePair<T, double>> items = 
    sequence.Select(item => new KeyValuePair<T, double>(item, weightWithCheck(item))); 

double sum = items.Sum(pair => pair.Value); 

En este punto, si hay una excepción que se tenía, que debiera tenerlo. Sin embargo, debe enumerar items antes de poder estar seguro de obtener la excepción, pero una vez que la obtiene, no volverá a llamar al weight.

0

Ambas respuestas son buenas (dónde arrojar la excepción y memorizar la función).

Pero su problema real es que su expresión LINQ se evalúa cada vez que la usa, a menos que la obligue a evaluarla y almacenarla como una Lista (o similar). Sólo cambia esto:

sequence.Select(item => new KeyValuePair<T, double>(item, weight(item)));

A esto:

sequence.Select(item => new KeyValuePair<T, double>(item, weight(item))).ToList();

0

Usted podría hacerlo con un bucle foreach. Aquí hay una manera de hacerlo en una declaración:

IEnumerable<KeyValuePair<T, double>> items = sequence 
    .Select(item => new KeyValuePair<T, double>(item, weight(item))) 
    .Select(kvp => 
    { 
     if (kvp.Value < 0) 
      throw new ArgumentException("Item weights cannot be less than zero."); 
     else 
      return kvp; 
    } 
    ); 
0

No, ya no hay nada EN el marco de LINQ para hacer esto, pero seguramente se podría escribir sus propios métodos y invocarlos de la consulta LINQ (Como ya se ha demostrado por muchos).

Personalmente, yo o bien ToList la primera consulta o el uso de la sugerencia de Eric.

Cuestiones relacionadas