什么.NET字典支持“查找最近的键”操作?

时间:2009-11-06 22:29:17

标签: .net dictionary key lower-bound

我正在将一些C ++代码转换为C#,并调用std :: map :: lower_bound(k)来查找映射中的键,该键的大小等于或大于k。但是,我没有看到任何方法与.NET的SortedDictionary做同样的事情。我怀疑我可以使用SortedList实现变通方法,但遗憾的是SortedList太慢(O(n)用于插入和删除键)。我该怎么办?

注意:我发现使用的解决方法利用了我的特定场景......具体来说,我的键是一个密集的整数,从0开始,所以我使用了List< TValue>作为我的字典,其中列表索引用作键,并且搜索等于或大于k的键只能在几次循环迭代中完成。但是看到原来的问题得到解答仍然会很好。

8 个答案:

答案 0 :(得分:2)

花了几个月的工作,但最后我可以提供至少部分解决这个问题的方法......我称之为Compact Patricia Trie,一个提供“查找下一个更大的键”操作的排序字典。

http://www.codeproject.com/KB/recipes/cptrie.aspx

这只是部分解决方案,因为只支持某些类型的密钥,即byte[]string和所有原始整数类型(Int8..UInt64)。此外,字符串排序区分大小写。

答案 1 :(得分:1)

问题是字典/哈希表被设计为根据输入值到达唯一的内存位置,因此您需要一个数据结构,该结构旨在容纳与您存储的每个值相关的范围,以及同时正确更新每个间隔

我认为skip lists(或平衡二叉树)可以帮助你。虽然它们不能在O(n)中执行查找,但它们可以以对数方式进行,并且仍然比树木更快。

我知道这不是一个正确的答案,因为我不能说.NET BCL已经包含这样一个类,不幸的是你必须自己实现一个,或找到一个支持它的第三方程序集。不过,The CodeProject here似乎有一个很好的例子。

答案 2 :(得分:1)

您可以尝试我在下面编写的代码。它使用二进制搜索,因此假设列表/数组已预先排序。

public static class ListExtensions
{
    public static int GetAtMostIndex<TItem, TValue>(/*this*/ IList<TItem> list, TValue value, Func<TItem, TValue, int> comparer)
    {
        return GetAtMostIndex(list, value, comparer, 0, list.Count);
    }

    public static int GetAtLeastIndex<TItem, TValue>(/*this*/ IList<TItem> list, TValue value, Func<TItem, TValue, int> comparer)
    {
        return GetAtLeastIndex(list, value, comparer, 0, list.Count);
    }

    public static int GetAtMostIndex<TItem, TValue>(/*this*/ IList<TItem> list, TValue value, Func<TItem, TValue, int> comparer, int index, int count)
    {
        if (count == 0)
        {
            return -1;
        }

        int startIndex = index;
        int endIndex = index + count - 1;
        int middleIndex = 0;
        int compareResult = -1;

        while (startIndex < endIndex)
        {
            middleIndex = (startIndex + endIndex) >> 1; //  / 2
            compareResult = comparer.Invoke(list[middleIndex], value);

            if (compareResult > 0)
            {
                endIndex = middleIndex - 1;
            }
            else if (compareResult < 0)
            {
                startIndex = middleIndex + 1;
            }
            else
            {
                return middleIndex;
            }
        }

        if (startIndex == endIndex)
        {
            compareResult = comparer.Invoke(list[startIndex], value);

            if (compareResult <= 0)
            {
                return startIndex;
            }
            else
            {
                int returnIndex = startIndex - 1;

                if (returnIndex < index)
                {
                    return -1;
                }
                else
                {
                    return returnIndex;
                }
            }
        }
        else
        {
            //todo: test
            return startIndex - 1;
        }
    }

    public static int GetAtLeastIndex<TItem, TValue>(/*this*/ IList<TItem> list, TValue value, Func<TItem, TValue, int> comparer, int index, int count)
    {
        if (count == 0)
        {
            return -1;
        }

        int startIndex = index;
        int endIndex = index + count - 1;
        int middleIndex = 0;
        int compareResult = -1;

        while (startIndex < endIndex)
        {
            middleIndex = (startIndex + endIndex) >> 1; //  / 2
            compareResult = comparer.Invoke(list[middleIndex], value);

            if (compareResult > 0)
            {
                endIndex = middleIndex - 1;
            }
            else if (compareResult < 0)
            {
                startIndex = middleIndex + 1;
            }
            else
            {
                return middleIndex;
            }
        }

        if (startIndex == endIndex)
        {
            compareResult = comparer.Invoke(list[startIndex], value);

            if (compareResult >= 0)
            {
                return startIndex;
            }
            else
            {
                int returnIndex = startIndex + 1;

                if (returnIndex >= index + count)
                {
                    return -1;
                }
                else
                {
                    return returnIndex;
                }
            }
        }
        else
        {
            return endIndex + 1;
        }
    }
}

答案 3 :(得分:1)

我创建了三个与B +树相关的数据结构,为任何数据类型提供此功能:BList<T>, BDictionary<K,V> and BMultiMap<K,V>。这些数据结构中的每一个都提供了{C} FindLowerBound()FindUpperBound()的{​​{1}}和lower_bound方法。

答案 4 :(得分:0)

找到离K最近的地方:

dict.Keys.Where(i => i >= K).OrderBy(i => i).First();

或更快:

public int? GetNearestKey(dict, K) 
{
    int? lowerK = null;
    foreach (int key in dict.Keys)
    {
        if (key == K) 
        {
            lowerK = K;
            break; 
        }
        else if (key >= K && (!lowerK.HasValue || key < lowerK))
        {
            lowerK = key;
        }
    }
    return lowerK;
}

答案 5 :(得分:0)

基础框架中没有二进制搜索树集合实现,因此您必须构建一个或查找实现。如您所述,SortedList在搜索方面最接近,但由于插入/删除的速度较慢(由于其底层数组实现)。

答案 6 :(得分:0)

我认为关于SortedList复杂性的问题存在错误。

SortedList 具有inserting新项目的O(log(n))摊销复杂度。如果您事先知道在最坏的情况下可以在O(Log(n))中完成容量。

答案 7 :(得分:0)

您可以使用以下扩展方法为SortedSet<T>执行此操作:

public static class SortedSetExtensions
{
    public static bool FindLowerOrEqualThan<T>(this SortedSet<T> set, T value, out T first)
    {
        if(set.Count == 0)
        {
            first = default(T);
            return false;
        }

        var minimum = set.Min;

        if(set.Comparer.Compare(minimum, value) > 0)
        {
            first = default(T);
            return false;
        }

        first = set.GetViewBetween(minimum, value).Max;
        return true;
    }

    public static bool FindGreaterOrEqualThan<T>(this SortedSet<T> set, T value, out T first)
    {
        if (set.Count == 0)
        {
            first = default(T);
            return false;
        }

        var maximum = set.Max;

        if (set.Comparer.Compare(maximum, value) < 0)
        {
            first = default(T);
            return false;
        }

        first = set.GetViewBetween(value, maximum).Min;
        return true;
    }
}