2015-04-03 9 views
12

Ich wollte eine Funktion schreiben, um ein zusammenhängendes Subarray innerhalb eines gegebenen Arrays von einem gegebenen Startindex zu finden und den Index des Subarrays innerhalb des Arrays zurückzugeben, wenn es gefunden wird, und -1, wenn es nicht gefunden wird. Dies ist vergleichbar mit String.indexOf, aber für Arrays und Subarrays anstelle von Strings und Substrings.Elegante Möglichkeit, zusammenhängendes Subarray innerhalb eines Arrays in JavaScript zu finden?

Das ist mein Arbeitscode:

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

Und das sind meine Tests und ihre erwarteten Werte:

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

Mein Code die Tests besteht, aber wie Sie sehen können, verwendet es einen boolean Wert found in der inneren Schleife, die nur meine unordentliche, Ad-hoc-Möglichkeit der Fortsetzung einer äußeren Schleife aus einer verschachtelten Schleife ist. Gibt es eine sauberere Art, es zu schreiben? Ich schaute in Array.prototype.findIndex, aber es ist eine experimentelle Technologie im Moment, so dass ich es nicht verwenden kann. Ich möchte eine Methode, die in den meisten Browsern funktioniert. Ich weiß, dass auf der Mozilla-Seite ein "Polyfill" -Code-Snippet geschrieben ist, aber das ist sogar länger als mein aktueller Code und wird aufgrund der Funktionsaufrufe langsamer sein, also würde ich es lieber vermeiden.

Mein primäres Ziel für diese Funktion ist Leistung (der Sub-Arrays sehr klein sein, so dass ich glaube, dass Boyer-Moore string search algorithm oder tries mit einem bisschen übertrieben ist), und dann mein zweites Ziel ist Eleganz meiner Umsetzung. Mit diesen zwei Zielen möchte ich wissen, ob es eine bessere Möglichkeit gibt, diesen Code zu schreiben, oder ob es irgendwelche JavaScript-Funktionen oder -Funktionen gibt, die mir fehlen, die mir helfen könnten, den booleschen Wert zu vermeiden.

JSFiddle, wenn es hilft jemand: http://jsfiddle.net/qc4zxq2p/

+0

Was 1. 'JSON.stringify' 2.' array.prototype.slice' – zerkms

+0

nicht mit 'kommen sie ''' aber kommen mit ',' stattdessen. String.indexOf würde immer noch funktionieren, aber es wird vermeiden, '[2,11,5]' und '[21,15]' als dasselbe zu interpretieren – slebetman

+0

@zerkms, ich könnte JSON.stringify sehen, wenn es darum geht, die Existenz eines zusammenhängenden Objekts zu finden Subarray, aber wissen Sie, wie Sie seinen Index abrufen würden? Ich nehme an, Sie müssten sich auf Kommas oder etwas trennen. Aber dann würden Sie wieder auf Platz 1 sein. Bei Ihrer Nummer-2-Methode kann ich nicht erkennen, wie "Array.prototype.slice" helfen würde. Ich verstehe, dass Sie es verwenden können, um Zeichenfolgen in Arrays zu konvertieren, die "Anruf" verwenden, aber das ist nicht das Problem. – Shashank

Antwort

11

es irgendwelche JavaScript oder Funktionen verfügt dass ich fehle, dass mir helfen, die found boolean

Ja vermeiden konnte, können Sie eineverwenden 210 auf der äußeren Schleife:

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

Schön, aber ich habe zwei Fragen, was ist die >>> 'Syntax, die Sie verwenden? Ist das eine Standardmethode zum Festlegen von Standardargumentwerten? Wird die Verwendung von Etiketten als schlechte Praxis angesehen? Der Grund, warum ich frage, ist, weil es sagt, sie hier zu vermeiden: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/label – Shashank

+3

Nicht Standard, '>>> 0 ist eine hacky Art zu sagen "Convert to Integer". "x >>> y" ist "shift" x "nach rechts" y "Bits"; Wenn Sie 0 Bits verschieben, ist es das gleiche, was Sie vorher hatten, aber da '>>>' nur für ganze Zahlen funktioniert, erhalten Sie den Nebeneffekt der Nötigung zu einem ganzzahligen Wert. Es ist wahrscheinlich der schnellste Weg, es zu tun, zusammen mit 'x | 0' (was dasselbe tut, AFAIK). Natürlich vorausgesetzt, dass Ihre Argumente bereits ganze Zahlen sind, ist noch schneller, wenn Sie wissen, dass der Anrufer den Vertrag befolgen wird. :) – Amadan

+1

@Shashank: Die '>>>' unsigned Bitshift wird verwendet, um den 'from_index' in eine ganze Array-Länge umzuwandeln (http://stackoverflow.com/q/3081987/1048572). Etiketten sind absolut sicher, es scheint, dass einige Leute nicht verstehen, wie sie arbeiten und versuchen, diese Funktion zu vermeiden. – Bergi

7

Dies ist die gleiche wie Sie ist, verniedlicht nur ein bisschen (zumindest meiner Ästhetik):

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

Ich mag diese Antwort. Ich wusste nicht, dass es Labels in JS gibt. Das bereinigt meinen Code ein wenig. In der [Mozilla-Dokumentation für Labels] (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/label) wird jedoch davor gewarnt, sie zu verwenden. Wird es als schlechte Praxis angesehen? – Shashank

+1

http: // Stapelüberlauf.com/questions/46496/sollte-ich-vermeiden-using-java-label-statements - tl; dr: Wenn das Programm verständlicher und prägnanter bei der Verwendung einer Bezeichnung ist, verwenden Sie eine Bezeichnung (aber es ist oft nicht). – Amadan

4

Die innere Schleife auf eine einzige Zeile reduziert werden kann every das Array-Methode:

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

oder (ES6 Vorschlag):

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

Aber Dies kann nur ein Kuriosum oder ein Bildungsbeispiel sein, es sei denn, Sie interessieren sich nicht für die Leistung.

1

Innerhalb der Schleife können Sie die found Variable beseitigen und vermeiden so weitermachen:

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; 

gesagt haben, dass hier meine Lösung mit Array.join und String.indexOf ist.Das ist nur gut für die Reihe von Zahlen:

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

Wenn die Annahme gemacht wird, dass die Array-Elemente alle Zahlen sind, wird dieser Ansatz sehr gut funktionieren. +1 – Shashank

1

Lese erste Diskussion auf Basis von zerkms Satz, ich war daran interessiert, eine Lösung zu versuchen JSON.stringify verwenden, trotz der ungünstigen Meinungen.

Dann habe ich endlich eine Lösung, die alle Tests richtig besteht.
Wahrscheinlich nicht die schnellere Methode, aber sicher die kürzeste:

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

Der obige Code akzeptiert Arrays von Arrays, wie Shashank vorgeschlagen, doch irgendwie Elemente enthalten Kommas zu verarbeiten.

Also habe ich eine andere Lösung entwickelt, die auch Kommas akzeptiert (Danke an Steven Levithan für die elegant tip über while(str!=(str=str.replace(regexp,replacement)));).

Aber es ist nur zum Spaß, da:

  • der Code ist nicht so kurz, jetzt ... Seufz!
  • es verbraucht wahrscheinlich viele CPU-Zeit
  • es nicht richtig mit leeren Elementen funktioniert (sie werden ignoriert)
  • Ich vermute (und nicht tiefer zu graben habe :-) es mit komplexen Objekten scheitern könnte als
  • Artikel

Wie auch immer, hier ist es:

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

Dies ist ziemlich beeindruckend und einzigartig als Lösung. Es schlägt fehl, wenn Sie Arrays von Strings mit Kommas oder Arrays von Arrays aus offensichtlichen Gründen testen, aber für Arrays von Zahlen scheint es perfekt zu sein. – Shashank

+0

@Shashank: Du hast Recht! Ich habe auf mehr als die Testbeispiele nicht geachtet. Ich habe meine Lösung bearbeitet, um Array-Arrays zu akzeptieren. Hoffentlich kommst du später wieder in Kommas :-) – cFreed

+0

@Shashank: Nun, es akzeptiert jetzt auch Kommas ... aber der Code wurde ziemlich dreckig. Eigentlich keine wirkliche Lösung. – cFreed

Verwandte Themen