2012-08-24 31 views
6

Ho un List<Thing> things, dove è necessario recuperare frequentemente un numero di Thing s cercando una combinazione di due variabili T1 f1 e T2 f2, che sono tipi di valore. Il modo in cui lo faccio ora è semplicemente things.Where(t => t.Field1 == f1 && t.Field2 == f2). Tuttavia, faccio molte di queste ricerche frequentemente e ho bisogno di un metodo più efficace.Il dizionario supporta chiavi duplicate e multidimensionali?

Fortunatamente, non è necessario che gli elementi things siano stati rimossi o aggiunti, quindi ho pensato di analizzare l'elenco in fase di costruzione e aggiungerlo a Dictionary<T1, Lookup<T2, Thing>>. Tuttavia, questo sembra disordinato, specialmente con l'analisi aggiunta. E diventa davvero peloso se ho bisogno di cercare ancora più campi. Tre campi apparirebbero come Dictionary<T1, Dictionary<T2, Lookup<T3, Thing>>>.

Il mio prossimo pensiero è stato quello di fare un Lookup<Tuple<T1,T2,T3,...>,Thing>. Ma in questo caso, non sono sicuro se i tasti funzioneranno effettivamente perché Tuple è un tipo di riferimento.

Anche se faccio un Lookup<ValueType<T1,T2,T3,...>,Thing> things, la dichiarazione di ricerca sarà qualcosa come things[new ValueType<T1,T2,T3,...>(f1, f2, f3, ...)] che è piuttosto brutto (e non sono ancora sicuro se potrei fidarmi di quelle chiavi).

Esiste una soluzione più elegante a questo che mantiene i vantaggi delle prestazioni di un hashtable e dove potrei semplicemente digitare qualcosa come IEnumerable<Thing> found = things[f1, f2, f3, ...];?

+1

Hai mai pensato di utilizzare qualcosa come un SQLite nel database di memoria? – CodingGorilla

+0

Does Thing? Ha una proprietà di identificazione (ID, PrimaryKey o qualsiasi altra cosa)? –

+0

[Dizionario generico multi-chiave C#] (http://www.codeproject.com/Articles/32894/C-Multi-key-Generic-Dictionary) –

risposta

3

Lookup<Tuple<T1,T2,T3,...>,Thing> funziona, dal momento che le sostituzioni TupleEquals e GetHashCode.

Per rendere la sintassi di ricerca meno brutta, è possibile utilizzare Tuple.Create che supporta l'inferenza di tipo. Il tuo codice diventa things[Tuple.Create(f1, f2, f3, ...)]. Se è ancora troppo brutto, è banale aggiungere un metodo di supporto che assuma i singoli valori come parametri.

Considererei anche la creazione della mia immutabile classe (o tipo di valore) per la chiave, in modo da ottenere nomi di campo puliti invece di ItemX. Hai solo bisogno di ignorare Equals e GetHashCode in modo coerente.

+0

Forse ero a qualcosa allora. Questo sicuramente sembra la soluzione semplice e ordinata. Non sono sicuro di cosa intendessi per "quindi hai nomi di campo chiari invece di' ItemX' "? E come sovrascrivere 'Equals' e' GetHashCode'? Non so come dovrebbero essere queste implementazioni. –

+1

Si verifica 'ItemX' se si utilizza la classe' Tuple'. L'idea è invece quella di creare la tua classe con nomi di proprietà migliori di "Elemento1", "Elemento2", ecc. – Oliver

+0

Tuple genera molte collisioni di hash. Non è un buon candidato per una chiave. – Paparazzi

1

Hai mai pensato di utilizzare una tabella hash con una combinazione di campi come chiave? Non so abbastanza del tuo set di dati per dire se questo è vitale o meno. Dal momento che le chiavi dovrebbero essere uniche. Ma dal momento che non stai facendo aggiunte o rimozioni utilizzando una tabella hash per le ricerche in memoria è più veloce che puoi ottenere.

+0

Ovviamente, la tupla è una combinazione dei campi e 'Lookup' è una tabella hash. – CodesInChaos

1

Se ho ottenuto destro, è possibile utilizzare Hashtable con Tuple, esempio qui sotto:

soluzione
 // populate Hastable 
     var hash = new Hashtable();    
     var tuple = Tuple.Create("string", 1, 1.0); 
     hash.Add(tuple,tuple); 

     // search for item you want 
     var anotherTuple = Tuple.Create("string", 1, 1.0); 
     // result will be tuple declared above 
     var result = hash[anotherTuple]; 

più complessa (se le chiavi duplicate necessari):

public class Thing 
{ 
    public int Value1 { get; set; } 

    public double Value2 { get; set; } 

    public string Value3 { get; set; } 

    // preferable to create own Equals and GetHashCode methods 
    public Tuple<int, double> GetKey() 
    { 
     // create key on fields you want 
     return Tuple.Create(Value1, Value2); 
    } 
} 

utilizzo

var t1 = new Thing() {Value1 = 1, Value2 = 1.0, Value3 = "something"}; 
var t2 = new Thing() {Value1 = 1, Value2 = 2.0, Value3 = "something"}; 
var hash = new [] { t1, t2 }.ToLookup(item => item.GetKey()); 

var criteria = new Thing() { Value1 = 1, Value2 = 2.0, value3 = "bla-bla-bla" }; 
var r = hash[criteria.GetKey()]; // will give you t1 
+0

Errore per le chiavi duplicate e perché si utilizza l'hashtable non generico? – CodesInChaos

+0

Non penso che questa collezione debba contenere oggetti identici. 'Hastable' - usato per la * semplificazione del codice *. – user854301

+0

Sfortunatamente, il mio requisito richiede chiavi duplicate –

2

È possibile creare più ricerche e quindi intersecarle per eseguire la scansione Ches. Ecco un esempio un po 'semplificato, ma dovrebbe illustrare l'idea:

class Test { 
    public string A { get; set; } 
    public string B { get; set; } 
    public string C { get; set; } 
} 

var list = new List<Test> { 
    new Test {A = "quick", B = "brown", C = "fox"} 
, new Test {A = "jumps", B = "over", C = "the"} 
, new Test {A = "lazy", B = "dog", C = "quick"} 
, new Test {A = "brown", B = "fox", C = "jumps"} 
, new Test {A = "over", B = "the", C = "lazy"} 
, new Test {A = "dog", B = "quick", C = "brown"} 
, new Test {A = "fox", B = "jumps", C = "over"} 
, new Test {A = "the", B = "lazy", C = "dog"} 
, new Test {A = "fox", B = "brown", C = "quick"} 
, new Test {A = "the", B = "over", C = "jumps"} 
, new Test {A = "quick", B = "dog", C = "lazy"} 
, new Test {A = "jums", B = "fox", C = "brown"} 
, new Test {A = "lazy", B = "the", C = "over"} 
, new Test {A = "brown", B = "quick", C = "dog"} 
, new Test {A = "over", B = "jumps", C = "fox"} 
, new Test {A = "dog", B = "lazy", C = "the"} 
}; 
var byA = list.ToLookup(v => v.A); 
var byB = list.ToLookup(v => v.B); 
var byC = list.ToLookup(v => v.C); 
var all = byA["quick"].Intersect(byB["dog"]); 
foreach (var test in all) { 
    Console.WriteLine("{0} {1} {2}", test.A, test.B, test.C); 
} 
all = byA["fox"].Intersect(byC["over"]); 
foreach (var test in all) { 
    Console.WriteLine("{0} {1} {2}", test.A, test.B, test.C); 
} 

Questo stampa

quick dog lazy 
fox jumps over 
+0

Può essere veloce se la parola più rara che si cerca è abbastanza rara. Può essere lento se non lo è. – CodesInChaos

+0

@CodesInChaos Questo è vero, se le parole non sono distribuite bene, non si otterrebbe molta accelerazione. Anche se suppongo che avresti ancora battuto il metodo "scansione completa" descritto nella parte superiore del post. – dasblinkenlight

+0

Una leggera variante su questo sta usando la ricerca per la parola più rara, e poi filtrando giù con 'Where' da lì in poi. Potrebbe essere leggermente più veloce e richiede meno memoria. – CodesInChaos

0

Il Linq Dove o il dizionario dei dizionari è probabilmente il più bello che otterrete. Ma potrebbe essere più una questione di come stai organizzando i tuoi dati.

E.G. Questo non sarà un modo piuttosto di accesso ai dati di persone:

people["FirstName"]["LastName"] 

Di solito è meglio così cercare di venire con una chiave più semplice.

Problemi correlati