2011-12-01 14 views
6

Ho creato un metodo di estensione per trovare il numero di valori consecutivi in ​​una raccolta. Poiché è generico, consento al chiamante di definire "incrementor" che è un Func <> che dovrebbe incrementare il valore per verificare l'esistenza di un valore "successivo".Come evitare la ricorsione infinita con un enumeratore personalizzato?

Tuttavia, se il chiamante passa un incrementore improprio (cioè x => x), causerà un loop ricorsivo infinito. Qualche suggerimento su un modo pulito per prevenire questo?

public static int CountConsecutive<T>(this IEnumerable<T> values, T startValue, Func<T, T> incrementor) 
{ 
    if (values == null) 
    { 
     throw new ArgumentNullException("values"); 
    } 
    if (incrementor == null) 
    { 
     throw new ArgumentNullException("incrementor"); 
    } 
    var nextValue = incrementor(startValue); 
    return values.Contains(nextValue) 
     ? values.CountConsecutive(nextValue, incrementor) + 1 
     : 1; 
} 
+2

La soluzione semplice è assumere che la persona che scrive il chiamante è sana. A volte devi solo incolpare la persona sopra di te. Tuttavia, questa è una domanda interessante, quindi mi piacerebbe vedere quali altre persone possono portare sul tavolo. – Polynomial

+1

[Problema di interruzione] (http://en.wikipedia.org/wiki/Halting_problem)? –

+0

Se IEnumerable è ampio e contiguo (dato come incrementatore), questo è suscettibile a StackOverflowExceptions. –

risposta

0

Nel senso più puro, questo è un tentativo allo Halting Problem ed è indecidibile. Per tutti tranne i casi più semplici, dovrai fidarti di quelli che chiamano il tuo metodo.

Come altri hanno mostrato, è possibile eseguire un semplice controllo di uguaglianza per mostrare che il valore successivo è diverso. Memorizzando ogni visitato lo T funzionerà ma dovrai preoccuparti della memoria eventualmente.

Come parte, ecco uno StackOverflowException facilmente implementato, quindi devi essere cauto su qualsiasi set di dati che avrà molto su valori consecutivi.

var x = Enumerable.Range(1, 100000).CountConsecutive(1, x => x+1); 
+0

L'overflow dello stack è causato dalla profondità della ricorsione? –

+0

Sì. Nell'esempio, sono tutti consecutivi dati 'x => x + 1' e questo avrà un overflow di circa 77K (sulla mia macchina). –

1

È possibile confrontare nextValue a startValue (avrete bisogno T per implementare IComparable).

Questo risolverà questo errore, non risolverà un brutto bug incrementore che restituisce un ciclo - a1, a2, a3, ..., an, a1. Non credo che si desidera gestire questo caso, però

+1

non è necessario implementare IComparable - tutto ciò che serve è l'uguaglianza –

4

Per affrontare il caso più semplice, si può fare questo:

var nextValue = incrementor(startValue); 
if (nextValue.Equals(startValue)) { 
    throw new ArgumentException("incrementor"); 
} 

Per caso generale, fare questo:

public static int CountConsecutive<T>(this IEnumerable<T> values, T startValue, Func<T, T> incrementor) { 
    if (values == null) { 
     throw new ArgumentNullException("values"); 
    } 
    if (incrementor == null) { 
     throw new ArgumentNullException("incrementor"); 
    } 
    ISet<T> seen = new HashSet<T>(); 
    return CountConsecutive(values, startValue, incrementor, seen); 
} 

private static int CountConsecutive<T>(IEnumerable<T> values, T startValue, Func<T, T> incrementor, ISet<T> seen) { 
    if (!seen.Add(startValue)) { 
     throw new ArgumentException("incrementor"); 
    } 
    var nextValue = incrementor(startValue); 
    return values.Contains(nextValue) 
     ? values.CountConsecutive(nextValue, incrementor) + 1 
     : 1; 
} 
+1

+1: dato che l'incremento è definito come Func , non avrebbe senso se restituisse lo stesso valore. A meno che, naturalmente, non mantenga una sorta di conteggio delle chiamate interne statiche e venga effettuato per restituire qualcosa come 1,1,2,2,3,3, ma sarei sorpreso se fosse necessario supportare tale incremento. Sembra ragionevole richiedere che incrementor (x)! = X e anche che sia aciclico. –

Problemi correlati