簡體   English   中英

遞歸連接由抽象語法樹狀結構形成的數組時遇到問題

[英]Trouble with recursively joining an array formed from an abstract syntax tree-like structure

我有一個類似 AST 的粗略結構; an array containing either a string, or an object, where the object has an id and arguments, where the arguments are an array of either a string or an object, as above.

目標是 output 從連接的字符串和對象的字符串中遞歸構建(由於結構的性質)。

更復雜的是,這些對象實際上是 function 調用,具有不同的參數消耗。 但是,output 始終是一個字符串。

我嘗試了多種方法將這種類似 AST 的結構轉換為字符串,但均未成功; 下面是我目前的嘗試,但除了明顯的 output 沖突之外,它不起作用,我無法弄清楚實現目標所需的確切邏輯。

 const argLists = [ [ "one", { id: "$id1", args: [["two", "three"], "4", "3", "$1-"] }, "four", "five", { id: "$id1", args: [["six", "seven"], "3", "4", "$1-"] } ], ["(", "$+", "$1", "$+", "text", "$+", "$2", "$+", ")", "$3-"], [ { id: "$id1", args: [ [ "one", "$+", { id: "$+", args: ["two", { id: "$chr", args: ["44"] }, "three"] }, "$+", "four" ], "4", "5", "$1-" ] } ] ] function joinArgs(args = [], output) { for (let i = 0; i < args.length; i++) { let arg = args[i]; if (typeof arg === "string") { i--; let tempOutput = "", prev = args[i] || "", join = "", next = args[i + 2] || ""; if (typeof prev === "string" && typeof next === "string") { join += arg === "$+"? "": `${arg}`; tempOutput = `${prev}${join}${next}`; } else if (typeof prev === "string" && typeof next === "object") { tempOutput = `${prev}${join}${joinArgs( next.args, output )}`; } else if (typeof prev === "object" && typeof next === "string") { tempOutput = `${joinArgs( prev.args, output )}${join}${next}`; } i += 3; output += tempOutput; } else if (typeof arg === "object") { if (Array.isArray(arg)) { output += joinArgs(arg, output); } else { if (arg.id === "$+") { output += joinArgs(arg.args, output); } else if (arg.id === "$chr") { output += String.fromCharCode(arg.args[0]); } else if (arg.id === "$id1") { const id1Args = []; for ( let id1ArgIdx = 0; id1ArgIdx < arg.args.length; id1ArgIdx++ ) { let id1Arg = arg.args[id1ArgIdx]; if (Array.isArray(id1Arg)) { id1Arg = joinArgs(id1Arg, output).trim(); } id1Args.push(id1Arg); } output += " " // This function is irrelevant to the problem; but it does return a string //id1( //...id1Args.slice(0, id1Args.length - 1), // id1Args[id1Args.length - 1] //); } } } } return output; } argLists.forEach(arg => { console.log(joinArgs(arg, "")); });

測試用例的數據:

 const argLists = [ [ "one", { id: "$id1", args: [["two", "three"], "4", "3", "$1-"] }, "four", "five", { id: "$id1", args: [["six", "seven"], "3", "4", "$1-"] } ], ["(", "$+", "$1", "$+", "text", "$+", "$2", "$+", ")", "$3-"], [ { id: "$id1", args: [ [ "one", "$+", { id: "$+", args: ["two", { id: "$chr", args: ["44"] }, "three"] }, "$+", "four" ], "4", "5", "$1-" ] } ] ];

預期的 output:

 const output = [ "one (two three 4 3 $1-) four five (six seven 3 4 $1-)", "($1text$2) $3-", "(onetwo,threefour 4 5 $1-)" ];

output 應根據以下規則連接:

  1. 如果參數是字符串並且是$+ ,則連接前一個值和下一個值,它們之間沒有空格( prevnext )。
  2. 如果參數是字符串而不是$+ ,則連接上一個、當前和下一個值,它們之間有一個空格( prev current next )。
  3. 如果參數是 object,則從args屬性中獲取其 arguments 並根據需要進行遞歸,但是:
    1. 如果 object id 是$+ ,則加入所有不帶空格的 arguments ,根據需要根據參數遞歸。
    2. 如果 object id 是$chr ,則返回String.fromCharCode() ,並將提供給 object 的唯一參數提供給該 static 方法。
    3. 如果 object id 為$id1 ,則根據上述規則將第一個參數視為數組(如果是數組),並將所有剩余的 arguments 用空格連接起來,並將所有內容括在括號中。
    4. 從技術上講,對象的 id 可能不是前三種可能性中的任何一種,但這種可能性不是問題,因為 function object 調用總是會返回一個字符串。

這適用於您提供的所有測試用例。

每當出現$+時,只需將其留在字符串中,然后在末尾替換所有出現的它。 這樣就不必處理任何“上一個”或“下一個”的事情,這確實增加了復雜性。 對於下面的代碼片段,它每次都會在遞歸 function 返回之前刪除它們。

使用遞歸函數的關鍵是要記住最深的遞歸 function 調用首先返回,並且在這種情況下不斷構建更大的字符串並向上堆棧。 這行代碼,

outputString += "(" + joinArgs(current_arg.args) + ") ";

我真的認為有助於理解下面的代碼是如何工作的。 請注意,右括號后面有一個尾隨空白字符,如果右括號是最終字符串中的最后一個字符,則會將其修剪掉。

 const argLists = [ [ "one", { id: "$id1", args: [["two", "three"], "4", "3", "$1-"] }, "four", "five", { id: "$id1", args: [["six", "seven"], "3", "4", "$1-"] } ], ["(", "$+", "$1", "$+", "text", "$+", "$2", "$+", ")", "$3-"], [ { id: "$id1", args: [ [ "one", "$+", { id: "$+", args: ["two", { id: "$chr", args: ["44"] }, "three"] }, "$+", "four" ], "4", "5", "$1-" ] } ] ]; function joinArgs(args = []) { outputString = ""; for (let i = 0; i < args.length; i++) { var current_arg = args[i]; //check for string condition if (typeof current_arg === 'string') { outputString += current_arg + " "; } //check for object condition if (typeof current_arg === "object") { if (current_arg.id == '$id1'){ //if object has $id1 id, call joinArgs with the object's args as args parameter //surround object's returned output with parenthesis outputString += "(" + joinArgs(current_arg.args) + ") "; } //no recursive call needed here if (current_arg.id == '$chr') { outputString += "$+ " + String.fromCharCode(current_arg.args); } //call joinArgs with object args as args parameter if (current_arg.id == '$+') { outputString += joinArgs(current_arg.args); } // if object is array call joinArgs with the array as args paramater if (Array.isArray(current_arg)) { outputString += joinArgs(current_arg); } } } //clean up outputString to remove unwanted " $+ " outputString = outputString.replace(/ \$\+ /g, ""); //remove whitespace before closing parenthesis outputString = outputString.replace(/ \)/g, "\)"); return outputString; } argLists.forEach(arg => { //trim leading and trailing whitespace astString = joinArgs(arg).trim(); console.log(astString); });

此版本將通用值的處理分離為對 arrays、普通對象和其他值(剛剛返回)的處理。主joinArg function 調用joinObjectjoinArray ,每個都可以遞歸回調joinArg

我認為joinObject中的細分足以清楚地說明如何在需要時添加其他情況。 它還有一個默認處理的位置,您提到但沒有指定。

 const joinObject = ({id, args}) => (({ '$+': (ns) => ns.map (joinArgs).join (''), '$chr': ([n]) => String.fromCharCode (n), '$id1': ([ns, ...more]) => `(${joinArgs (ns)} ${more.map (joinArgs).join (' ')})`, }) [id] || ((args) => args.map (joinArgs).join (' '))) (args) // `----------------------------------------' // `--- default. Not sure what belongs here const joinArray = (args) => args.reduce( ({str, skip}, arg) => ({ str: str + (skip || arg == '$+'? '': ' ') + (arg == '$+'? '': joinArgs(arg)), skip: arg == '$+' }), {str: '', skip: true} ).str const joinArgs = (args) => Array.isArray (args)? joinArray (args): Object (args) === args? joinObject (args): args const argLists = [["one", {id: "$id1", args: [["two", "three"], "4", "3", "$1-"]}, "four", "five", {id: "$id1", args: [["six", "seven"], "3", "4", "$1-"]}], ["(", "$+", "$1", "$+", "text", "$+", "$2", "$+", ")", "$3-"], [{id: "$id1", args: [["one", "$+", {id: "$+", args: ["two", {id: "$chr", args: ["44"]}, "three"]}, "$+", "four"], "4", "5", "$1-"]}]] argLists.forEach(args => console.log(joinArgs(args)))

這將處理您提供的樣本。 我很想知道它是否滿足您的全部需求,或者是否仍需要針對其他情況進行調整。

這里的大技巧是相互遞歸。 它可以很好地簡化您在這種情況下編寫的代碼。 另一個有用的技術是 object 持有為不同的 Object id值運行的處理程序。 我發現這對於將邏輯的主要部分與將它們粘合在一起的部分分離非常有用。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM