What .NET collection provides the fastest search
Asked Answered
L

10

168

I have 60k items that need to be checked against a 20k lookup list. Is there a collection object (like List, HashTable) that provides an exceptionly fast Contains() method? Or will I have to write my own? In otherwords, is the default Contains() method just scan each item or does it use a better search algorithm.

foreach (Record item in LargeCollection)
{
    if (LookupCollection.Contains(item.Key))
    {
       // Do something
    }
}

Note. The lookup list is already sorted.

Literality answered 17/6, 2009 at 19:37 Comment(6)
Contains for List doesn't work for list of objects because it's comparing references.Geiger
Sorted data? Binary search - see @Mark's answer.Deandreadeane
HashtTable beats anything up to 2m items in my experienceIsosceles
As an aside, if your elements are in a meaningful order and are pretty evenly distributed, you can do a binary search much faster by having your first guesses be within an estimated range of your item. This may or may not have any meaning for your specific application.Lithoid
Don't forget about System.Collections.Generic.SortedList(TKey, TValue) if you want to simplify this stuff but avoid a hashset.Lithoid
I'm curious, why would you want to avoid a hashset?Knar
M
170

In the most general case, consider System.Collections.Generic.HashSet as your default "Contains" workhorse data structure, because it takes constant time to evaluate Contains.

The actual answer to "What is the fastest searchable collection" depends on your specific data size, ordered-ness, cost-of-hashing, and search frequency.

Musical answered 17/6, 2009 at 19:38 Comment(9)
Note: Do not forget to override the hashcode function. For added performance, pregenerate your hashcode in your constructor.Lithoid
@Brian: good point. I was assuming (baselessly) Record.Key was a builtin type of some kind.Musical
@Brian: instead of pregenerating I prefer to store the generated one the first time, why to slow down the constructor with something you don't know if it will be used?Mariahmariam
@jmservera: True, but storing the generated hash seems more complicated to me (though only slightly), and often has no meaningful performance impact. And I suspect it makes the gethackcode function slightly more expensive, though this is also negligible.Lithoid
Given the number of searches required, for this particular situation, Clemahieu's answer seems to be more efficient. If he plans on doing more lookups in the future, a HashSet is a wise option, as it will perform individual searches very efficiently.Echopraxia
@Matt Boehm: for this particular situation, it wasn't given that LargeCollection started out sorted.Musical
FYI: Performance test - I created a comparison between List<T> and HashSet<T> for strings. I found that HashSet was about 1000 times faster than List.Synergism
@Quango: 3 years later, but really if you don't specify the size of your data set this performance comparison means nothing: Hashsets have O(1) search, lists have O(n) search, so the performance ratio is proportional to n.Swanson
@Synergism you also need to specify the function. List<T>.Contains() is much slower than List<T>.BinarySearch()Scarlatina
T
78

If you don't need ordering, try HashSet<Record> (new to .Net 3.5)

If you do, use a List<Record> and call BinarySearch.

Tarsometatarsus answered 17/6, 2009 at 19:40 Comment(2)
Or, in .NET >= 4, use SortedSetLuce
Or better yet, ImmutableSortedSet from System.ImmutableCollectionsLooney
P
25

Have you considered List.BinarySearch(item)?

You said that your large collection is already sorted so this seems like the perfect opportunity? A hash would definitely be the fastest, but this brings about its own problems and requires a lot more overhead for storage.

Prolix answered 17/6, 2009 at 19:44 Comment(1)
You are right, a hash may bring some undesirable problems when using mutable objects as a key.Mariahmariam
G
13

You should read this blog that speed tested several different types of collections and methods for each using both single and multi-threaded techniques.

According to the results, a BinarySearch on a List and SortedList were the top performers constantly running neck-in-neck when looking up something as a "value".

When using a collection that allows for "keys", the Dictionary, ConcurrentDictionary, Hashset, and HashTables performed the best overall.

Glazunov answered 27/11, 2014 at 7:23 Comment(0)
I
10

I've put a test together:

  • First - 3 chars with all of the possible combinations of A-Z0-9
  • Fill each of the collections mentioned here with those strings
  • Finally - search and time each collection for a random string (same string for each collection).

This test simulates a lookup when there is guaranteed to be a result.

FullCollection

Then I changed the initial collection from all possible combinations to only 10,000 random 3 character combinations, this should induce a 1 in 4.6 hit rate of a random 3 char lookup, thus this is a test where there isn't guaranteed to be a result, and ran the test again:

PartialCollection

IMHO HashTable, although fastest, isn't always the most convenient; working with objects. But a HashSet is so close behind it's probably the one to recommend.

Just for fun (you know FUN) I ran with 1.68M rows (4 characters): BiggerCollection

Irrefrangible answered 3/11, 2021 at 20:24 Comment(0)
G
4

Keep both lists x and y in sorted order.

If x = y, do your action, if x < y, advance x, if y < x, advance y until either list is empty.

The run time of this intersection is proportional to min (size (x), size (y))

Don't run a .Contains () loop, this is proportional to x * y which is much worse.

Gan answered 17/6, 2009 at 20:10 Comment(3)
+1 for the more efficient algorithm. Even if the lists are currently unsorted, it would be more efficient to first sort them and then run this algorithm.Echopraxia
Wouldn't the runtime be proportional to max(size(x),size(y)) in the worst case scenario though? Example: int[] x = {99,100}; int[] y = {0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1};Echopraxia
No because once you complete the smaller set, you can append the remaining elements from the larger set because they are already sorted. I think this process is similar to Merge Sort.Literality
V
3

If it's possible to sort your items then there is a much faster way to do this then doing key lookups into a hashtable or b-tree. Though if you're items aren't sortable you can't really put them into a b-tree anyway.

Anyway, if sortable sort both lists then it's just a matter of walking the lookup list in order.

Walk lookup list
   While items in check list <= lookup list item
     if check list item = lookup list item do something
   Move to next lookup list item
Vu answered 17/6, 2009 at 19:51 Comment(1)
Yes, so true. If you have two sorted lists you only need to traverse each once.Rossini
L
3

If you're using .Net 3.5, you can make cleaner code using:

foreach (Record item in LookupCollection.Intersect(LargeCollection))
{
  //dostuff
}

I don't have .Net 3.5 here and so this is untested. It relies on an extension method. Not that LookupCollection.Intersect(LargeCollection) is probably not the same as LargeCollection.Intersect(LookupCollection) ... the latter is probably much slower.

This assumes LookupCollection is a HashSet

Lithoid answered 17/6, 2009 at 19:57 Comment(0)
P
2

If you aren't worried about squeaking every single last bit of performance the suggestion to use a HashSet or binary search is solid. Your datasets just aren't large enough that this is going to be a problem 99% of the time.

But if this just one of thousands of times you are going to do this and performance is critical (and proven to be unacceptable using HashSet/binary search), you could certainly write your own algorithm that walked the sorted lists doing comparisons as you went. Each list would be walked at most once and in the pathological cases wouldn't be bad (once you went this route you'd probably find that the comparison, assuming it's a string or other non-integral value, would be the real expense and that optimizing that would be the next step).

Prefect answered 17/6, 2009 at 19:48 Comment(0)
F
0

In case of .NET 8 you might also consider using System.Buffers.SearchValues<T>

https://learn.microsoft.com/en-us/dotnet/api/system.buffers.searchvalues-1?view=net-8.0

and also for .NET 8 you might have a look at System.Collections.Frozen.FrozenSet<T> or FrozenDictionary<TKey,TValue>:

https://learn.microsoft.com/en-us/dotnet/api/system.collections.frozen.frozenset-1?view=net-8.0

Find answered 12/2 at 15:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.