如何确定Javascript中项目网格中的选择范围之间的重叠

问题描述 投票:2回答:2

想象一下像物品网格。用户可以在网格中选择多个项目范围。当存在多个范围(选择)时,我想确定最后创建的范围是否与现有范围重叠。可以将网格系统与文本区域中的字符进行比较,其中可以突出显示多个文本范围。

  • 每个单一范围始终存在相邻项目,彼此相邻或网格中下一行中的第一项。 (也与在文本文档中选择文本完全相似。)
  • 当用户创建范围时,它可以与现有范围重叠,甚至完全适合现有范围。

范围存储在内存中:

{
    'rangeStartLineIndex': 2,
    'rangeStartColIndex': 9,
    'rangeEndLineIndex': 4,
    'rangeEndColIndex': 7
}

上面的范围可以像图像一样可视化。但请注意,网格的行数和列数不是恒定的。

enter image description here

目标是遍历现有范围,并查看刚刚创建的范围是否与现有范围重叠(或完全适合)。如果是,则获取该现有范围,并对其进行扩展,以使创建的范围与其重叠的范围合并。所以,这是一种规范化数据。

代码中的另一个例子:

var ranges = []; // stores the range objects that are created earlier.
var createdRange = {...}; // range object just created.

for(var i = 0; i < ranges; i++) {
    var overlap = doesThisOverlap(createdRange, ranges[i]);

    if(overlap) {

        // overlaps, which means we extend the existing range.
        range[i].rangeStartLineIndex = Math.min(range[i].rangeStartLineIndex, createdRange.rangeStartLineIndex);
        range[i].rangeStartColIndex = Math.min(range[i].rangeStartColIndex, createdRange.rangeStartColIndex);
        range[i].rangeEndLineIndex = Math.max(range[i].rangeEndLineIndex, createdRange.rangeEndLineIndex);
        range[i].rangeEndColIndex = Math.max(range[i].rangeEndColIndex, createdRange.rangeEndColIndex);

    } else {
        // means the new range does not extend an existing range, so add it.
        ranges.push(createdRange);
    }
}

function doesThisOverlap(rangeA, rangeB) {
    // ???
}

当试图实现doesThisOverlap函数时,我最终得到了过多的if-blocks。我感到困惑,也因为我觉得有一种算法可以找到。

我还尝试了添加一个范围起点的线和col索引来给它一个'得分',(并对它的端点的行和列索引做同样的事情)。然后比较范围之间的起点/终点分数。

javascript algorithm range normalization
2个回答
2
投票

这个问题实际上不是2D,如果你将范围表示为,则会变得容易得多

{
  rangeStart: 29,
  rangeEnd:48
}

您可以通过计算转换为此表示形式

lineIndex * COLUMN_NUMBER + columnIndex.

你应该基本上迭代所有范围并找到min rangeStart和rangeEnd。然后,您可以使用以下结果将结果转换为列/行:

columnIndex = x % COLUMN_NUMBER;
lineIndex = parseInt(x / COLUMN_NUMBER).

1
投票

确定createdRange是否与其中一个ranges重叠的方法之一是给每个range起始指数和结束指数,然后检查createdRange的指数是否与任何其他范围的指数重叠。

首先,让我们从更好的可读性改变range对象的形状:

{
    start: { row: 2, col: 9 },
    end: { row: 4, col: 7 }
}

range对象与您定义的对象映射很简单:

rangeStartLineIndex => start.row
rangeStartColIndex  => start.col
rangeEndLineIndex   => end.row
rangeEndColIndex    => end.col

有了这个,我首先会指出逻辑中的一个小错误。 在for循环中,您正在检查createdRange是否与当前的range重叠。如果没有,则将createdRange添加到范围数组中。 但是,你只需要在createdRange中添加ranges如果没有一个范围与createdRange重叠

因此,正确的for循环看起来像:

var hasOverlap = false; // this will tell if any of the ranges overlap

for(var i = 0; i < ranges; i++) {
    var overlap = doesThisOverlap(createdRange, ranges[i]);

    if(overlap) {
        // overlaps, which means we extend the existing range.
        // some logic to update the overlapped ranges
        hasOverlap = true; // a range has overlapped, set the flag to true
        break;

    }
}
// Did we see any overlap?
if(!hasOverlap) {
    // no we did not, let us add this range to ranges array
    // means the new range does not extend an existing range, so add it.
    ranges.push(createdRange);
}

好的,现在让我们看看如何计算给定范围的指数。 如果我们开始在网格中从左到右分配索引(从0开始),简单的数学表示行rc列中的框的索引将是:

index = r * (COL + 1) + c [COL is the total number of columns in the grid]

以下是辅助函数,它们有助于计算给定范围的索引:

function getIndex(row, col, COL) {
    return row * (COL + 1) + col;
}

function getIndices(range) {
    var start = range.start;
    var end = range.end;

    var startIndex = getIndex(start.row, start.col, COLS);
    var endIndex = getIndex(end.row, end.col, COLS);

    return { start: startIndex, end: endIndex };
}

请注意,getIndices采用range并输出一个startend指数的对象。我们现在可以计算createdRange和当前range的指数。并且基于指数,我们将知道范围是否重叠。


问题现在归结为: 我们有一条AB线,给定一条新线PQ,找出新线PQ是否与AB重叠。 (其中A,B,P,Q是数字线上的点,A <B和P <Q)。 拿笔和纸画几行。你会发现线条不会重叠时只有两种情况:

Q <A或B <P

将这些观察结果映射到我们的range对象,我们可以说:

P => createdRange.startIndex
Q => createdRange.endIndex

A => currentRange.startIndex
B => currentRange.endIndex

这就是它在代码中的样子:

var createdRangeIndices = getIndices(createdRange);
var hasOverlap = false;

for (var i = 0; i < ranges.length; i++) {
    var currentRangeIndices = getIndices(ranges[i]);
    var overlap = (createdRangeIndices.end < currentRangeIndices.start) 
                    || (currentRangeIndices.end < createdRangeIndices.start);
    if (!overlap) {
        // overlaps, which means we extend the existing range.
        // some logic to update the overlapped ranges
        hasOverlap = true;
        break;
    }
}

if (!hasOverlap) {
    // means the new range does not extend an existing range, so add it.
    ranges.push(createdRange);
}

请注意,我们摆脱了doesThisOverlap功能。一个简单的旗帜就可以了。

如果存在重叠,现在剩下的就是更新range的逻辑。您已经在问题中找到的部分内容。我们采用起始索引的最小值和结束索引的最大值。这是代码:

for (var i = 0; i < ranges.length; i++) {
    var currentRangeIndices = getIndices(ranges[i]);
    var overlap = (createdRangeIndices.end < currentRangeIndices.start) 
                    || (currentRangeIndices.end < createdRangeIndices.start);
    if (!overlap) {
        // overlaps, which means we extend the existing range.
        // some logic to update the overlapped ranges

        var start, end;
        if (currentRangeIndices.start < createdRangeIndices.start) {
            start = ranges[i].start;
        } else {
            start = createdRange.start;
        }

        if (currentRangeIndices.end > createdRangeIndices.end) {
            end = ranges[i].end;
        } else {
            end = createdRange.end;
        }
        ranges[i] = { start: start, end: end };
        hasOverlap = true;
        break;
    }
}

并做了!

以下是将所有部分组合在一起的完整代码:

var ROWS = 7;
var COLS = 3;

function getIndex(row, col, COL) {
    return row * (COL + 1) + col;
}

function getIndices(range) {
    var start = range.start;
    var end = range.end;

    var startIndex = getIndex(start.row, start.col, COLS);
    var endIndex = getIndex(end.row, end.col, COLS);

    return { start: startIndex, end: endIndex };
}

function addRange(ranges, createdRange) {
    var createdRangeIndices = getIndices(createdRange);
    var hasOverlap = false;
    for (var i = 0; i < ranges.length; i++) {
        var currentRangeIndices = getIndices(ranges[i]);
        var overlap =
            createdRangeIndices.end < currentRangeIndices.start ||
            currentRangeIndices.end < createdRangeIndices.start;
        if (!overlap) {
            var start, end;
            if (currentRangeIndices.start < createdRangeIndices.start) {
                start = ranges[i].start;
            } else {
                start = createdRange.start;
            }
            if (currentRangeIndices.end > createdRangeIndices.end) {
                end = ranges[i].end;
            } else {
                end = createdRange.end;
            }
            ranges[i] = { start: start, end: end };
            hasOverlap = true;
            break;
        }
    }
    if (!hasOverlap) {
        // means the new range does not extend an existing range, so add it.
        ranges.push(createdRange);
    }
}

var ranges = []; // stores the range objects that are created earlier.
var rangesToAdd = [
    {
        start: { row: 2, col: 1 },
        end: { row: 6, col: 0 }
    },
    {
        start: { row: 6, col: 2 },
        end: { row: 7, col: 2 }
    },
    {
        start: { row: 3, col: 1 },
        end: { row: 6, col: 1 }
    },
    {
        start: { row: 6, col: 1 },
        end: { row: 6, col: 2 }
    }
];

rangesToAdd.forEach(aRange => addRange(ranges, aRange));
console.log(ranges);
© www.soinside.com 2019 - 2024. All rights reserved.