LINQ into SortedList

时间:2010-05-24 14:36:17

标签: c# linq sortedlist sorteddictionary

我是一个完整的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坏了吗?我期待太多了吗?我应该自己编写二进制搜索功能吗?

6 个答案:

答案 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之上管理自己的搜索。