繁体   English   中英

二进制搜索范围内的多个项目(日志时间过滤器)

[英]Binary Search for multiple items within a Range (Log time filter)

我有一个日志项目数组,已经按时间戳(自 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 落在结果集的中间。

编辑以添加注释: minmax可能undefined (也可能两者都是,在这种情况下我可以设置一个if并返回整个数组的副本)。 如果MIN_VALUEMAX_VALUEundefined ,是不是更好,还是有更好的方法来处理这种情况?

我建议如下:

  • 编写两个二进制搜索函数,因为执行时间不会因传递和检查isLowerBound boolean 而受到阻碍。

  • 使返回的upperBound表示属于该范围的潜在最后一个索引之后的下一个索引。 这对应于 arguments 如何与slice等原生函数一起工作。

  • 不要将 -1 用作特殊索引。 如果编码得当,一个空范围将以任何方式从两个二进制搜索中出来,并给出一个空数组作为结果

  • 使比较 function 与 2 个参数一起工作,因此您实际上可以搜索minmax

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

这是使用 integer 数据(而不是对象)的建议实现,以保持简单。 为了让它在片段中运行,我还删除了类型引用:

 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));

结合本文的变体,我将firstlast代码合并为一个 function:

    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;
    }

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM