【问题标题】:Fast Int Range Lookup in Multidimensional Array?多维数组中的快速整数范围查找?
【发布时间】:2014-10-27 11:13:46
【问题描述】:

我正在尝试设计一种方法 (.NET 4.5.2) 来非常快速地确定 int 是否在数字范围内。范围不重叠。速度是这种全内存操作的第一要务。 下面的代码工作正常,但实际系统将有 500,000 行来自数据库,我担心在数组中间查找范围命中会导致性能损失。从数据库中读取数据后,它会保留在内存中并用作 Web 应用程序中的参考数据。

感谢所有想法。感谢 https://stackoverflow.com/a/5612589/139618 提供的 Filter 方法。

// Running console app correctly shows "2288779".

static void Main( string[] args )
{
    int[,] intervals = new int[3, 3];
    intervals[0, 0] = 200;
    intervals[0, 1] = 250;
    intervals[0, 2] = 1121214;
    intervals[1, 0] = 300;
    intervals[1, 1] = 350;
    intervals[1, 2] = 2288779;
    intervals[2, 0] = 400;
    intervals[2, 1] = 450;
    intervals[2, 2] = 3300004;
    var seekIntA = 336;
    var result = Filter(intervals, u => u[0] <= seekIntA && u[1] >= seekIntA).FirstOrDefault();
    if (null != result)
    {
        Console.WriteLine(result[2]);
    }
    else
    {
        Console.WriteLine("null");
    }
}

public static IEnumerable<T[]> Filter<T>( T[,] source , Func<T[] , bool> predicate )
{
    for ( int i = 0 ; i < source.GetLength( 0 ) ; ++i )
    {
        T[] values = new T[source.GetLength( 1 )];
        for ( int j = 0 ; j < values.Length ; ++j )
        {
            values[j] = source[i , j];
        }
        if ( predicate( values ) )
        {
            yield return values;
        }
    }
}

我愿意完全放弃数组的想法,并使用任何其他集合(有意小写)类型来存储/查找范围。

谢谢。

【问题讨论】:

  • 当你有一个看起来像 n*3 的数组时,不确定你是什么意思“在数字范围内”... 3 更像 {rangeBoundary, Data1, Data2} 或者不知何故“范围”被定义为超过2个值? (如果只是一维搜索,那么对于一般情况,二分搜索是最快的解决方案)。
  • @Alexi,区间 [0,0] 包含一个范围的下限,区间 [0,1] 包含上限,区间 [0,2] 包含任何范围的结果值包含在包含范围内的数字。
  • 我明白了。旁注:您为什么选择使用数组而不是公共示例代码? class/struct 具有 2 个值(即如 Assafss 答案中所示)将更具可读性和自我记录性,同时如果您确实需要它仍然很容易转换回数组。
  • 可能过度思考使用原始类型会比类(甚至是结构)更快且内存效率更高的风险。我需要它尽可能快并且使用最少的内存,因为它会在负载下被非常非常多的同时请求读取。

标签: c# arrays performance linq multidimensional-array


【解决方案1】:

如果看起来您的范围是一致的,您可以在 O(1) 时间和内存中计算范围。 对于更通用但更复杂的解决方案:

class Range
{
    public int min { get; private set; }
    public int max { get; private set; }

    public Range(int min, int max) {
        this.min = min;
        this.max = max;
    }
}

class MinComparer : IComparer<Range>
{
    public int Compare(Range x, Range y) {
        return (x.min - y.min);
    }
}

class MaxComparer : IComparer<Range>
{
    public int Compare(Range x, Range y) {
        return (x.max - y.max);
    }
}

class Ranges
{
    private List<Range> rangesMin;
    private List<Range> rangesMax;

    private IComparer<Range> minComparer;
    private IComparer<Range> maxComparer;

    public Ranges() {
        minComparer = new MinComparer();
        maxComparer = new MaxComparer();

        rangesMin = getRanges();
        rangesMax = new List<Range>(rangesMin);

        rangesMin.Sort(minComparer);
        rangesMax.Sort(maxComparer);
    }

    public IEnumerable<Range> getSetOfPossibleRanges(int numberToSeek) {
        Range rangeToSeek = new Range(numberToSeek, numberToSeek);
        int indexMin = rangesMin.BinarySearch(rangeToSeek, minComparer);
        int indexMax = rangesMax.BinarySearch(rangeToSeek, maxComparer);

        if(indexMin < 0) {
            indexMin = ~indexMin;
        }

        if(indexMax < 0) {
            indexMax = ~indexMax;
        }

        List<Range> subMin = rangesMin.GetRange(0, indexMin);
        List<Range> subMax = rangesMax.GetRange(indexMax, rangesMax.Count - indexMax);

        return subMin.Intersect(subMax);
    }

    private List<Range> getRanges() { //get ranges from DB here }
}

我使用了两个列表,一个按范围的下限排序,另一个按上限排序。 所有具有查找数字的范围都是这些列表的子集的交集,其中数字大于最小排序列表中的下限,并且小于最大排序列表中的上限。

Ranges 只应在应用程序启动时进行初始化(在初始化时进行昂贵的排序操作)。

我针对与您的代码类似的解决方案对此进行了测试,发现它要快得多(使用 1M 随机范围进行测试)。

【讨论】:

    猜你喜欢
    • 2020-08-14
    • 1970-01-01
    • 2014-01-16
    • 2010-11-14
    • 1970-01-01
    • 1970-01-01
    • 2014-05-04
    • 2014-03-18
    • 1970-01-01
    相关资源
    最近更新 更多