2010-01-27 4 views
11

Dato un elenco generico, avrei bisogno di un qualche tipo di indice (nel senso del database) che mi consenta un recupero rapido. Le chiavi per questo indice non sarebbero uniche, quindi non posso usare un dizionario. Ecco quello che ho in mente: Data una classe Foo {P1, P2, P3} che possono avere i dati in questo modoElenco con più indici

{ "aaa", 111, "yes" } 
{ "aaa", 112, "no" } 
{ "bbb", 111, "no" } 
{ "bbb", 220, "yes" } 
{ "bbb", 220, "no" } 
{ "ccc", 300, "yes" } 

avrei bisogno di accedere rapidamente tutti i record in cui P1 è "bbb" (3 °, 4 ° e 5) o tutti quelli in cui P2 è 111 (1 ° e 3 °). Potrei usare una lista ordinata, ma se ho bisogno di più di un modo di ordinare/indicizzare finirei con elenchi duplicati.

C'è qualcosa di incorporato nel framework .NET o forse una libreria del sistema operativo che farebbe qualcosa del genere? Grazie.

P.S. Ho menzionato "Elenco ordinato" con l'idea che un elenco ordinato restituirà/troverà un oggetto molto più velocemente. Non ho bisogno che la lista sia necessariamente ordinata; Sto solo cercando il recupero/ricerca veloce.

risposta

2

non ho mai realmente avuto la possibilità di usarla, ma si può tentare i4o. Dovrebbe fornire indici per oggetti in memoria da utilizzare con Linq. Si specificano gli indici per una classe utilizzando uno degli attributi o come parte della costruzione dell'indicizzatore, quindi si crea un IndexableCollection.

A questo punto, si interroga la raccolta utilizzando Linq e gli indici funzionano dietro le quinte per optomizzare i modelli di accesso per i dati.

+0

Suoni promettenti; Lo darò un'occhiata ... – pbz

+0

L'idea alla base di i4o è molto accurata e penso che dovrebbe essere integrata nel framework. Sfortunatamente, come è ora, è limitato a un semplice singolo dove condizione (cioè solo dove qualcosa = "valore", no && o ||). Per il mio caso è stato comunque sufficiente. Grazie. – pbz

11

(cura di elaborare la strategia di raccolta-based)

Non v'è alcuna struttura intrinseca in .NET per la ricerca utilizzando vari indici. Qui ci sono due buone strategie:

Opzione 1: LINQ, per la flessibilità e la semplicità
Per semplicità e un sacco di altre opzioni integrate, creare una lista (o qualcos'altro che implementa IEnumerable) di tipi personalizzati e usa LINQ per effettuare le tue ricerche on demand. Nota che potresti usare i tipi anonimi se è conveniente per te. Puoi anche avere i tuoi dati in una struttura XML e fare ancora tutto questo. Probabilmente sarai in grado di ottenere i tuoi dati, fare le tue ricerche e manipolare i risultati in una piccola quantità di codice chiaro. In .Net 4.0 è possibile utilizzare parallela Ling (PLINQ) per fare in modo che questo processo sfrutti facilmente l'elaborazione multi-core.

List<foo> bigFooList = new List<foo> 
{ 
    new Foo {"aaa", 111, "yes"}, 
    new Foo {"aaa", 112, "no"}, 
    new Foo {"bbb", 111, "no"}, 
    new Foo {"bbb", 220, "yes"}, 
    new Foo {"bbb", 220, "no"}, 
    new Foo {"ccc", 300, "yes"} 
};  
var smallFooList = From f In bigFooList Where f.P2 = 220 Select f; 

Opzione 2: più raccolte, per indicizzato potere look-up.
Se si stanno eseguendo molte ricerche su un set di grandi dimensioni e si richiede energia, è possibile utilizzare più raccolte per ottenere ricerche più rapide. La parte difficile è il requisito che i valori dell'indice possono essere duplicati. Ecco alcune strategie:

  • Check out the Lookup class. Crea la tua lista. Quindi, per ciascun campo per il quale si desidera una ricerca indicizzata, creare un oggetto Ricerca. Non possono essere costruiti, ma derivano dalla raccolta IEnumerable:
    Lookup<string, foo> LookupP1 = (Lookup<string, foo>) fooList.ToLookup(f => f.P1, f => p)
    Vedere il collegamento per la sintassi per il recupero degli articoli. Fondamentalmente LookupP1 contiene IGrouping di oggetti per ciascun valore univoco di P1, immesso sul valore P1. Si scorre su quell'oggetto per ottenere gli oggetti corrispondenti. Un attributo chiave degli oggetti Lookup è che sono immutabili; quindi ogni volta che aggiungi/sottrai dalla tua lista foo, devi rifare tutti gli oggetti di ricerca. Ma se raramente modifichi la tua lista, questa è la strada da percorrere.
  • Creare un Dictionary<T, List<foo>> per ogni campo su cui è necessario effettuare la ricerca per indice, dove T è il tipo di tale valore.Così, per il tuo esempio si creerebbe:
    var FoosByP1 = new Dictionary<String,List<foo>>
    var FoosByP2 = new Dictionary<Int32,List<foo>> ecc
    Poi aggiungi al FoosByP1, calettato su ogni valore P1 unico, un elenco contenente tutti gli elementi foo dove P1 ha quel valore. (es. digitata da "aaa", una lista contenente tutti gli oggetti foo per cui P1 è "aaa".) Ripetere per ogni campo Foo. Sulla base dei tuoi dati, FoosByP1You dovrebbe contenere 3 oggetti List, contenenti rispettivamente 2, 3 e 1 elementi foo. Con questo schema è possibile recuperare molto rapidamente. (Un dizionario è fondamentalmente una tabella hash).
    Il problema principale è che i tuoi dati sarebbero duplicati in in ciascuno di questi dizionari, che potrebbe essere o meno un problema. Se Foo ha campi e hai molti elementi foo, puoi risparmiare memoria avendo un dizionario centrale con un tasto numerico e tutti gli elementi foo, e i singoli dizionari indicizzati sarebbero invece Dictionary<T, List<Int32>>, dove il numero intero sarebbe l'indice di un oggetto Foo nel dizionario centrale. Ciò farebbe risparmiare memoria e sarebbe comunque abbastanza veloce.
    Se hai un dizionario centrale o meno, costruire i tuoi Dictonaries richiederà alcuni cicli di CPU, ma una volta che li avrai sarai in ottima forma. E usa Linq per costruire i tuoi dizionari!
+0

non ho bisogno di loro di essere ordinati per sé, ho solo bisogno di accedere rapidamente a questi sottoinsiemi. – pbz

+0

Com'è diverso dal solo scorrere l'elenco con un foreach? Per quanto ne so, finirà per essere un loop alla fine, cioè nessun uso di alcun indice ... – pbz

+0

Il tuo dizionario > è quello che avevo in mente. Nel mio caso specifico i4o si è rivelato sufficiente, ma questo potrebbe aiutare qualcun altro in futuro. Grazie. – pbz

1

Una via potrebbe essere quella di utilizzare solo un database relazionale integrato alla SQLite (c'è un ADO.NET vincolante qui: http://sqlite.phxsoftware.com/)

La maggior parte delle strutture di dati non stanno andando a soddisfare le vostre esigenze meno che non siate disposto a riordinare la lista/qualunque ogni volta in quanto è necessario un diverso ordine.

0

Si potrebbe prendere in considerazione qualcosa come Lucene.Net, una libreria di indicizzazione e ricerca. Non so se questa potrebbe essere una soluzione più complessa di quella che stavi cercando, ma sicuramente soddisferà le tue esigenze in termini di prestazioni.

-1

Perché non utilizzare un HashSet per memorizzare le diverse istanze dell'oggetto Foo (che sarà univoco) e quindi utilizzare una query LINQ per recuperare quelli che corrispondono ai criteri specificati?

Qualcosa di simile:

var hash = new HashSet<Foo> 
{ 
new Foo { P1 = "aaa", P2 = 111, P3 = "yes"}, 
new Foo { P1 = "aaa", P2 = 112, P3 = "no"}, 
new Foo { P1 = "bbb", P2 = 111, P3 = "no"}, 
new Foo { P1 = "bbb", P2 = 220, P3 = "yes"}, 
new Foo { P1 = "bbb", P2 = 220, P3 = "no"}, 
new Foo { P1 = "ccc", P2 = 300, P3 = "yes"}, 
}; 

var results = from match in hash 
where match.P1 == "aaa" 
select match; 
+0

Hai dimenticato la necessità di ordinare. È possibile aggiungere un ordine per clausola alla query LINQ per gestire l'ordinamento dell'elenco risultante (che è più intelligente quindi ordinare l'intero elenco prima di filtrare nella maggior parte dei casi) –

+0

Come saprebbe che P1 è indicizzato? Non sarebbe lento come un foreach? Grazie. – pbz

+0

-1: questa risposta non risolve nulla, è proprio come una matrice, non ordinata, con un sovraccarico in più. Nota anche che non dice che vuole solo una riga per 111, li vuole tutti, veloce. La soluzione di cui sopra, dato che nessuno degli oggetti è effettivamente duplicato, li memorizzerebbe tutti, e la query di Linq avrebbe iterato su tutti loro, come con un semplice array. La vera soluzione è capire prima fino a che punto è necessario andare e, se necessario, implementare una struttura simile a un database in memoria con più indici. –

12

Non dimenticare mai questo principio: renderlo corretto, renderlo chiaro, renderlo conciso, farlo in fretta. In questo ordine. Quindi, primo codice l'attuazione ingenuo:

static IEnumerable<T> GetByIndex<T>(
    List<T> list, 
    Func<T, TIndex> func, 
    TIndex key 
) { 
    return list.Where(x => func(x) == key); 
} 

Usage:

List<Test> tests = new List<Test>() { 
      new Test { Name = "aaa", Value = 111, Valid = Valid.Yes }, 
      new Test { Name = "aaa", Value = 111, Valid = Valid.Yes }, 
      new Test { Name = "bbb", Value = 112, Valid = Valid.No }, 
      new Test { Name = "bbb", Value = 111, Valid = Valid.No }, 
      new Test { Name = "bbb", Value = 220, Valid = Valid.No }, 
      new Test { Name = "ccc", Value = 220, Valid = Valid.Yes } 
}; 
IEnumerable<Test> lookup = GetByIndex(tests, x => x.Name, "bbb"); 

Quanto sopra è corretto, chiaro e conciso. Quasi sicuramente è abbastanza veloce per i tuoi scopi.

Quindi, per quanto lo rende veloce è necessario prima misura:

  1. Stabilire criterio di rendimento ragionevole.
  2. Stabilire un banco di prova dei dati del mondo reale.
  3. Profilo l'approccio semplice contro il banco di prova dei dati del mondo reale. Si noti qui che la profilazione include la deduzione o meno di questa funzionalità come collo di bottiglia nell'applicazione.

Quindi, se e solo se questo non è abbastanza veloce, dovresti provare ad ottimizzare. Non sarebbe troppo difficile implementare uno IndexedList<T> : ICollection<T> che consentirebbe di indicizzare varie proprietà.

Ecco un'implementazione ingenua che potrebbe iniziare:

class IndexedList<T> : IEnumerable<T> { 
    List<T> _list; 
    Dictionary<string, Dictionary<object, List<T>>> _dictionary; 
    Dictionary<string, Func<T, object>> _propertyDictionary; 

    public IndexedList(IEnumerable<string> propertyNames) : this(propertyNames, new List<T>()) { } 

    public IndexedList(IEnumerable<string> propertyNames, IEnumerable<T> source) { 
     _list = new List<T>(); 
     _dictionary = new Dictionary<string, Dictionary<object, List<T>>>(); 
     _propertyDictionary = BuildPropertyDictionary(propertyNames); 
     foreach (var item in source) { 
      Add(item); 
     } 
    } 

    static Dictionary<string, Func<T, object>> BuildPropertyDictionary(IEnumerable<string> keys) { 
     var propertyDictionary = new Dictionary<string,Func<T,object>>(); 
     foreach (string key in keys) { 
      ParameterExpression parameter = Expression.Parameter(typeof(T), "parameter"); 
      Expression property = Expression.Property(parameter, key); 
      Expression converted = Expression.Convert(property, typeof(object)); 
      Func<T, object> func = Expression.Lambda<Func<T, object>>(converted, parameter).Compile(); 
      propertyDictionary.Add(key, func); 
     } 
     return propertyDictionary; 
    } 

    public void Add(T item) { 
     _list.Add(item); 
     foreach (var kvp in _propertyDictionary) { 
      object key = kvp.Value(item); 
      Dictionary<object, List<T>> propertyIndex; 
      if (!_dictionary.TryGetValue(kvp.Key, out propertyIndex)) { 
       propertyIndex = new Dictionary<object, List<T>>(); 
       _dictionary.Add(kvp.Key, propertyIndex); 
      } 
      List<T> list; 
      if (!propertyIndex.TryGetValue(key, out list)) { 
       list = new List<T>(); 
       propertyIndex.Add(key, list); 
      } 
      propertyIndex[key].Add(item); 
     } 
    } 

    public IEnumerable<T> GetByIndex<TIndex>(string propertyName, TIndex index) { 
     return _dictionary[propertyName][index]; 
    } 

    public IEnumerator<T> GetEnumerator() { 
     return _list.GetEnumerator(); 
    } 

    IEnumerator IEnumerable.GetEnumerator() { 
     return GetEnumerator(); 
    } 
} 

Usage:

List<Test> tests = new List<Test>() { 
      new Test { Name = "aaa", Value = 111, Valid = Valid.Yes }, 
      new Test { Name = "aaa", Value = 111, Valid = Valid.Yes }, 
      new Test { Name = "bbb", Value = 112, Valid = Valid.No }, 
      new Test { Name = "bbb", Value = 111, Valid = Valid.No }, 
      new Test { Name = "bbb", Value = 220, Valid = Valid.No }, 
      new Test { Name = "ccc", Value = 220, Valid = Valid.Yes } 
}; 
// build an IndexedList<Text> indexed by Name and Value 
IndexedList<Test> indexed = new IndexedList<Test>(new List<string>() { "Name", "Value" }, tests); 
// lookup where Name == "bbb" 
foreach (var result in indexed.GetByIndex("Name", "bbb")) { 
    Console.WriteLine(result.Value); 
} 

Ma vedi, la ragione non si fa questo a meno che l'implementazione ingenuo non è già veloce abbastanza è a causa della complessità aggiuntiva che hai appena aggiunto al tuo sistema. Hai appena aggiunto un nuovo codice da mantenere, un nuovo codice da testare e potresti non ottenere nulla se questo non è più veloce sui dati del mondo reale o non è un collo di bottiglia della tua applicazione.

+1

Ho passato 4 ore a preoccuparmi di questo per il mio programma di giocattoli. Grazie per avermi fatto tornare alla realtà. –

0

So che hai detto che non è possibile utilizzare un dizionario, ma il seguente lavoro?

per i dati esempio dato:

{ "aaa", 111, "yes" } 
{ "aaa", 112, "no" } 
{ "bbb", 111, "no" } 
{ "bbb", 220, "yes" } 
{ "bbb", 220, "no" } 
{ "ccc", 300, "yes" } 

è possibile utilizzare il seguente:

var p1Lookup = new Dictionary<string,int []>(); 
p1Lookup.Add("aaa", new int [] {0, 1}); 
p1Lookup.Add("bbb", new int [] {2, 3, 4}); 
p1Lookup.Add("ccc", new int [] {5}); 

var p2Lookup = new Dictionary<int,int []>(); 
p1Lookup.Add(111, new int [] {0, 2}); 
p1Lookup.Add(112, new int [] {1}); 
p1Lookup.Add(220, new int [] {3, 4}); 
p1Lookup.Add(300, new int [] {5}); 

var p3Lookup = new Dictionary<int,int []>(); 
p1Lookup.Add("yes", new int [] {0, 3, 5}); 
p1Lookup.Add( "no", new int [] {1, 2, 4}); 

A seconda dell'uso, si potrebbe costruire i dizionari di look-up solo una volta

0

Se hai solo bisogno di iterare l'elenco una volta, ma cercarlo più volte e cambiarlo molto poco (dato che gli indici DB sono i migliori). Un dizionario sarebbe molto veloce una volta costruito. Il mio metodo non crea duplicati.

var indexDict = new Dictionary<string, List<int>>(); 

for(int ct = 0; ct < pList.length; ct++) 
{ 
    var item = pList[ct]; 

    if (!indexDict.ContainsKey(item.toIndexBy)) 
    { 
     indexDict.Add(item.toIndexBy, new List<int> { ct }; 
    } 
    else 
    { 
     indexDict[item.toIndexBy].add(ct); 
    } 
} 

Ora avete una ricerca super veloce per gli indici.

Quindi, se volete "BBB" s 'gli indici che si potrebbe fare:

int bbbIndexes = indexDict["bbb"];