我是一个完整的LINQ新手,所以我不知道我的LINQ是否不正确我需要做什么,或者我对性能的期望是否过高。
我有一个对象的SortedList,用int键入; SortedList与SortedDictionary相对,因为我将使用预先排序的数据填充集合。我的任务是找到确切的密钥,或者,如果没有确切的密钥,则找到具有下一个更高值的密钥。如果列表的搜索太高(例如,最高键为100,但搜索105),则返回null。
// The structure of this class is unimportant. Just using
// it as an illustration.
public class CX
{
public int KEY;
public DateTime DT;
}
static CX getItem(int i, SortedList<int, CX> list)
{
var items =
(from kv in list
where kv.Key >= i
select kv.Key);
if (items.Any())
{
return list[items.Min()];
}
return null;
}
给定50,000条记录的列表,调用getItem 500次需要大约一秒半。拨打50,000次电话需要2分钟。这种表现似乎很差。我的LINQ坏了吗?我期待太多了吗?我应该自己编写二进制搜索功能吗?
答案 0 :(得分:7)
首先,您的查询被评估两次(一次用于Any
,一次用于Min
)。其次,Min
要求它遍历整个列表,即使它排序的事实意味着第一个项目将是最小值。你应该可以改变这个:
if (items.Any())
{
return list[items.Min()];
}
对此:
var default =
(from kv in list
where kv.Key >= i
select (int?)kv.Key).FirstOrDefault();
if(default != null) return list[default.Value];
return null;
<强>更新强>
因为您正在选择值类型,FirstOrDefault
不会返回可为空的对象。我已更改您的查询以将所选值转换为int?
,从而允许检查结果值null
。我建议使用ContainsKey
,因为如果您的列表包含true
的值,则返回0
。例如,假设您有以下值
0 2 4 6 8
如果您传入的内容少于或等于8,那么您将得到正确的值。但是,如果您传入9,则会得到0(default(int)
),其中 在列表中,但不是有效结果。< / p>
答案 1 :(得分:5)
自己编写二进制搜索可能很困难。
幸运的是,微软已经写了一篇相当强大的文章:Array.BinarySearch<T>
。 This is, in fact, the method that SortedList<TKey, TValue>.IndexOfKey
uses internally。唯一的问题是,它需要T[]
个参数,而不是任何IList<T>
(如SortedList<TKey, TValue>.Keys
)。
你知道吗?这个名为Reflector的好工具可以让你看看.NET源代码......
检查出来:BinarySearch
上的通用IList<T>
扩展方法,直接来自Microsoft Array.BinarySearch<T>
实施的反映代码。
public static int BinarySearch<T>(this IList<T> list, int index, int length, T value, IComparer<T> comparer) {
if (list == null)
throw new ArgumentNullException("list");
else if (index < 0 || length < 0)
throw new ArgumentOutOfRangeException((index < 0) ? "index" : "length");
else if (list.Count - index < length)
throw new ArgumentException();
int lower = index;
int upper = (index + length) - 1;
while (lower <= upper) {
int adjustedIndex = lower + ((upper - lower) >> 1);
int comparison = comparer.Compare(list[adjustedIndex], value);
if (comparison == 0)
return adjustedIndex;
else if (comparison < 0)
lower = adjustedIndex + 1;
else
upper = adjustedIndex - 1;
}
return ~lower;
}
public static int BinarySearch<T>(this IList<T> list, T value, IComparer<T> comparer) {
return list.BinarySearch(0, list.Count, value, comparer);
}
public static int BinarySearch<T>(this IList<T> list, T value) where T : IComparable<T> {
return list.BinarySearch(value, Comparer<T>.Default);
}
这将允许您调用list.Keys.BinarySearch
并获得所需索引的负位补码,以防找不到所需的键(以下内容基本上直接取自tzaman的答案):
int index = list.Keys.BinarySearch(i);
if (index < 0)
index = ~index;
var item = index < list.Count ? list[list.Keys[index]] : null;
return item;
答案 2 :(得分:3)
在SortedList
上使用LINQ不会给您带来排序的好处。
为获得最佳性能,您应该编写自己的二分查找。
答案 3 :(得分:2)
好的,只是为了给这个更多的可见性 - 这是Adam Robinson的答案的更简洁版本:
return list.FirstOrDefault(kv => kv.Key >= i).Value;
FirstOrDefault
函数有一个重载,它接受一个谓词,它选择满足条件的第一个元素 - 你可以用它来直接获取你想要的元素,如果它不是null
存在。
答案 4 :(得分:1)
为什么不使用BinarySearch
类中内置的List
?
var keys = list.Keys.ToList();
int index = keys.BinarySearch(i);
if (index < 0)
index = ~index;
var item = index < keys.Count ? list[keys[index]] : null;
return item;
如果搜索目标不在列表中,BinarySearch
将返回下一个更高项的逐位补码;如果它是负面的,我们可以通过重新补充结果来直接获得你想要的东西。如果它等于Count
,则您的搜索键大于列表中的任何内容。
这应该比执行LINQ
正如评论所指出的那样,where
快得多,因为它已经排序了...... ToList
调用将强制对整个列表进行评估,因此只有在不更改基础SortedList
的情况下进行多次搜索并且保留keys
时,这才有用。分别列出。
答案 5 :(得分:0)
在PowerCollections中使用OrderedDictionary,你可以得到一个枚举器,它在你正在寻找的键所在的位置开始......如果它不在那里,你将得到下一个最近的节点,然后可以从O中向前/向后导航(记录N)每次导航呼叫的时间。
这样做的好处是您不必编写自己的搜索,甚至可以在SortedList之上管理自己的搜索。