包含多个索引的列表

时间:2010-01-27 00:34:31

标签: c# .net list collections indexing

给定一个通用List我需要某种索引(在数据库意义上),这将允许我快速检索。这个索引的键不是唯一的,所以我不能使用字典。这就是我的想法:给定一个类Foo {P1,P2,P3}可能有这样的数据

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

我需要快速访问P1为“bbb”(第3,第4和第5)的所有记录或P2为111(第1和第3)的所有记录。我可以使用排序列表,但如果我需要多种排序/索引方法,我最终会得到重复的列表。

.NET框架中是否内置了某些东西,或者可能会有类似这样的操作系统库?感谢。

P.S。我提到“排序列表”的想法是排序列表将更快地返回/查找项目。我不需要列表必须排序;我只是在寻找快速检索/发现。

8 个答案:

答案 0 :(得分:13)

永远不要忘记这个原则:正确,明确,简洁,快速。以该顺序。所以,首先编写天真的实现代码:

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. 描述针对现实世界数据的测试平台的简单方法。请注意,分析包括推断此功能是否是您应用程序中的瓶颈。
  4. 然后,当且仅当这个不够快时,你应该尝试优化。实现允许您索引各种属性的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 :(得分:12)

编辑详细阐述基于收集的策略)

.NET中没有内部结构可供查找使用各种索引。以下是两个好策略:

选项1 :LINQ ,灵活性和简单性 为了简单和许多其他集成选项,创建一个自定义类型的List(或其他实现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包含P1的每个唯一值的 IGrouping 对象,键入该P1值。您遍历该对象以获取匹配的项目。 Lookup对象的一个​​关键属性是它们是不可变的;因此,每次从fooList中添加/减去时,都必须重做所有Lookup对象。但是如果你很少改变你的fooList,这就是你要走的路。
  • 为每个需要按索引搜索的字段创建Dictionary<T, List<foo>>,其中T是该值的类型。因此,对于您的示例,我们将创建:
    var FoosByP1 = new Dictionary<String,List<foo>>
    var FoosByP2 = new Dictionary<Int32,List<foo>>等 然后添加到FoosByP1,键入每个唯一的P1值,List包含P1具有该值的所有foo项。 (例如,由“aaa”键入,包含P1为“aaa”的所有foo对象的List。)对每个Foo字段重复。根据您的数据,FoosByP1You将包含3个List对象,分别包含2,3和1个foo项。使用此方案,您可以非常快速地检索。 (字典基本上是一个哈希表) 主要问题是您的数据将在每个词典中重复,这可能是也可能不是问题。如果Foo有 20 个字段且你有很多foo项,你可以通过一个带有数字键和所有foo项的中心字典来节省内存,而单个索引字典将改为{{1} },其中整数将是中央字典中Foo项的索引。这样可以节省内存并且速度非常快 无论你是否有中心词典,建立你的Dictonaries都需要一些cpu周期,但是一旦你拥有它们,你将会处于良好的状态。并使用Linq来构建你的词典!

答案 2 :(得分:2)

一种方法是使用嵌入式关系数据库和SQLite(这里有一个ADO.NET绑定:http://sqlite.phxsoftware.com/

大多数数据结构都不符合您的要求,除非您愿意每次重新排序列表/任何内容,因为您需要不同的顺序。

答案 3 :(得分:2)

我从未真正有机会使用它,但您可以尝试i4o。它应该为内存中的对象提供索引以供Linq使用。您可以使用任一属性或构建索引器来指定类的索引,然后创建IndexableCollection。

此时,您只需使用Linq查询集合,并且索引在幕后工作以优化数据的访问模式。

答案 4 :(得分:0)

您可能需要考虑像Lucene.Net这样的索引和搜索库。我不知道这可能是一个比你想要的更复杂的解决方案,但它肯定会满足你的性能需求。

答案 5 :(得分: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} );

根据使用情况,您只需构建一次查找词典

答案 6 :(得分:0)

如果你只需要迭代列表一次,但是多次搜索它,并且很少改变它(因为DB索引是最好的)。一旦建成,字典就会非常快。我的方法不会创建重复项。

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

现在您可以快速查找索引。

因此,如果您想要&#34; bbb&#34;的索引,您可以这样做:

int bbbIndexes = indexDict["bbb"];

答案 7 :(得分:-2)

为什么不使用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;