2012-04-20 11 views
7

Ho il codice seguente:È possibile memorizzare nella cache query LINQ parzialmente eseguite?

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) {...} 

Dove weight è un Func<T, double>.

Il problema è che voglio eseguire weight il minor numero possibile di volte. Ciò significa che dovrebbe essere eseguito al massimo una volta per ogni elemento. Potrei ottenere questo salvandolo su un array. Tuttavia, se qualsiasi peso restituisce un valore negativo, non voglio continuare l'esecuzione.

C'è un modo per farlo facilmente all'interno del framework LINQ?

risposta

15

Certo, è assolutamente fattibile:

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

E ora ...

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

e il gioco è fatto.

+0

Perché? Memoization è il termine corretto per questo concetto: http://en.wikipedia.org/wiki/Memoization – mellamokb

+0

@ L.B: OK, quale nome preferiresti per un * function memoizer *? –

+0

@EricLippert Non penso che nessuna delle risposte sia all'interno del framework LINQ (la domanda originale). Se, d'altra parte, Memoize() era già parte di LINQ, allora THAT (imho) sarebbe la risposta alla domanda dell'OP. Penso che la tua risposta dovrebbe iniziare con "No, ma puoi estendere la funzionalità in questo modo:" Forse questo non è l'intento letterale del PO. – payo

2

Un modo è quello di spostare l'eccezione nella funzione weight, o almeno simulare questo modo, facendo qualcosa di simile:

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

A questo punto, se v'è un'eccezione per essere avuto, si dovrebbe averlo. È necessario enumerare items prima di poter essere certi di ottenere l'eccezione, tuttavia, ma una volta ottenuto, non si chiamerà nuovamente weight.

0

Entrambe le risposte sono buone (dove lanciare l'eccezione e memoizzare la funzione).

Ma il tuo vero problema è che l'espressione LINQ viene valutata ogni volta che la si utilizza, a meno che non si imponga di valutare e archiviare come Elenco (o simile). Basta cambiare questo:

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

A tal:

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

0

Si potrebbe farlo con un ciclo foreach. Ecco un modo per farlo in una dichiarazione:

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, non c'è nulla di già IN quadro LINQ per fare questo, ma si potrebbe sicuramente redigere i propri metodi e richiamare dalla query LINQ (Come è già stato dimostrato da molti).

Personalmente, vorrei o ToList la prima query o utilizzare il suggerimento di Eric.

Problemi correlati