【问题标题】:Find all disjointed intersections in a set of vertical line segments在一组垂直线段中查找所有不相交的交点
【发布时间】:2016-10-29 11:56:22
【问题描述】:

我有一组由 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/

【问题讨论】:

  • 我没明白你的意思disjointed intersection在这里,以及[60, 100][50, 100]更好的地方
  • [60,100]是前三个区域的交集,[60,100]和[140,180]的交集是空的,所以是不相交的。
  • 我注意到 [60,100] 的覆盖数最大,但您没有提到这一事实的重要性。所以你暗示了这个标准,但没有描述它。请更好地说明问题。我怀疑不同的标准可能会产生矛盾。

标签: javascript algorithm geometry


【解决方案1】:

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

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

array 为我们提供了两个不相交的交集,一个来自[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 并再次开始通过交集减少垂直边缘,但这次是从以前未使用的项目开始。

所以代码的核心,交叉点reducer是

.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,这将是下一个reduce循环的前一个元素。如果交集不成功,则将之前的交集返回到下一个reduce循环。

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

好的,我们已经完成了一个循环,只有[10, 100], [50, 120], [60, 180] 相交并产生[60,100]。所以我们把这个结果推到外部reduce初始数组中。

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

但是,我们将 reduce 链接到的 map 函数是什么。每次外部 reduce 的迭代,map 将我们的输入数组旋转一个索引。好的,看看它,你就会明白它是如何工作的

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~16 毫秒内计算 1000 条输入数据,在大约 400-500 毫秒内计算 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));

【讨论】:

  • 我正在努力使用你的代码,一些 cmets 对我很有用,因为我无法按原样复制和粘贴你的解决方案...请你帮帮我理解算法?
  • @deblocker 好的,我已经添加了一些解释。我希望它对你有帮助。可能有一些更好的方法,但这是我昨天深夜想出的。
  • THX,我猜小于给定大小的交叉点可以在推送到外部减少数组之前被过滤掉,从而避免对所有返回的交叉点进行额外的最终传递。你同意吗?
  • @deblocker 好的,我们不需要在输出中附加过滤器。只需稍加改动即可很好地过滤它。我已将代码重构为更传统的代码,并包含在答案的末尾。我很确定您可以通过这种方式更好地理解。
【解决方案2】:

正如@MBo 所说,似乎没有明确的理由选择交叉口[60, 100],[140, 180],因为所有交叉口 ([50, 100],[60, 100],[60, 120],[140, 180]) 显然都超过 20 个。但如果由于某种原因你仍然想得到大于一定大小的交叉点这里有一个过程:

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']

如果这是您已经完成的微不足道的事情,我深表歉意,但也许有些有用的东西......

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-12-17
      • 2016-12-22
      • 2016-07-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-06-09
      相关资源
      最近更新 更多