2017-03-12 2 views
0

Ich versuche, rekursiv ein Objekt, das Zeichenfolgen, Arrays und andere Objekte enthält, um ein Element zu finden (einen Wert zu finden) auf der tiefsten Ebene, aber ich bin immer als Ergebnis unbestimmt werden. Ich kann durch eine Konsolenprotokollierung sehen, dass ich das Element finde, aber es wird überschrieben. Irgendeine Idee, wo ich falsch liege?Javascript Objekt Rekursion, um ein Element auf der tiefsten Ebene

var theCobWeb = { 
    biggestWeb: { 
     item: "comb", 
     biggerWeb: { 
     items: ["glasses", "paperclip", "bubblegum"], 
     smallerWeb: { 
      item: "toothbrush", 
      tinyWeb: { 
      items: ["toenails", "lint", "wrapper", "homework"] 
      } 
     } 
     }, 
     otherBigWeb: { 
     item: "headphones" 
     } 
    } 
    }; 

    function findItem (item, obj) { 
    var foundItem; 
    for (var key in obj) { 
    if (obj[key] === item) { 
     foundItem = obj; 
    } else if (Array.isArray(obj[key]) && obj[key].includes(item)) { 
     foundItem = obj; 
    } else if (typeof obj[key] === 'object' && !Array.isArray(obj[key])) { 
     findItem(item, obj[key]); 
    } 
    } 
     return foundItem; 
} 

var foundIt = findItem('glasses', theCobWeb); 
console.log('The item is here: ' + foundIt); // The item is here: undefined 

Bearbeiten: bereinigt den Code ein bisschen basierend auf Feedback unten.

Antwort

1
function findItem (item, obj) { 
    for (var key in obj) { 
    if (obj[key] === item) { // if the item is a property of the object 
     return obj;   // return the object and stop further searching 
    } else if (Array.isArray(obj[key]) && obj[key].includes(item)) { // if the item is inside an array property of the object 
     return obj;   // return the object and stop the search 
    } else if (typeof obj[key] === 'object' && !Array.isArray(obj[key])) { // if the property is another object 
     var res = findItem(item, obj[key]); // get the result of the search in that sub object 
     if(res) return res; // return the result if the search was successful, otherwise don't return and move on to the next property 
    } 
    } 
    return null; // return null or any default value you want if the search is unsuccessful (must be falsy to work) 
} 

Anmerkung 1:Array.isArray und Array.prototype.includes bereits Rückkehr booleans so sie es nicht erforderlich, gegen booleans zu überprüfen ist.

Anmerkung 2: Sie können den Wert eines boolean Flip die NOT-Operator (!) verwendet wird.

Hinweis3: Sie müssen das Ergebnis (falls gefunden) sofort zurückgeben, nachdem es gefunden wurde, damit Sie keine Zeit verschwenden, nach etwas zu suchen, das Sie bereits haben.

Hinweis4: Die Rückkehr Ergebnis der Suche wird ein Objekt sein (falls gefunden) und da Objekte durch Verweis nicht von Wert übergeben werden, das Ändern der Eigenschaften dieses Objekts wird auch die Eigenschaften des ursprünglichen Objekts ändern.

Edit: das tiefste Objekt finden:

Wenn Sie das tiefste Objekt finden wollen, werden Sie throug jedes Objekt gehen und Unterobjekt in dem Objekt obj und jedes Mal haben Sie die speichern Objekt und seine Tiefe (wenn die Tiefe des Ergebnisses natürlich größer ist als das vorherige Ergebnis). Hier ist der Code mit einigen Kommentaren (Ich habe eine interne Funktion _find, die tatsächlich auf alle Objekte aufgerufen):

function findItem (item, obj) { 
    var found = null;    // the result (initialized to the default return value null) 
    var depth = -1;    // the depth of the current found element (initialized to -1 so any found element could beat this one) (matched elements will not be assigned to found unless they are deeper than this depth) 

    function _find(obj, d) {  // a function that take an object (obj) and on which depth it is (d) 
     for (var key in obj) {  // for each ... 
      // first call _find on sub-objects (pass a depth of d + 1 as we are going to a one deep bellow) 
      if (typeof obj[key] === 'object' && !Array.isArray(obj[key])) { 
       _find(obj[key], d + 1); 
      } 
      // then check if this object actually contain the item (we still at the depth d) 
      else if (obj[key] === item || (Array.isArray(obj[key]) && obj[key].includes(item))) { 
       // if we found something and the depth of this object is deeper than the previously found element 
       if(d > depth) { 
        depth = d;  // then assign the new depth 
        found = obj; // and assign the new result 
       } 
      } 
     } 
    } 
    _find(obj, 0);     // start the party by calling _find on the object obj passed to findItem with a depth of 0 

    // at this point found is either the initial value (null) means nothing is found or it is an object (the deepest one) 
    return found; 
} 
+0

Danke. Sie haben einige sehr nützliche Hinweise zur Säuberung meines Codes hinzugefügt. Das erreicht jedoch nicht vollständig, wonach ich suche. Mein Ziel ist es, den Gegenstand auf der tiefsten Ebene zu finden, nicht in der ersten Instanz. Also in diesem Beispiel, wenn die Brille auch in tinyWeb.items vorhanden wäre, würde ich das zurückgegeben haben, nicht größerWeb. – Phil

+0

@Phil Ich werde bearbeiten! –

+0

@Phil dies scheint ein bisschen schwierig als Was ist, wenn es "Brille" in zwei verschachtelten Objekte, aber nicht die gleiche Tiefe, sollte die Funktion irgendwelche von ihnen oder nur die tiefe zurückgeben? –

0

Das liegt daran, dass der rekursive Aufruf die Rückgabe der Variablen nicht zuweist. Und Sie sollten die Rückkehr von der rekursiven Aufruf überprüfen und zurückgeben, wenn wahr oder brechen von der for-Schleife, wenn Sie andere Logik danach haben.

function findItem(item, obj) { 
for (var key in obj) { 
    if (obj[key] === item) { 
     return obj; 
    } else if (Array.isArray(obj[key]) === true && obj[key].includes(item) === true) { 
     return obj; 
    } else if (typeof obj[key] === 'object' && Array.isArray(obj[key]) === false) { 
     var foundItem = findItem(item, obj[key]); 
     if(foundItem) 
      return foundItem; 
    } 
} 
+1

das funktioniert nicht, wenn Sie nach 'Kopfhörern' suchen - das' return findItem' sollte bedingt sein –

+1

so etwas wie - 'foundItem = findItem (item, obj [key]); if (foundItem) { return foundItem; } ' –

+0

Das stimmt, ich habe es aktualisiert, damit es auch funktioniert, (y) –

0

„Ich versuche rekursiv ein Objekt zu suchen, die Zeichenfolgen enthält, Arrays, und andere Objekte, um ein Objekt auf der tiefsten Ebene zu finden (einen Wert zu finden), aber ich bekomme als Ergebnis immer undefiniert. "

var foundIt = findItem('glasses', theCobWeb); 
console.log('The item is here: ' + foundIt); // The item is here: undefined 

"Der Artikel ist hier ..." - wo?

Nun, was genau willst du als Rückgabewert? Sollte es nur "glasses" sagen, wenn alles fertig ist? Meiner Meinung nach ist das sinnlos - im Grunde ist es nicht besser, als einfach true oder false zurückzugeben.

Ich schrieb diese Funktion vor einer Weile, weil ich einen Haufen von Daten suchen musste, aber auch genau weiß, wo es zusammenpasst.Ich würde das jetzt wahrscheinlich ein wenig überarbeiten (oder zumindest Typ Anmerkungen hinzufügen), aber es funktioniert wie es ist, also hier gehen Sie.

// helpers 
 
const keys = Object.keys 
 
const isObject = x=> Object(x) === x 
 
const isArray = Array.isArray 
 
const rest = ([x,...xs])=> xs 
 

 
// findDeep 
 
const findDeep = (f,x) => { 
 
    let make = (x,ks)=> ({node: x, keys: ks || keys(x)}) 
 
    let processNode = (parents, path, {node, keys:[k,...ks]})=> { 
 
    if (k === undefined) 
 
     return loop(parents, rest(path)) 
 
    else if (isArray(node[k]) || isObject(node[k])) 
 
     return loop([make(node[k]), make(node, ks), ...parents], [k, ...path]) 
 
    else if (f(node[k], k)) 
 
     return {parents, path: [k,...path], node} 
 
    else 
 
     return loop([{node, keys: ks}, ...parents], path) 
 
    } 
 
    let loop = ([node,...parents], path) => { 
 
    if (node === undefined) 
 
     return {parents: [], path: [], node: undefined} 
 
    else 
 
     return processNode(parents, path, node) 
 
    } 
 
    return loop([make(x)], []) 
 
} 
 

 
// your sample data 
 
var theCobWeb = {biggestWeb: {item: "comb",biggerWeb: {items: ["glasses", "paperclip", "bubblegum"],smallerWeb: {item: "toothbrush",tinyWeb: {items: ["toenails", "lint", "wrapper", "homework"]}}},otherBigWeb: {item: "headphones"}}}; 
 

 
// find path returns {parents, path, node} 
 
let {path, node} = findDeep((value,key)=> value === "glasses", theCobWeb) 
 

 
// path to get to the item, note it is in reverse order 
 
console.log(path) // => [0, 'items', 'biggerWeb', 'biggestWeb'] 
 

 
// entire matched node 
 
console.log(node) // => ['glasses', 'paperclip', 'bubblegum']

Die grundlegende Intuition hier ist node[path[0]] === searchTerm


komplette Pfad zur angepassten Abfrage

wir den gesamten Schlüsselpfad zu den angepassten Daten. Dies ist nützlich, weil wir genau wissen, wo es auf der Wurzel unserer Suche basiert. Um zu überprüfen, der Pfad korrekt ist, finden Sie in diesem Beispiel

const lookup = ([p,...path], x) => 
    (p === undefined) ? x : lookup(path,x)[p] 
lookup([0, 'items', 'biggerWeb', 'biggestWeb'], theCobWeb) // => 'glasses' 

Unübertroffene Abfrage

Hinweis, wenn wir nach etwas zu suchen, die nicht gefunden wird, wird node sein undefined

let {path, node} = findDeep((value,key)=> value === "sonic the hog", theCobWeb) 
console.log(path) // => [] 
console.log(node) // => undefined 

Suchen nach einem bestimmten Schlüssel/Wert-Paar

Die Suchfunktion empfängt ein value und key Argument. Verwenden Sie sie, wie Sie

let {path, node} = findDeep((value,key)=> key === 'item' && value === 'toothbrush', theCobWeb) 
console.log(path) // => [ 'item', 'smallerWeb', 'biggerWeb', 'biggestWeb' ] 
console.log(node) // => { item: 'toothbrush', tinyWeb: { items: [ 'toenails', 'lint', 'wrapper', 'homework' ] } } 

Kurzschluss wünschen - 150cc

Oh, und weil ich dich verderben, findDeep wird eine frühe Rückkehr geben, sobald die erste Übereinstimmung gefunden wird. Es wird keine Berechnungszyklen verschwenden und weiter durch Ihren Datenstapel gehen, nachdem es die Antwort kennt. Das ist eine gute Sache.


Go erkunden

Mut haben, abenteuerlich sein. Die obige findDeep-Funktion gibt auch eine parents-Eigenschaft für zurückgegebenes Objekt. Es ist wahrscheinlich nützlich für Sie in gewisser Hinsicht, aber es ist ein wenig komplizierter zu erklären und nicht wirklich kritisch für die Beantwortung der Frage. Um diese Antwort zu vereinfachen, werde ich nur erwähnen, dass es da ist.

+0

tfw wenn Sie eine corecursive Prozedur schreiben, die kein Spielzeug ist – naomik

+0

Es gibt tatsächlich die Objektreferenz zurück. Also nicht so nutzlos wie du denkst. Danke, dass du den anderen Code aufgenommen hast, es ist interessant. – Phil

Verwandte Themen