Элегантный способ найти смежный субарак в массиве в JavaScript?
Я хотел написать функцию для поиска смежного субарама в заданном массиве из заданного начального индекса и вернуть индекс подмассива в массив, если он найдет, и -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
во внутреннем цикле, который является всего лишь моим беспорядочным, ad-hoc способом продолжения внешнего цикла из вложенного цикла. есть ли более чистый способ его написания? Я просмотрел Array.prototype.findIndex
, но сейчас это экспериментальная технология, поэтому я не могу ее использовать. Мне нужен метод, который работает в большинстве браузеров. Я знаю, что есть фрагмент кода polyfill, написанный на странице Mozilla, но это даже больше, чем мой текущий код, и он будет медленнее из-за вызовов функций, поэтому я бы предпочел избежать этого.
Моя основная цель для этой функции - производительность (субмарины будут очень маленькими, поэтому я считаю, что используя строчный алгоритм поиска Boyer-Moore или try - это слишком много, но моя вторая цель - изящество моей реализации. Учитывая эти две цели, я хотел бы знать, есть ли лучший способ написания этого кода, или если есть какие-либо функции или функции JavaScript, которые мне не хватает, что могло бы помочь мне избежать found
boolean.
JSFiddle, если это кому-то помогает: http://jsfiddle.net/qc4zxq2p/
Ответы
Ответ 1
Есть ли какие-либо функции или функции JavaScript, которые у меня отсутствуют, что может помочь мне избежать found
boolean
Да, вы можете использовать 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;
}
Ответ 2
Это то же самое, что и у вас, просто немного приукрашено (по крайней мере, к моей эстетике):
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;
};
Ответ 3
Внутренний цикл может быть сведен к одной строке с использованием метода массива 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;
Но это может быть просто любопытство или образовательный пример, если вы не заботитесь о производительности.
Ответ 4
Внутри вашего цикла вы можете исключить переменную 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.join и String.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);
Ответ 5
Прочитав начальную дискуссию на основе предложения 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, но не обрабатывает элементы, содержащие запятые.
Итак, я разработал другое решение, которое также принимает запятые (спасибо Стивену Левитану за элегантный отзыв о 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;
}