2010-01-27 38 views
11

Với danh sách chung tôi sẽ cần một số loại chỉ mục (trong ý nghĩa cơ sở dữ liệu) sẽ cho phép tôi truy xuất nhanh. Các khóa cho chỉ mục này sẽ không phải là duy nhất, vì vậy tôi không thể sử dụng từ điển. Đây là những gì tôi có trong tâm trí: Cho một lớp Foo {P1, P2, P3} mà có thể có dữ liệu như thế nàyDanh sách có nhiều chỉ mục

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

tôi sẽ cần phải nhanh chóng truy cập vào tất cả các hồ sơ, nơi P1 là "bbb" (3, 4 , và thứ 5) hoặc tất cả những nơi P2 là 111 (1 và 3). Tôi có thể sử dụng một danh sách được sắp xếp nhưng nếu tôi cần nhiều hơn một cách sắp xếp/lập chỉ mục, tôi sẽ kết thúc với các danh sách trùng lặp.

Có điều gì đó được tích hợp trong khuôn khổ .NET hoặc có thể là thư viện hệ điều hành sẽ làm điều gì đó như thế này? Cảm ơn.

P.S. Tôi đã đề cập đến "Danh sách được sắp xếp" với ý tưởng rằng một danh sách được sắp xếp sẽ trả về/tìm một mục nhanh hơn nhiều. Tôi không cần danh sách cần phải được sắp xếp; Tôi chỉ đang tìm kiếm/tìm kiếm nhanh.

Trả lời

2

Tôi chưa bao giờ thực sự đã có một cơ hội để sử dụng nó, nhưng bạn có thể thử i4o. Nó được cho là cung cấp các chỉ mục cho các đối tượng trong bộ nhớ để sử dụng với LINQ. Bạn chỉ định các chỉ mục cho một lớp bằng cách sử dụng các thuộc tính hoặc là một phần của việc xây dựng trình chỉ mục, sau đó bạn tạo một IndexableCollection.

Tại thời điểm đó, bạn chỉ truy vấn bộ sưu tập bằng cách sử dụng LINQ và các chỉ mục hoạt động phía sau hậu trường để tối ưu hóa mẫu truy cập cho dữ liệu.

+0

Âm thanh đầy hứa hẹn; Tôi sẽ xem nó ... – pbz

+0

Ý tưởng đằng sau i4o rất gọn gàng và tôi nghĩ nó nên được xây dựng trong khung công tác. Thật không may, vì nó là ngay bây giờ nó được giới hạn trong một đơn giản, nơi điều kiện (tức là chỉ nơi một cái gì đó = "giá trị", không && hoặc ||). Đối với trường hợp của tôi nó là đủ mặc dù. Cảm ơn. – pbz

11

(Sửa để xây dựng chiến lược thu-based)

Không có cấu trúc nội tại trong .NET cho tìm kiếm bằng cách sử dụng các chỉ số khác nhau. Dưới đây là hai chiến lược tốt:

Lựa chọn 1: LINQ, sự linh hoạt và đơn giản
Để đơn giản và rất nhiều tùy chọn tích hợp khác, tạo ra một danh sách (hay cái gì khác mà thực hiện IEnumerable) các loại tùy chỉnh và sử dụng LINQ để thực hiện tra cứu theo yêu cầu của bạn. Lưu ý rằng bạn có thể sử dụng các loại ẩn danh nếu điều đó thuận tiện cho bạn. Bạn cũng có thể có dữ liệu của bạn trong một cấu trúc XML và vẫn làm tất cả điều này. Bạn sẽ có khả năng nhận dữ liệu của mình, thực hiện tra cứu và thao tác các kết quả trong một lượng nhỏ mã rõ ràng. Trong Net 4.0, bạn có thể sử dụng song song Ling (PLINQ) để dễ dàng có quá trình này tận dụng lợi thế của xử lý đa lõi.

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; 

Lựa chọn 2: Nhiều bộ sưu tập, cho lập chỉ mục điện nhìn lên.
Nếu bạn đang thực hiện rất nhiều tra cứu trên một bộ lớn và cần sức mạnh, bạn có thể sử dụng nhiều bộ sưu tập để đạt được tra cứu nhanh hơn. Phần khó khăn là yêu cầu của bạn rằng các giá trị chỉ mục có thể được nhân đôi. Dưới đây là một số chiến lược:

  • Kiểm tra the Lookup class. Tạo danh sách của bạn. Sau đó, đối với mỗi trường mà bạn muốn tìm kiếm được lập chỉ mục, hãy tạo đối tượng tra cứu. Chúng không thể được xây dựng, nhưng có nguồn gốc từ bộ sưu tập IEnumerable của bạn:
    Lookup<string, foo> LookupP1 = (Lookup<string, foo>) fooList.ToLookup(f => f.P1, f => p)
    Xem liên kết để tìm kiếm các mục của bạn. Về cơ bản LookupP1 chứa IGrouping đối tượng cho từng giá trị duy nhất của P1, được khóa trên giá trị P1 đó. Bạn lặp qua đối tượng đó để lấy các mục phù hợp của mình. Một thuộc tính quan trọng của các đối tượng Lookup là chúng không thay đổi được; do đó, mỗi khi bạn cộng/trừ từ fooList của mình, bạn sẽ phải làm lại tất cả các đối tượng Tra cứu của mình. Nhưng nếu bạn hiếm khi thay đổi fooList của bạn, đây là con đường để đi.
  • Tạo một Dictionary<T, List<foo>> cho mỗi trường mà bạn sẽ cần phải tìm kiếm theo chỉ mục, trong đó T là loại giá trị đó.Vì vậy, ví dụ như bạn, chúng tôi sẽ tạo ra:
    var FoosByP1 = new Dictionary<String,List<foo>>
    var FoosByP2 = new Dictionary<Int32,List<foo>>, vv
    Sau đó thêm vào FoosByP1, keyed trên mỗi giá trị P1 độc đáo, một danh sách chứa tất cả các mục foo nơi P1 có giá trị đó. (ví dụ: "aaa", một List chứa tất cả các đối tượng foo mà P1 là "aaa".) Lặp lại cho mỗi trường Foo. Dựa trên dữ liệu của bạn, FoosByP1Bạn sẽ chứa 3 đối tượng Danh sách, chứa các mục 2, 3 và 1 foo tương ứng. Với lược đồ này, bạn có thể truy xuất nhanh chóng. (Một từ điển về cơ bản là một bảng băm).
    Bắt chính là dữ liệu của bạn sẽ là được sao chép trong mỗi bộ từ điển này, điều này có thể hoặc không có vấn đề gì. Nếu Foo có các trường và bạn có nhiều mục foo, bạn có thể lưu bộ nhớ bằng cách có từ điển trung tâm với phím số và tất cả các mục foo của bạn và từ điển được lập chỉ mục riêng lẻ thay vào đó là Dictionary<T, List<Int32>>, trong đó số nguyên sẽ là chỉ mục của một mục Foo trong từ điển trung tâm của bạn. Điều này sẽ tiết kiệm bộ nhớ và vẫn còn khá nhanh.
    Cho dù bạn có từ điển trung tâm hay không, việc xây dựng Dictonaries của bạn sẽ mất một số chu kỳ CPU, nhưng một khi bạn có chúng, bạn sẽ có hình dạng tuyệt vời. Và sử dụng LINQ để xây dựng từ điển của bạn!
+0

Tôi không cần họ để được sắp xếp cho mỗi gia nhập, tôi chỉ cần những tập con truy cập nhanh. – pbz

+0

Điều đó khác với việc lặp qua danh sách với một tài liệu tham khảo như thế nào? Theo như tôi biết rằng sẽ kết thúc là một vòng lặp cuối cùng, tức là không sử dụng bất kỳ chỉ số nào ... – pbz

+0

Từ điển của bạn > là những gì tôi có trong đầu. Trong trường hợp cụ thể của tôi i4o hóa ra là đủ, nhưng điều này có thể giúp đỡ người khác trong tương lai. Cảm ơn. – pbz

1

Một tuyến đường sẽ được chỉ cần sử dụng một cơ sở dữ liệu nhúng quan hệ a la SQLite (có một ADO.NET ràng buộc ở đây: http://sqlite.phxsoftware.com/)

Hầu hết các cấu trúc dữ liệu sẽ không đáp ứng yêu cầu của bạn trừ khi bạn sẵn sàng sắp xếp lại danh sách/bất cứ khi nào bạn cần một thứ tự khác.

0

Bạn có thể muốn xem xét thứ gì đó như Lucene.Net, một thư viện lập chỉ mục và tìm kiếm. Tôi không biết nếu điều này có thể là một giải pháp phức tạp hơn bạn đang tìm kiếm, nhưng nó chắc chắn sẽ đáp ứng nhu cầu hiệu suất của bạn.

-1

Tại sao không sử dụng một HashSet để lưu trữ các trường hợp khác nhau của đối tượng Foo (sẽ là duy nhất) và sau đó sử dụng truy vấn LINQ để truy xuất kết quả phù hợp với tiêu chí đã cho?

Cái gì như:

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

Quên về nhu cầu phân loại. Bạn có thể thêm mệnh lệnh theo mệnh đề vào truy vấn LINQ để xử lý việc sắp xếp danh sách kết quả (thông minh hơn, sau đó sắp xếp toàn bộ danh sách trước rồi lọc trong hầu hết các trường hợp) –

+0

Làm sao biết được P1 được lập chỉ mục? Nó sẽ không chỉ là chậm như một foreach? Cảm ơn. – pbz

+0

-1: Câu trả lời này không giải quyết được gì, nó giống như một mảng, không được phân loại ở đó, với chi phí phụ trội. Cũng lưu ý rằng anh ta không nói anh ta muốn chỉ một hàng cho 111, anh ta muốn tất cả, nhanh chóng. Giải pháp trên, cho rằng không có đối tượng nào thực sự trùng lặp, sẽ lưu trữ tất cả chúng và truy vấn LINQ sẽ lặp lại trên tất cả, như với một mảng đơn giản. Giải pháp thực tế đầu tiên là tìm ra khoảng cách bạn cần đến, và sau đó nếu cần, hãy thực hiện cấu trúc giống như bộ nhớ trong với nhiều chỉ mục. –

12

Đừng bao giờ quên nguyên tắc này: Làm cho nó chính xác, làm cho nó rõ ràng, làm cho nó ngắn gọn, làm cho nó nhanh. Theo thứ tự đó. Vì vậy, mã đầu tiên lên thi ngây thơ:

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

Cách sử dụng:

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

Trên đây là chính xác, rõ ràng và súc tích. Hầu như chắc chắn nó đủ nhanh cho mục đích của bạn.

Vì vậy, càng xa càng làm cho nó nhanh bạn phải biện pháp đầu tiên:

  1. Xây dựng tiêu chí hiệu suất hợp lý.
  2. Thiết lập giường thử nghiệm của dữ liệu trong thế giới thực.
  3. Lập hồ sơ cách tiếp cận đơn giản chống lại giường thử nghiệm của dữ liệu trong thế giới thực. Lưu ý ở đây rằng lược tả bao gồm suy luận xem chức năng này có phải là nút cổ chai trong ứng dụng của bạn hay không.

Sau đó, nếu và chỉ nếu điều này không đủ nhanh cho bạn, bạn nên cố gắng tối ưu hóa. Nó sẽ không quá khó để thực hiện một IndexedList<T> : ICollection<T> mà sẽ cho phép bạn lập chỉ mục ra khỏi các thuộc tính khác nhau.

Đây là một thực hiện ngây thơ mà có thể giúp bạn bắt đầu:

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

Cách sử dụng:

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

Nhưng thấy, lý do bạn không làm điều này trừ khi việc thực hiện ngây thơ chưa được nhanh đủ là vì sự phức tạp bổ sung mà bạn vừa thêm vào hệ thống của mình. Bạn vừa thêm mã mới để duy trì, mã mới để kiểm tra và có thể không đạt được bất kỳ thứ gì nếu dữ liệu trong thế giới thực của bạn không nhanh hơn hoặc không phải là một nút cổ chai của ứng dụng của bạn.

+1

Tôi đã dành 4 giờ lo lắng về điều này cho chương trình đồ chơi của tôi. Cảm ơn bạn đã tát tôi vào thực tế. –

0

Tôi biết bạn nói rằng bạn không thể sử dụng từ điển, nhưng công việc sau đây có được không?

Đối với dữ liệu ví dụ của bạn thiết lập:

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

Bạn có thể sử dụng như sau:

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

Tùy thuộc vào cách sử dụng, bạn có thể xây dựng các điển nhìn lên chỉ một lần

0

Nếu bạn chỉ cần lặp lại danh sách một lần, nhưng tìm kiếm nó nhiều lần và thay đổi nó rất ít (như chỉ mục DB là tốt nhất). Một từ điển sẽ rất nhanh khi được xây dựng. Phương pháp của tôi không tạo bản sao.

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

Bây giờ bạn có tra cứu siêu nhanh các chỉ mục.

Vì vậy, nếu bạn muốn "bbb" 's chỉ số bạn có thể làm:

int bbbIndexes = indexDict["bbb"]; 
Các vấn đề liên quan