2010-01-27 8 views
11

Учитывая общий список, мне нужен какой-то индекс (в смысле базы данных), который позволит мне быстро найти. Ключи для этого индекса не будут уникальными, поэтому я не могу использовать словарь. Вот что я имею в виду: Учитывая класс Foo {P1, P2, P3}, которые могут иметь данные, как этотСписок с несколькими индексами

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

я должен был бы быстро все записи, где P1 «БББ» (3-й, 4-й , и 5) или все те, где P2 равно 111 (1-й и 3-й). Я мог бы использовать отсортированный список, но если мне понадобится более одного способа сортировки/индексации, я получаю дублированные списки.

Есть ли что-то встроенное в .NET framework или, возможно, библиотека ОС, которая будет делать что-то вроде этого? Благодарю.

P.S. Я упомянул «отсортированный список» с мыслью, что отсортированный список вернет/найдет элемент намного быстрее. Мне не нужен список, который нужно сортировать; Я просто ищу быстрого поиска/поиска.

ответ

2

Я никогда на самом деле имел возможность использовать его, но вы можете попробовать i4o. Он должен предоставлять индексы для объектов в памяти для использования с Linq. Вы указываете индексы для класса, используя либо атрибуты, либо как часть построения индексатора, тогда вы создаете IndexableCollection.

В этот момент вы просто запрашиваете коллекцию с помощью Linq, а индексы работают за кулисами, чтобы опционировать шаблоны доступа для данных.

+0

Звучит многообещающе; Я посмотрю на это ... – pbz

+0

Идея i4o очень аккуратная, и я думаю, что она должна быть встроена в фреймворк. К сожалению, так как сейчас он ограничен простым синим, где условие (т. Е. Только где-то = «значение», нет && или ||). Для моего случая это было достаточно, хотя. Благодарю. – pbz

11

(ред разработать на стратегии сбора на основе)

Там нет внутренней структуры в .NET для поиска использования различных индексов. Вот две хорошие стратегии:

Вариант 1: LINQ, гибкости и простоты
Для простоты и много других интегрированных опций, создать список (или что-то другое, что реализует IEnumerable) пользовательских типов и используйте LINQ для поиска по запросу. Обратите внимание, что вы можете использовать анонимные типы, если это удобно для вас. Вы также можете иметь свои данные в структуре XML и все еще делать все это. Вероятно, вы сможете получить свои данные, выполнить поиск и обработать результаты в небольшом количестве чистого кода. В .Net 4.0 вы можете использовать parallel Ling (PLINQ), чтобы этот процесс легко использовал многоядерную обработку.

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; 

Вариант 2: Несколько коллекции, для индексированной справочной мощности.
Если вы делаете много поисков на большом наборе и нуждаетесь в мощности, вы можете использовать несколько коллекций для более быстрого поиска. Трудная часть - ваше требование, чтобы значения индекса можно было дублировать. Вот несколько стратегий:

  • Отъезд the Lookup class. Создайте свой список. Затем для каждого поля, для которого требуется индексированный поиск, создайте объект Lookup. Они не могут быть построены, но получены из вашей коллекции IEnumerable:
    Lookup<string, foo> LookupP1 = (Lookup<string, foo>) fooList.ToLookup(f => f.P1, f => p)
    См. Ссылку для синтаксиса для извлечения ваших элементов. В основном LookupP1 содержит IGrouping объектов для каждого уникального значения P1, с ключом на это значение P1. Вы перебираете этот объект, чтобы получить соответствующие элементы. Ключевым атрибутом объектов Lookup является то, что они неизменяемы; поэтому каждый раз, когда вы добавляете/вычитаете из своего fooList, вам нужно будет redo все ваши объекты Lookup. Но если вы редко изменяете свой fooList, это путь.
  • Создайте Dictionary<T, List<foo>> для каждого поля, по которому вам нужно будет искать по индексу, где T - тип этого значения.Так для примера мы создали бы:
    var FoosByP1 = new Dictionary<String,List<foo>>
    var FoosByP2 = new Dictionary<Int32,List<foo>> и т.д.
    Затем добавить к FoosByP1, шпонке на каждом уникальном значении P1, список, содержащий все детали Foo где P1 имеет это значение. (например, «aaa», «Список», содержащий все объекты foo, для которых P1 «aaa».) Повторите для каждого поля Foo. Основываясь на ваших данных, FoosByP1You будет содержать 3 объекта List, содержащие 2, 3 и 1 элементы foo соответственно. С помощью этой схемы вы можете быстро получить ее. (Словарь в основном представляет собой хеш-таблицу).
    Главное, что ваши данные будут дублированы в каждом из этих словарей, что может быть или не быть проблемой. Если Foo имеет поля, и у вас есть много элементов foo, вы можете сохранить память, имея центральный словарь с цифровым ключом и всеми вашими элементами foo, а отдельные индексированные словари будут вместо этого Dictionary<T, List<Int32>>, где целым числом будет индекс пункта Foo в вашем центральном словаре. Это позволит сэкономить память и будет довольно быстро.
    Если у вас есть центральный словарь или нет, построение ваших диктонаров займет несколько циклов процессора, но как только вы их получите, вы будете в отличной форме. И используйте Linq для создания ваших словарей!
+0

Мне не нужно, чтобы они были отсортированы по себе, я просто нужен быстрый доступ к этим подмножеств. – pbz

+0

Как это отличается от простого перебора списка с помощью foreach? Насколько я знаю, в конце концов это будет цикл, т. Е. Не использовать какой-либо индекс ... – pbz

+0

Ваш словарь > это то, что я имел в виду. В моем конкретном случае i4o оказалось достаточным, но это может помочь кому-то еще в будущем. Благодарю. – pbz

1

Один маршрут будет просто использовать встроенную реляционную базу данных, а ля SQLite (есть более ADO.NET связывании здесь: http://sqlite.phxsoftware.com/)

Большинство структур данных не будет соответствовать вашим требованиям, если вы не желая повторно отсортировать список/независимо от каждого раза, так как вам нужен другой порядок.

0

Возможно, вы захотите рассмотреть что-то вроде Lucene.Net, библиотеку индексирования и поиска. Я не знаю, может ли это быть более сложным решением, чем вы искали, но это определенно соответствовало бы вашим потребностям в производительности.

-1

Почему бы не использовать HashSet для хранения различных экземпляров объекта Foo (который будет уникальным), а затем использовать запрос LINQ для извлечения тех, которые соответствуют заданным критериям?

Что-то вроде:

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

Забыл о сортировке. Вы можете добавить предложение order by к запросу LINQ для обработки сортировки результирующего списка (что более умно, чем сортировка всего списка, а затем фильтрация в большинстве случаев). –

+0

Как он узнает, что P1 проиндексирован? Разве это не было бы так же медленно, как предсказание? Благодарю. – pbz

+0

-1: Этот ответ ничего не решает, это похоже на массив, несортированный при этом, с дополнительными накладными расходами. Также обратите внимание, что он не говорит, что хочет всего одну строку за 111, он хочет их всех, быстро. Вышеупомянутое решение, учитывая, что ни один из объектов на самом деле не дублирует, сохранит их все, и запрос Linq будет перебирать их по всем, как с простым массивом. Реальное решение заключается в том, чтобы сначала выяснить, как далеко вы должны идти, а затем, если необходимо, реализовать структуру базы данных в памяти с несколькими индексами. –

12

Никогда не забывайте об этом принципе: сделайте это правильно, дайте ему понять, сделайте его кратким, сделайте это быстро. В этой последовательности. Итак, первый код до наивной реализации:

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

Использование:

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

выше правильно, четко и кратко. Почти наверняка это достаточно быстро для ваших целей.

Итак, насколько делая это быстро вы должны сначала меру:

  1. Установить разумный критерий эффективности.
  2. Установите испытательный стенд данных реального мира.
  3. Простой подход к тестовому пласту реальных данных. Обратите внимание, что профилирование включает в себя вывод о том, является ли эта функциональность узким местом в вашем приложении.

Тогда, если и только если это недостаточно быстро для вас, вы должны попытаться оптимизировать. Было бы непросто реализовать IndexedList<T> : ICollection<T>, что позволит вам индексировать различные свойства.

Вот наивная реализация, которая может вам начать:

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

Использование:

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

Но видите, причина, вы не делайте этого, если наивной реализации уже не быстро достаточно из-за дополнительной сложности, которую вы только что добавили в свою систему. Вы просто добавили новый код для поддержки, новый код для тестирования и, возможно, ничего не выиграете, если это не ускорится в ваших реальных данных или не является узким местом вашего приложения.

+1

Я провел 4 часа, беспокоясь об этом для своей игрушечной программы. Спасибо, что помог мне вернуться в реальность. –

0

Я знаю, что вы сказали, что не можете использовать словарь, но будет ли следующая работа?

Для примера набора данных:

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

Вы можете использовать следующее:

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

В зависимости от использования, вы можете построить просмотровых словари только один раз

0

Если вам нужно только перебирать список один раз, но искать его много раз и менять его очень мало (лучше всего использовать индексы БД). Словарь будет очень быстрым после его создания. Мой метод не создает дубликатов.

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

Теперь у вас есть быстрый быстрый поиск индексов.

Так что, если вы хотите «БББ» 's индексов, которые вы могли бы сделать:

int bbbIndexes = indexDict["bbb"]; 
Смежные вопросы