【问题标题】:Binary Search for multiple items within a Range (Log time filter)对范围内的多个项目进行二分搜索(日志时间过滤器)
【发布时间】:2021-06-21 16:10:59
【问题描述】:

我有一个日志项数组,已按其时间戳(自 1970 年以来的毫秒数)排序。现在我想按特定时间范围过滤它们,所以我想到了二进制搜索,但是这个变体与我之前知道的所有变体不同,因为我需要在一个范围内找到一个范围。请注意,值边缘可能没有或有多个项目。

我想出了这个来减少一个范围要求,但仍然不知道如何到达第一个/最后一个边缘项目:

    filterByTime(min: number, max: number): LogItem[] {
        const items = this.items;
        const len = items.length;

        if (min === undefined && max === undefined) {
            return items.slice(0, len - 1);
        }
        min = min || Number.MIN_VALUE;
        max = max || Number.MAX_VALUE;

        const fn = (a: LogItem) => {
            if (a.time < min) {
                return -1;
            } else if (a.time > max) {
                return 1;
            } else {
                return 0;
            }
        };

        const lowerBound = this.binarySearchBound(fn, 0, len, true);
        if (lowerBound == -1) { return []; }

        const upperBound = this.binarySearchBound(fn, 0, len, false);

        return items.slice(lowerBound, upperBound + 1);
    }

    binarySearchBound(compareFn: (a: LogItem) => number, left: number, right: number, isLowerBound: boolean): number {
        const items = this.items;
        if (items.length == 0) {
            return -1;
        }

        if (isLowerBound && compareFn(items[0]) == 0) {
            return 0;
        } else if (!isLowerBound && compareFn(items[items.length - 1]) == 0) {
            return items.length - 1;
        }

        while (left <= right) {
            const mid = (left + right) / 2;
            const item = this.items[mid];

            const compare = compareFn(item);
            if (compare < 0) {
                left = mid + 1;
            } else if (compare > 0) {
                right = mid - 1;
            } else {
                // What to do now?
            }
        }

        return -1;
    }

最坏的情况,我可以从边缘进行线性搜索,因为我可以假设边缘没有那么多项,但肯定有更好的方法我没有想到但是如果 mid 位于结果集的中间,我可能不得不遍历整个结果集。

添加注释的编辑:min 或 max 可能是 undefined(也可以是两者,在这种情况下,我可以设置一个 if 并返回整个数组的副本)。如果 MIN_VALUE 和 MAX_VALUE 是 undefined,则将其替换为更好,还是有更好的方法来处理这种情况?

【问题讨论】:

    标签: javascript typescript algorithm binary-search


    【解决方案1】:

    我建议如下:

    • 编写两个二分搜索函数,因为传递和检查 isLowerBound 布尔值不会妨碍执行时间。

    • 使返回的upperBound 表示属于该范围的潜在最后一个索引之后的下一个 索引。这与参数如何与 slice 等本机函数一起使用。

    • 不要使用 -1 作为特殊索引。如果编码得当,两次二分查找都会产生一个空范围,并给出一个空数组作为结果

    • 使比较函数使用 2 个参数,因此您实际上可以搜索 min 或 max 值。

    • 是的,我会使用MIN_VALUE 和MAX_VALUE 作为默认值,而不是测试边界情况。如果边界情况经常发生,则可能值得包含这些检查,但通常请注意,这些检查随后将针对每个过滤器执行,这可能会降低平均执行时间。

    为了简单起见,这里建议使用整数数据(而不是对象)实现。为了让它在 sn-p 中运行,我还删除了类型引用:

    function filterByTime(min=Number.MIN_VALUE, max=Number.MAX_VALUE) {
        const fn = (a, b) => a - b; // simplified (should be a.time - b.time)
    
        const lowerBound = this.binarySearchLowerBound(fn, 0, this.items.length, min);
        const upperBound = this.binarySearchUpperBound(fn, lowerBound, this.items.length, max);
    
        return this.items.slice(lowerBound, upperBound);
    }
    
    function binarySearchLowerBound(compareFn, left, right, target) {
        while (left < right) {
            const mid = (left + right) >> 1;
            if (compareFn(this.items[mid], target) < 0) {
                left = mid + 1;
            } else { // Also when equal...
                right = mid;
            }
        }
        return left;
    }
    
    function binarySearchUpperBound(compareFn, left, right, target) {
        while (left < right) {
            const mid = (left + right) >> 1;
            if (compareFn(this.items[mid], target) <= 0) { // Also when equal...
                left = mid + 1;
            } else {
                right = mid;
            }
        }
        return left;
    }
    
    // Demo with simplified data (instead of objects with time property)
    this.items = [1, 2, 2, 2, 3, 4, 4, 5, 5, 5, 6, 7, 8, 8];
    console.log(this.filterByTime(2, 4));
    console.log(this.filterByTime(4, 5));

    【讨论】:

    • 谢谢!通常我不喜欢重复代码,但有了这个代码会好很多。我也喜欢&gt;&gt; 1 的把戏。
    • 是的,我同意。重复代码不是一个好习惯,但是当速度很重要时,比如二分搜索,我会在此破例。
    【解决方案2】:

    结合变体on this article,我将first和last代码合并为一个函数:

        filterByTime(items: LogItem[], min: number, max: number): LogItem[] {
            const len = items.length;
    
            if (len == 0) {
                return [];
            }
    
            if (min === undefined && max === undefined) {
                return items.slice(0, len - 1);
            }
            min = min || Number.MIN_VALUE;
            max = max || Number.MAX_VALUE;
    
            const fn = (a: LogItem) => {
                if (a.time < min) {
                    return -1;
                } else if (a.time > max) {
                    return 1;
                } else {
                    return 0;
                }
            };
    
            const lowerBound = this.binarySearchBound(fn, 0, len, true);
            if (lowerBound == -1) { return []; }
    
            const upperBound = this.binarySearchBound(fn, 0, len, false);
    
            return items.slice(lowerBound, upperBound + 1);
        }
    
    
        binarySearchBound(compareFn: (a: LogItem) => number, left: number, right: number, isLowerBound: boolean): number {
            const items = this.items;
            if (items.length == 0) {
                return -1;
            }
    
            if (isLowerBound && compareFn(items[0]) == 0) {
                return 0;
            } else if (!isLowerBound && compareFn(items[items.length - 1]) == 0) {
                return items.length - 1;
            }
    
            let result = -1;
            while (left <= right) {
                const mid = (left + right) / 2;
                const item = this.items[mid];
    
                const compare = compareFn(item);
                if (compare < 0) {
                    left = mid + 1;
                } else if (compare > 0) {
                    right = mid - 1;
                } else {
                    result = mid;
    
                    if (isLowerBound) {
                        right = mid - 1;
                    } else {
                        left = mid + 1;
                    }
                }
            }
    
            return result;
        }
    
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-06-22
      • 1970-01-01
      • 1970-01-01
      • 2014-03-02
      • 2015-08-28
      • 1970-01-01
      相关资源
      最近更新 更多