JavaScript:减去数字范围

时间:2015-11-22 15:44:19

标签: javascript algorithm

我正在尝试编写一个JS函数,它有两个参数,include和exclude,每个参数都是一个对象{X,Y}的数组,表示从X到Y的数字范围,两者都包括在内。

输出是减去include中所有范围的所有范围。

例如:

include = [ {1,7}, {9,10}, {12,14} ]
exclude = [ {4,5}, {11,20} ]
output  = [ {1,3}, {6,7}, {9,10} ]
  • {4,5}将{1,7}分成两个范围对象:{1,3}和{6,7}
  • {9,10}未受影响
  • {12,14}已完全删除

6 个答案:

答案 0 :(得分:1)

您可以使用扫描线算法。对于每个数字保存它代表的内容(开始和结束,包含和排除)。然后将所有数字放在一个数组中并对其进行排序。然后迭代地从数组中删除元素并执行适当的操作。

include_list = [[1,7]]
exclude_list = [[4,5]]
(1,start,inclusion),(4,start,exclusion),(5,end,exclusion),(7,end,inclusion)

include = 0
exclude = 0
cur_element = (1,start,inclusion) -> include = 1, has_open_range = 1, range_start = 1 // we start a new range starting at 1
cur_element = (4,start,exclusion) -> exclude = 1, has_open_range = 0, result.append ( [1,4] ) // we close the open range and add range to result
cur_element = (5,end,exclusion) -> exclude = 0, has_open_range = 1, range_start = 5 // because include was 1 and exclude become 0 we must create a new range starting at 5
cur_element = (7,end,inclusion) -> include = 0, has_open_range = 0, result.append([5,7]) // include became zero so we must close the current open range so we add [5,7] to result

维护变量includeexclude使用相应元素的开头递增它们,并在接收到结束元素时递减它们。根据{{​​1}}和include的值,你可以确定你应该开始一个新范围,关闭开放范围,或者什么也不做。

该算法以线性时间O(n)运行。

答案 1 :(得分:0)

注意:include = [ {1,7}, {9,10}, {12,14} ]不是有效的javascript,所以我假设你传递的是数组数组,例如:

 include = [ [1,7], [9,10], [12,14] ]

蛮力方法(解决方案,可能不是最有说服力的):

function solve_range(include, exclude) {
    numbers = [];
    include.forEach(function (range) {
        for (i = range[0]; i <= range[1]; i++) {
            numbers[i] = true;
        }
    });

    exclude.forEach(function (range) {
        for (i = range[0]; i <= range[1]; i++) {
            numbers[i] = false;
        }
    });

    contiguous_start = null;

    results = [];

    for (i = 0; i < numbers.length; i++) {
        if (numbers[i] === true) {
            if (contiguous_start == null) {
                contiguous_start = i;
            }
        } else {
            if (contiguous_start !== null) {
                results[results.length] = [contiguous_start, i - 1];
            }
            contiguous_start = null;
        }
    }

    return results;
}

var include = [
    [1, 7],
    [9, 10],
    [12, 14]
];
var exclude = [
    [4, 5],
    [11, 20]
];

var output = solve_range(include, exclude);

https://jsfiddle.net/dwyk631d/2/

答案 2 :(得分:0)

用于减去两组X,Y的整数集算术的规则是

X − Y := {x − y | x ∈ X, y ∈ Y }

但这似乎不是你想要的。

您可以在示例中假设有序集,它允许您将每个出现的x==y设置为JavaScript数组中的任意值,并使用它来分割。但你不需要那样。

设置差异{1...7}\{4...5}扩展为{1,2,3,4,5,6,7}\{4,5}。您可以很容易地看到,使用设置算术规则的减法将保留{1,2,3,0,0,6,7},并使用正常的设置减法(符号\)得到{1,2,3,6,7}

设定差异{12...14}\{11...20}扩展为{12,13,14}\{11,12,13,14,15,16,17,18,19,20};设定算术。差异是{-11,0,0,0,-15,-16,..., - 20},但正常的set-subtraction留下空集{}

使用空集处理操作等同于算术集规则的常规算术{x}-{}={x}{}-{x} = {-x}以及具有正常规则的{x}\{}={x}{}\{x}= {}

因此,根据您的示例,您必须使用的是正常的设置规则。没有必要扩展集合,可以假设它们是密集的。

您可以使用相对差异(您可以称之为距离)。

使用{1...7}\{4...5}时,第一个开头很小,然后第二个开始,第一个结尾比第二个结尾更大,这导致了两个不同的集合。

使用{12...14}\{11...20}时,第一个开头大于第二个开始,第一个结尾低于第二个结尾,导致空集。

第三个例子使用了空集规则。

您需要一个示例代码段吗?

答案 3 :(得分:0)

这里的答案适用于分数而且不仅仅是暴力强迫。我已经添加了评论来解释它是如何工作的。这可能看起来很大,前提很简单:

  1. 创建一个接受点p1_excluding_p2p1的方法p2,并返回执行p1 - p2后存在的点数组

  2. 创建一个方法points_excluding_p2,它执行与上面相同的操作,但这次允许我们传递一个points数组,并返回减去{后存在的点数组{1}}来自我们数组中的所有点,现在我们有p2

  3. 创建一个方法(points) - p2,它采用与上面相反的输入。这一次,接受一个点p1_excluding_all和许多排除点,并在减去所有排除点后返回剩余的点数组。这实际上很容易创建。我们只需从p1和第一个排除点([p1])开始,然后将其反馈到exclusion1。我们使用points_excluding_p2获取返回的数组(将p1 - exclusion1)并将提供给points_excluding_p2。我们会继续这一过程,直到我们排除每个排除点,然后我们留下一系列exclusion2

  4. 现在我们有能力执行p1 - (all exclusion points),只需循环遍历所有点并致电p1 - (all exclusion points),我们就会留下一个数组点减去每个排除点。我们通过p1_excluding_all运行我们的结果,如果我们有任何重复的条目,那就是它。

  5. 代码:

    remove_duplicates

    返回:

    var include = [ [1,7], [9,10], [12,14] ]
    var exclude = [ [4,5], [11,20] ]
    
    /* This method is just a small helper method that takes an array 
     * and returns a new array with duplicates removed
     */
    
    function remove_duplicates(arr) {
      var lookup = {};
      var results = [];
      for(var i = 0; i < arr.length; i++) {
        var el = arr[i];
        var key = el.toString();
        if(lookup[key]) continue;
        lookup[key] = 1;
        results.push(el);
      }
      return results;
    }
    
    /* This method takes 2 points p1 and p2 and returns an array of
     * points with the range of p2 removed, i.e. p1 = [1,7]
     * p2 = [4,5] returned = [[1,3],[6,7]]
     */
    
    function p1_excluding_p2(p1, p2) {
      if(p1[1] < p2[0]) return [p1]; // line p1 finishes before the exclusion line p2
      if(p1[0] > p2[1]) return [p1]; // line p1 starts after exclusion line p1
      var lines = [];
      // calculate p1 before p2 starts
      var line1 = [ p1[0], Math.min(p1[1], p2[0]-1) ];
      if(line1[0] < line1[1]) lines.push(line1);
      // calculate p1 after p2 ends
      var line2 = [ p2[1]+1, p1[1] ];
      if(line2[0] < line2[1]) lines.push(line2);
      // these contain the lines we calculated above
      return lines;
    }
    
    /* this performs the exact same operation as above, only it allows you to pass
     * multiple points (but still just 1 exclusion point) and returns results
     * in an identical format as above, i.e. points = [[1,7],[0,1]]
     *  p2 = [4,5] returned = [[0,1],[1,3],[6,7]]
     */
    
    function points_excluding_p2(points, p2) {
      var results = [];
      for(var i = 0; i < points.length; i++) {
        var lines = p1_excluding_p2(points[i], p2);
        results.push.apply(results, lines); // append the array lines to the array results
      }
      return results;
    }
    
    /* this method performs the same operation only this time it takes one point
     * and multiple exclusion points and returns an array of the results.
     * this is the important method of: given 1 point and many
     * exclusion points, return the remaining new ranges
     */
    
    function p1_excluding_all(p1, excluded_pts) {
      var checking = [p1];
      var points_leftover = [];
      for(var i = 0; i < exclude.length; i++) {
        checking = points_excluding_p2(checking, exclude[i]);
      }
      return remove_duplicates(checking);
    }
    
    /* now that we have a method that we can feed a point and an array of exclusion
     * points, its just a simple matter of throwing all our points into this
     * method, then at the end remove duplicate results for good measure
     */
    
    var results = [];
    for(var i = 0; i < include.length; i++) {
      var lines = p1_excluding_all(include[i], exclude);
      results.push.apply(results, lines); // append the array lines to the array results
    }
    results = remove_duplicates(results);
    
    console.log(results);
    

答案 4 :(得分:0)

这是一个工作解决方案,可处理排除范围的4种可能的重叠方案。

var include = [{from:1, to: 7},{from: 9, to: 10},{from: 12, to: 14}];
     var exclude = [{from:4, to: 5}, {from: 11, to: 20}];

     //result: {1,3}, {6,7}, {9,10}

     var resultList = [];

     for (var i=0;i<include.length;i++){

         var inc = include[i];
         var overlap = false;

         for (var x=0;x<exclude.length;x++ ){

             var exc = exclude[x];

             //4 scenarios to handle

             if (exc.from >= inc.from && exc.to <= inc.to){
                 //include swallows exclude - break in two
                 resultList.push({from: inc.from, to: exc.from - 1});
                 resultList.push({from: exc.to + 1, to: inc.to});
                 overlap = true;
             }else if (exc.from <= inc.from && exc.to >= inc.to){
                 //exclude swallows include - exclude entire range
                 overlap = true;
                 break;
             }else if (exc.from <= inc.from && exc.to <= inc.to && exc.to >= inc.from){
                 //exclusion overlaps on left
                 resultList.push({from: exc.to, to: inc.to});
                 overlap = true;
             }else if (exc.from >= inc.from && exc.to >= inc.to && exc.from <= inc.to){
                 //exclusion overlaps on right
                 resultList.push({from: inc.from, to: exc.from - 1});
                 overlap = true;
             }

         }

         if (!overlap){
             //no exclusion ranges touch the inclusion range
             resultList.push(inc);
         }
     }
     console.log(resultList);

答案 5 :(得分:0)

也许我们可以通过将标记的区间合并到一个排序列表中来提高效率:

include = [ {1,7}, {9,10}, {12,14} ]    
exclude = [ {4,5}, {11,20} ]

merged = [ [1,7,0], [4,5,1], [9,10,0], [11,20,1], [12,14,0] ];

然后,遍历列表,对于任何排除的间隔,更新任何周围受影响的间隔。