在一组垂直线段中查找所有脱节的交叉点

时间:2016-10-29 11:56:23

标签: javascript algorithm geometry

我有一组由y1和y2坐标定义的垂直区域,其中y1是起点,y2是每个区域的终点。我的坐标系的原点是左上角,所以y2总是大于y1。

这是一个例子:

var regions = [
  [10, 100],
  [50, 120],
  [60, 180],
  [140, 220]
];

我想找出大于一定大小的所有脱节交叉点,比方说,例如,20个单位。

到目前为止,我只能获得所有交叉点:[50, 100],[60, 100],[60, 120],[140, 180]但是我期待这个结果:[60, 100],[140, 180]。 有没有算法来获得这个结果?

小提琴:https://jsfiddle.net/4v5qnex5/

3 个答案:

答案 0 :(得分:1)

我相信问题很清楚。它在给定的垂直边缘范围数组中请求脱节的交叉点。这意味着

[[10, 100], [50, 120], [60, 180], [140, 220]]

数组为我们提供了两个脱节的交集1,来自[10, 100], [50, 120], [60, 180],结果为[60,100],另一个来自[60, 180], [140, 220]],结果为[140,180]。因此,当您注意到结果交叉[60,100][140,180]时,从这组给定的垂直边缘获得的交叉点是脱节的。

在JS中实现此功能的一种方法如下:

function getDisjointedIntersections(a){
  var di;
  return a.reduce((p,c,i,a) => c.used ? p
                                      : (p.push(a.map((_,j) => a[(i+j)%a.length])
                                                 .reduce((s,e) =>  (di = [Math.max(s[0],e[0]), Math.min(s[1],e[1])],
                                                                    di[0] < di[1] ? (e.used = true, di) : s))),p),[]);
}

var regions = [[10, 100],[50, 120],[60, 180],[140, 220],[150, 330]];
console.log(getDisjointedIntersections(regions));

说明:首先,很容易将这项工作视为一项简单的减少工作,但我猜不是。在某些情况下,您应该考虑以前的垂直边缘,因此可能需要多次传递。

因此,我们将从给定的数组开始,并尽可能地将垂直边缘减少到它们的交点。在执行此操作时,将为每个导致成功交叉的垂直边缘分配名为used且值为true的属性。一旦完成此传递,我们将向上旋转输入数组,直到未使用的垂直边缘到达索引位置0并再次开始通过交叉减少垂直边缘,但这次是从之前未使用的项目开始。

所以代码的核心,交叉减速器是

.reduce((s,e) =>  (di = [Math.max(s[0],e[0]), Math.min(s[1],e[1])],
                   di[0] < di[1] ? (e.used = true, di) : s))

这是一个没有初始化的简单缩减。它首先将交集分配给di

di = [Math.max(s[0],e[0]), Math.min(s[1],e[1])]

然后如果交集成功则将当前元素标记为已使用并返回di,这将是下一个缩减周期的前一个元素。如果交叉点不成功,那么它将返回上一个交点到下一个减少周期。

di[0] < di[1] ? (e.used = true, di) : s))

好的,我们已经完成了一个周期,只有[10, 100], [50, 120], [60, 180]已经交叉并产生[60,100]。因此我们将此结果推送到外部减少初始数组。

(p.push(a.map((_,j) => a[(i+j)%a.length])
         .reduce((s,e) => ... // the code that we already know

但是我们链接到的地图函数是什么减少到的。每个外部reduce的迭代,地图将我们的输入数组旋转一个索引。好看看它,你会明白它是如何做的

a.reduce((p,c,i,a) => c.used ? p
                             : (p.push(a.map((_,j) => a[(i+j)%a.length])
                                 .redu...

但是你也会注意到我们并没有浪费轮换。我们先等待,直到我们的外部reduce的当前元素(c)遇到未使用的项目。只有这样我们才能旋转输入数组以形成新的输入数组。例如,我们必须处理这个例子的两个输入数组是。

[ [ 10, 100 ],[ 50, 120 ],[ 60, 180 ],[ 140, 220 ],[ 150, 330 ] ]

[ [ 140, 220 ],[ 150, 330 ],[ 10, 100, used: true ],[ 50, 120, used: true ],[ 60, 180, used: true ] ]

因此,在这种特殊情况下,只有两次通过标记所有垂直边缘,并为我们提供所有两个断开的交叉点......无论您将输入数组洗牌多少次。

计算15~16msecs的1000项输入数据和400-500msecs的10000项数据。您可以检查代码并使用参数here

进行播放

确定以下版本被修改为仅显示允许的大小脱节交叉点。您可能希望here尝试不同数量的垂直边,轴大小和脱节交叉点的最小尺寸。

function getDisjointedIntersections(a,n){
  var di,                                  // disjointed intersections
      ri;                                  // resulting intersections
  return a.reduce(function(p,c,i,a){
                    if (c.used) return p;
                    ri = a.map((_,j) => a[(i+j)%a.length])
                          .reduce(function(s,e){
                                    di = [Math.max(s[0],e[0]), Math.min(s[1],e[1])];
                                    return di[0] < di[1] ? (e.used = true, di) : s;
                                  });
                    ri[1] - ri[0] > n && p.push(ri);
                    return p;
                  },[]);
}

var regions = [[10, 100],[50, 120],[60, 180],[140, 220],[150, 330]];
console.log(getDisjointedIntersections(regions,20));

答案 1 :(得分:0)

正如@MBo所说,选择交叉点[60, 100],[140, 180]似乎不是一个明确的原因,因为所有交叉点([50, 100],[60, 100],[60, 120],[140, 180])显然都超过20.但如果由于某种原因你和#39;仍然希望在这里获得大于特定大小的交叉点&#39;过程:

sort regions by the lower bound of each element
for (i = 0; i < len; i++)
    for (j = i+1; j < len; j++)
        if the ith and jth region overlap and resultingSize > theSizeYouWant
            add to the list of valid intersections // or whatever you want
        else break

其中lenregions的长度(因此在您给出len = 4的示例中)。这里有一些python执行上述操作(这是我知道的最简单的方法,可以按一组列表的索引进行排序)

from operator import itemgetter
regions = [
  [10, 100],
  [50, 120],
  [60, 180],
  [140, 220]
]   

# sort regions by the lower bound of each element
# (already done in your test case)
regions = sorted(regions, key=itemgetter(0))

# the size to compare to
size = 20

# list of valid intersections
l = []

# for (i = 0; i < len(regions)-1; i++)
for i in range(len(regions)-1):
    # for (j = i; j < len(regions)-1; j++)
    for j in range(i+1,len(regions)):
        # make sure regions overlap and if they do that they are at least size
        if (regions[i][1]>regions[j][0] and regions[i][1]-regions[j][0]>size):
            # add intersection to l
            l.append(str(regions[j][0])+"-"+str(regions[i][1]))
        # if not then there's no need to look at the others
        else:
            break

# print list of valid intersections
print(l)

输出:

['50-100', '60-100', '60-120', '140-180']

对于

的不同输入
regions = [
  [70, 100],
  [50, 120],
  [80, 180],
  [150, 220]
]

输出:

['70-120', '80-120', '150-180']

如果这是你已经做过的那件小事我很抱歉,但也许有些事情有用......

答案 2 :(得分:0)

您可以使用简化算法并使用叉积来搜索重叠项目。然后通过迭代和过滤未知匹配来获得公共部分。

var regions = [[10, 100], [50, 120], [60, 180], [140, 220]],
    hash = Object.create(null),
    result = regions.reduce(function (r, a) {
        var temp = regions.reduce(function (s, b) {
                var min = Math.max(s[0], b[0]), max = Math.min(s[1], b[1]);
                return min < max ? [min, max] : s;
            }, a),
            key = temp.join('|');

        if (key && !hash[key]) {
            hash[key] = true;
            r.push(temp);
        }
        return r;
    }, []);

console.log(result);