在 JavaScript 中查找数组中连续子数组的优雅方法?

问题描述 投票:0回答:8

我想编写一个函数来从给定的起始索引查找给定数组中的连续子数组,如果找到则返回数组中子数组的索引,如果未找到则返回-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字符串搜索算法tries有点矫枉过正),然后我的次要目标是优雅 我的实施。考虑到这两个目标,我想知道是否有更好的方法来编写此代码,或者是否有任何我缺少的 JavaScript 特性或函数可以帮助我避免

found
布尔值。

JSFiddle 如果它对任何人有帮助:http://jsfiddle.net/qc4zxq2p/

javascript arrays
8个回答
15
投票

是否有任何我缺少的 JavaScript 特性或函数可以帮助我避免

found
布尔值

是的,您可以在外循环上使用标签

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

8
投票

这和你的一样,只是美化了一下(至少按照我的审美):

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

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;

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


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

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

优雅提示
)。

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

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

无论如何,这就是:

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

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

0
投票

我在顶部处理离群值实例,并且只循环一次,检查当前元素是否与 subarr 的第一个元素相同。

如果是,我创建一个子数组来匹配 subarr 的长度,从当前索引开始。然后我使用 stringify 来检查我创建的子数组是否与 subarr 匹配。如果是这样或者长度只有 1(已经确认与上面匹配),我返回当前索引。

如果未找到匹配项,则返回-1。

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

    
    for (let i = from_index; i < arr.length; i++) {
        if (arr[i] == subarr[0]) {
            if (subarr.length == 1 
                    || JSON.stringify(arr.slice(i, i + subarr.length))===JSON.stringify(subarr)) {
                return i
            }
        }
    }
    return -1
}

0
投票

对于需要查找

search
数组的所有索引的人来说,这里是一个简单而高效的算法(作为 JS 函数):

function findSubArrayIndices(container, search){
    if(search.length > container.length || search.length === 0) return []
    const indecies = []
    const tLen = container.length
    const sLen = search.length
    let sI = 0
    let cmpStartIndex = -1
    for(let i = 0; i < tLen; i++){
        if(container[i] == search[sI]){
            if (sI == 0) cmpStartIndex = i;
            sI++
        }else{
            sI = 0
        }
        if(sI === sLen){
            indecies.push(cmpStartIndex)
            sI = 0
        }
    }
    return indecies
}

请注意,数组是按值进行比较的。

© www.soinside.com 2019 - 2024. All rights reserved.