在JavaScript中在数组中找到连续子阵列的优雅方法?

时间:2015-04-03 03:32:34

标签: javascript arrays

我想编写一个函数,从给定的起始索引中查找给定数组中的连续子数组,如果找到则返回数组中子数组的索引,如果找不到,则返回-1。这与String.indexOf类似,但对于数组和子数组而不是字符串和子字符串。

这是我的工作代码:

var find_csa = function (arr, subarr, from_index) {
    if (typeof from_index === 'undefined') {
        from_index = 0;
    }

    var i, found, j;
    for (i = from_index; i < 1 + (arr.length - subarr.length); ++i) {
        found = true;
        for (j = 0; j < subarr.length; ++j) {
            if (arr[i + j] !== subarr[j]) {
                found = false;
                break;
            }
        }
        if (found) return i;
    }
    return -1;
};

这些是我的测试及其预期值:

console.log(find_csa([1, 2, 3, 4, 5], [2, 3, 4]) === 1);
console.log(find_csa([1, 2, 3, 4, 5], [5]) === 4);
console.log(find_csa([1, 2, 3, 4, 5], [1, 3]) === -1);
console.log(find_csa([1, 2, 3, 4, 5], [42]) === -1);
console.log(find_csa([1, 2, 3, 4, 5], []) === 0);
console.log(find_csa([3, 4, 3, 4, 3, 4], [3, 4, 3], 1) === 2);
console.log(find_csa([6, 6, 6, 7], [6, 6, 7]) === 1);
console.log(find_csa([12, 9, 16, 42, 7, 866, 3], [16, 42, 7, 866]) === 2);

我的代码通过了测试,但正如您所看到的,它在内部循环中使用了一个布尔值found,这只是我从嵌套循环继续外部循环的混乱方式。有更清洁的写作方式吗?我查看了Array.prototype.findIndex,但目前这是一项实验性技术,所以我无法使用它。我想要一种适用于大多数浏览器的方法。我知道在Mozilla页面上有一个“polyfill”代码片段,但是它比我当前的代码更长,并且由于函数调用它会更慢,所以我宁愿避免它。

我这个功能的主要目标是性能(子阵列非常小,所以我相信使用Boyer-Moore string search algorithmtries有点过头了),然后我的第二个目标是实现优雅。考虑到这两个目标,我想知道是否有更好的方法来编写此代码,或者是否有任何我缺少的JavaScript特性或函数可以帮助我避免使用found布尔值。

JSFiddle是否可以帮助任何人:http://jsfiddle.net/qc4zxq2p/

6 个答案:

答案 0 :(得分:12)

  

我是否缺少任何可以帮助我避免found布尔值

的JavaScript功能或功能

是的,您可以在外循环中使用label

function find_csa(arr, subarr, from_index) {
    var i = from_index >>> 0,
        sl = subarr.length,
        l = arr.length + 1 - sl;

    loop: for (; i<l; i++) {
        for (var j=0; j<sl; j++)
            if (arr[i+j] !== subarr[j])
                continue loop;
        return i;
    }
    return -1;
}

答案 1 :(得分:7)

这与你的相同,只是美化了一点(至少对我的美学而言):

var find_csa = function (arr, subarr, from_index) {
    from_index = from_index || 0;

    var i, found, j;
    var last_check_index = arr.length - subarr.length;
    var subarr_length = subarr.length;

    position_loop:
    for (i = from_index; i <= last_check_index; ++i) {
        for (j = 0; j < subarr_length; ++j) {
            if (arr[i + j] !== subarr[j]) {
                continue position_loop;
            }
        }
        return i;
    }
    return -1;
};

答案 2 :(得分:4)

使用数组方法every

可以将内循环简化为单行
if(subarr.every(function(e, j) { return (e === arr[i + j]); })
    return i;

或(ES6提案):

if(subarr.every( (e, j) => (e === arr[i + j]) ))
    return i;

但这可能只是一个好奇心或教育的例子,除非你不关心表现。

答案 3 :(得分:1)

在循环中,您可以删除found变量,并避免像这样继续:

for (j = 0; j < subarr.length; ++j) {
    if (arr[i + j] !== subarr[j]) break;
}
/*
 * the above loop breaks in two cases:
 * normally: j === subarr.length
 * prematurely: array items did not match
 * we are interested in kowing if loop terminated normally
 */
if (j === subarr.length) return i;

话虽如此,这是我使用Array.joinString.indexOf的解决方案。这只适用于数字数组:

function find_csa(arr, subarr, from_index) {
    from_index |= 0;
    if (subarr.length === 0) {
        return from_index;
    }
    var haystack = "," + arr.slice(from_index).join(",") + ",",
        needle = "," + subarr.join(",") + ",",
        pos = haystack.indexOf(needle);
    if (pos > 0) {
        pos = haystack.substring(1, pos).split(",").length + from_index;
    }
    return pos;
}
console.log("All tests should return true");
console.log(find_csa([1, 2, 3, 4, 5], [1, 2, 3]) === 0);
console.log(find_csa([1, 2, 3, 4, 5], [2, 3, 4]) === 1);
console.log(find_csa([1, 2, 3, 4, 5], [5]) === 4);
console.log(find_csa([1, 2, 3, 4, 5], [6]) === -1);
console.log(find_csa([1, 2, 3, 4, 5], [1, 3]) === -1);
console.log(find_csa([6, 6, 6, 7], [6, 6, 7]) === 1);
console.log(find_csa([1, 2, 3, 4, 5], []) === 0);
console.log(find_csa([3, 4, 3, 4, 3, 4], [3, 4, 3], 1) === 2);
console.log(find_csa([1, 2, 3, 4, 5], [], 1) === 1);

答案 4 :(得分:1)

阅读基于zerkms命题的初步讨论,我有兴趣尝试使用JSON.stringify的解决方案,尽管有不利的意见。

然后我终于得到了一个解决方案,它正确地通过了所有测试 可能不是更快的方法,但肯定是最短的方法:

var find_csa = function (arr, subarr, from_index) {
  var start=from_index|0,
      needle=JSON.stringify(subarr),
      matches=JSON.stringify(arr.slice(start)).
      match(new RegExp('^\\[(.*?),?'+
        needle.substr(1,needle.length-2).replace(/([\[\]])/g,'\\$1')
      ));
  return !!matches?(matches[1].length?matches[1].split(',').length:0)+start:-1;
}

上面的代码接受Shashank建议的数组数组,但无法处理包含逗号的项目。

所以我开发了另一个也接受逗号的解决方案(感谢Steven Levithan关于while(str!=(str=str.replace(regexp,replacement)));关于var find_csa = function (arr, subarr, from_index) { var start=from_index|0, commas=new RegExp('(?:(\')([^,\']+),([^\']+)\'|(")([^,"]+),([^"]+))"'), strip_commas='$1$2$3$1$4$5$6$4', haystack=JSON.stringify(arr.slice(start)), needle=JSON.stringify(subarr).replace(/^\[(.*)\]$/,'$1'); while(haystack!=(haystack=haystack.replace(commas,strip_commas))); while(needle!=(needle=needle.replace(commas,strip_commas))); matches=haystack.match(new RegExp('^\\[(.*?),?'+needle.replace(/([\[\]])/g,'\\$1'))); return !!matches?(matches[1].length?matches[1].split(',').length:0)+start:-1; } )。

但这只是为了好玩,因为:

  • 代码不是那么短,现在......叹息!
  • 它可能会耗费大量的CPU时间
  • 它没有正确处理空项目(忽略它们)
  • 我怀疑(并没有深入挖掘:-)它可能会因为项目的复杂对象而失败

无论如何,这是它:

{{1}}

答案 5 :(得分:0)

我用这个代码解决了这个问题:

 getCount(arr)
{
  const chunked = [];

  for(let i=0; i<arr.length; i++) chunked[i] = [];
  let sub = 0;
  for (let i = 1;  i < arr.length; i++) {
    if (arr[i]>arr[i-1]) {
      chunked[sub].push(...[arr[i-1],arr[i]]);
    } else {
      sub++
    }
  }
  const chunked2 = [...chunked.filter(k=>k.length).map(k => [...new Set(k)])];

  for (let i = 0; i < chunked2.length; i++) {
    if (chunked2[i+1])
    if( chunked2[i][chunked2[i].length - 1] > chunked2[i+1][0]) {
      chunked2[i+1].shift();
    }
  }
  return [].concat.apply([], chunked2).lenght;
}