2012-08-24 11 views
6

Ich habe eine List<Thing> things, wo eine Anzahl von Thing s häufig durch Nachschlagen einer Kombination von zwei Variablen T1 f1 und T2 f2, die Werttypen sind abgerufen werden müssen. So wie ich das jetzt mache ist einfach things.Where(t => t.Field1 == f1 && t.Field2 == f2). Allerdings mache ich sehr viele dieser Nachschlagevorgänge häufig und benötige eine effektivere Methode.Wörterbuch unterstützt doppelte, mehrdimensionale Schlüssel?

Glücklicherweise müssen things Elemente nicht entfernt oder hinzugefügt werden, so dass ich dachte, die Liste auf Konstruktion zu analysieren und zu einer Dictionary<T1, Lookup<T2, Thing>> hinzuzufügen. Dies fühlt sich jedoch chaotisch an, insbesondere mit dem zusätzlichen Parsing. Und es wird sehr haarig, wenn ich noch mehr Felder nachschlagen muss. Drei Felder würden wie Dictionary<T1, Dictionary<T2, Lookup<T3, Thing>>> aussehen.

Mein nächster Gedanke war, eine Lookup<Tuple<T1,T2,T3,...>,Thing> zu machen. Aber in diesem Fall bin ich nicht sicher, ob die Schlüssel tatsächlich funktionieren, weil Tuple ein Referenztyp ist.

Selbst wenn ich einen Lookup<ValueType<T1,T2,T3,...>,Thing> things machen, wird die Lookup-Anweisung so etwas wie things[new ValueType<T1,T2,T3,...>(f1, f2, f3, ...)] sein, die ziemlich hässlich ist (und ich bin immer noch nicht sicher, ob ich diese Schlüssel vertrauen konnte).

Gibt es eine elegantere Lösung, die die Leistungsvorteile einer Hashtable hält und wo ich einfach etwas wie IEnumerable<Thing> found = things[f1, f2, f3, ...]; eingeben könnte?

+1

Haben Sie in Betracht gezogen, etwas wie eine SQLite in der Speicherdatenbank zu verwenden? – CodingGorilla

+0

Hat 'Thing' einen Identifikationsfaktor (ID, PrimaryKey oder was auch immer)? –

+0

[C# Mehrtasten-Generisches Wörterbuch] (http://www.codeproject.com/Articles/32894/C-Multi-key-Generic-Dictionary) –

Antwort

3

Lookup<Tuple<T1,T2,T3,...>,Thing> funktionieren wird, da Tuple Überschreibungen Equals und GetHashCode.

Um die Nachschlagesyntax weniger hässlich zu machen, können Sie Tuple.Create verwenden, die Typinferenz unterstützt. Ihr Code wird things[Tuple.Create(f1, f2, f3, ...)]. Wenn das immer noch zu hässlich ist, ist es einfach, eine Hilfsmethode hinzuzufügen, die die einzelnen Werte als Parameter verwendet.

Ich würde auch in Betracht ziehen, meine eigene unveränderliche Klasse (oder Werttyp) für den Schlüssel zu erstellen, so dass Sie saubere Feldnamen anstelle von ItemX erhalten. Sie müssen nur Equals und GetHashCode konsequent überschreiben.

+0

Vielleicht war ich dann auf etwas. Das scheint die einfache, saubere Lösung zu sein. Ich bin mir nicht sicher, was du damit meintest "also bekommst du saubere Feldnamen statt' ItemX' "obwohl? Und wie überschreibe ich 'Equals' und' GetHashCode'? Ich weiß nicht, wie diese Implementierungen aussehen sollten. –

+1

Der 'ItemX' tritt auf, wenn Sie die Klasse' Tuple' verwenden. Die Idee besteht stattdessen darin, eine eigene Klasse zu erstellen, die bessere Eigenschaften als 'Item1',' Item2' usw. hat. – Oliver

+0

Tuple erzeugt viele Hash-Kollisionen. Es ist kein guter Kandidat für einen Schlüssel. – Paparazzi

1

Haben Sie überlegt, eine Hash-Tabelle mit einer Art Kombination der Felder als Schlüssel zu verwenden? Ich weiß nicht genug über Ihren Datensatz, um zu sagen, ob dies machbar ist oder nicht. Da die Schlüssel eindeutig sein müssten. Aber da Sie keine Additionen oder Entnahmen mit einer Hash-Tabelle für Nachschlagevorgänge im Speicher durchführen, sind Sie so schnell wie möglich.

+0

Er tat es offensichtlich, das Tupel ist eine Kombination der Felder und 'Lookup' ist eine Hashtabelle. – CodesInChaos

1

Wenn ich Sie richtig verstanden habe, Sie Hashtable mit Tuple, Beispiel unten verwenden:

 // 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]; 

komplexere Lösung (wenn doppelte Schlüssel erforderlich):

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

Nutzung

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

Fehler bei doppelten Schlüsseln, und warum verwenden Sie die nicht generische Hashtabelle? – CodesInChaos

+0

Ich denke nicht, dass diese Sammlung identische Elemente enthalten sollte. 'Hastable' - verwendet für Code * Vereinfachung *. – user854301

+0

Leider benötigt meine Anforderung doppelte Schlüssel –

2

Sie können mehrere Suchvorgänge erstellen und diese dann überschneiden, um den Suchvorgang auszuführen Ches. Hier ist eine etwas vereinfachte Beispiel, aber es sollte die Idee veranschaulichen:

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

Dieser druckt

quick dog lazy 
fox jumps over 
+0

Kann schnell sein, wenn das seltenste Wort, nach dem Sie suchen, selten genug ist. Kann langsam sein, wenn es nicht ist. – CodesInChaos

+0

@CodesInChaos Das stimmt, wenn die Wörter nicht gut verteilt sind, würden Sie nicht viel schneller werden. Obwohl ich denke, dass Sie immer noch die "Full Scan" -Methode schlagen würden, die oben im Beitrag beschrieben wird. – dasblinkenlight

+0

Eine leichte Variante dazu ist das Nachschlagen nach dem seltensten Wort und das anschließende Filtern nach 'Where'. Kann etwas schneller sein und benötigt weniger Speicher. – CodesInChaos

0

Das Linq Wo oder Dictionary of Dictionaries ist wahrscheinlich das schönste, das Sie bekommen werden. Es kann jedoch eher eine Frage sein, wie Sie Ihre Daten organisieren.

E.G. Dies geht nie eine hübsche Weise den Zugriff auf Personen Daten sein:

people["FirstName"]["LastName"] 

Es ist in der Regel besser, so versuchen und mit einem einfacheren Schlüssel zu kommen.

Verwandte Themen