I see that you order the list just to take 5 items. .OrderBy(s => s).Take(5)
. This is an O(n*log(n)) operation. Instead you can find the top most N items in an O(n) time.
Here is an extension method TakeOrdered
( http://pastebin.com/NHDdrbYV ) I wrote some time ago just for this purpose .
return Json(MyModel.GetProducts()
.Where(s => s.ToUpper().StartsWith(term.ToUpper()))
.TakeOrdered(5,s=>s)
.ToList<string>()
);
you can compare the performance results with a function like below
void PerformanceTest()
{
Stopwatch sw = new Stopwatch();
int N = 1000000;
int M = 10;
//JIT - Warm up
var seq1 = RandomSequence().Take(10).OrderBy(x => x).Take(M).ToArray();
var seq2 = RandomSequence().Take(10).TakeOrdered(M).ToArray();
sw.Start();
seq1 = RandomSequence().Take(N).OrderBy(x => x).Take(M).ToArray();
long t1 = sw.ElapsedMilliseconds;
sw.Restart();
seq2 = RandomSequence().Take(N).TakeOrdered(M).ToArray();
long t2 = sw.ElapsedMilliseconds;
for (int i = 0; i < seq1.Length; i++) Debug.Assert(seq1[i] == seq2[i]);
Console.WriteLine(t1 + " " + t2);
}
public IEnumerable<int> RandomSequence()
{
Random rnd = new Random(0);
while (true)
yield return rnd.Next();
}
N |.OrderBy.Take .TakeOrdered (in ms.)
-----+---------------------------
100K | 65 23
600K | 578 131
1M | 1110 224
10M | 16540 2243
And since It doesn't require all items to be kept in memory(for sorting), it consumes much less RAM
PS:
TakeOrdered
utilizes PriorityQueue of Lucene.Net internally
http://lucene.apache.org/core/old_versioned_docs/versions/3_0_2/api/all/org/apache/lucene/util/PriorityQueue.html
Here is more explanation:
How can I use Lucene's PriorityQueue when I don't know the max size at create time?
@mast, Updated after 10 years; a generation may have missed my point :).
I still believe that minor improvements as in accepted answer doesn't solve the real problem
Suppose op wants to find the top 1 item in the list. Would you sort it first? No. A simple pass over the array to find the max/min would be enough.
For 2?
No. You would extend your code to compare the temp max/min with the current values...
So If we continue with that approach, instead of sorting the whole array (in memory), storing top N items in a sorted smaller array would be more feasable.
A poor performance test code for TakeTopN
void TestTopN()
{
Random rnd = new Random();
var array = Enumerable.Range(0, 1000 * 1000).Select(_ => rnd.Next()).ToArray();
for (int j = 0; j < 10; j++)
{
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < 10; i++)
{
var top = array.OrderBy(x => x).Take(5).ToArray();
}
var t1 = sw.ElapsedMilliseconds;
sw.Restart();
for (int i = 0; i < 10; i++)
{
var top = array.TakeTopN(5, x => x).ToArray();
}
var t2 = sw.ElapsedMilliseconds;
Console.WriteLine(t1 + " " + t2);
}
}
results on my machine
2512 108
2454 108
2390 105
2393 106
2433 107
2476 107
2373 107
2261 104
2202 102
2188 106
And the Oscar goes to .....
//similar to Lucene's PriorityQueue
using System;
using System.Collections.Generic;
using System.Linq;
namespace WindowsFormsApp1 //YOUR PROJECT'S NAMESPACE
{
public static class LinqExtensions
{
public static IEnumerable<T> TakeTopN<T, TKey>(this IEnumerable<T> list, int n, Func<T, TKey> keySelector, bool ascending = true) where TKey : IComparable<TKey>
{
IComparer<TKey> comparer = Comparer<TKey>.Default;
if (ascending == false) comparer = new ReverseComparer<TKey>(comparer);
List<T> values = new List<T>(n + 1);
List<TKey> keys = new List<TKey>(n + 1);
TKey max = keySelector(list.First());
foreach (var item in list)
{
var key = keySelector(item);
if (keys.Count < n)
{
int index = FindIndex<TKey>(keys, key, comparer);
keys.Insert(index, key);
values.Insert(index, item);
max = keySelector(values[values.Count - 1]);
continue;
}
if (comparer.Compare(key, max) < 0)
{
int index = FindIndex<TKey>(keys, key, comparer);
keys.Insert(index, key);
values.Insert(index, item);
if (keys.Count > n)
{
keys.RemoveAt(n);
values.RemoveAt(n);
}
max = keySelector(values[values.Count - 1]);
}
}
return values;
}
//Needed for stable sort...
private static int FindIndex<TKey>(List<TKey> keys, TKey key, IComparer<TKey> comparer) where TKey : IComparable<TKey>
{
int index = keys.BinarySearch(key, comparer);
if (index < 0) index = ~index;
while (index < keys.Count && comparer.Compare(keys[index], key) == 0) index++;
return index;
}
class ReverseComparer<T> : IComparer<T>
{
IComparer<T> _Comparer;
public ReverseComparer(IComparer<T> comparer)
{
_Comparer = comparer;
}
public int Compare(T x, T y)
{
return _Comparer.Compare(y, x);
}
}
}
}
List<T>
type that you are using as backend is not thread safe? You should really take that into account when attempting to use it in a multi-threaded environment such as ASP.NET. \$\endgroup\$return GetProducts();
with the (also-repeat, might want to DRY it out)cached = HttpContext.Current.Cache["MyApp-Products"];
. \$\endgroup\$