#javascript #recursion #abstract-syntax-tree
#javascript #рекурсия #abstract-syntax-tree
Вопрос:
У меня есть грубая структура, подобная AST; массив, содержащий либо строку, либо объект, где объект имеет идентификатор и аргументы, где аргументы представляют собой массив либо строки, либо объекта, как указано выше.
Цель состоит в том, чтобы вывести строку из соединенных строк и строк объектов, построенных рекурсивно (из-за природы структуры).
Еще одна сложность заключается в том, что объекты на самом деле являются вызовами функций с разным потреблением аргументов. Однако вывод всегда представляет собой строку.
Я пробовал различные методы перевода этой AST-подобной структуры в строку, но безуспешно; ниже приведена моя текущая попытка, но она не работает, поскольку, помимо очевидных выходных конфликтов, у меня возникают проблемы с определением точной логики, которая мне нужна для достижения цели.
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" amp;amp; typeof next === "string") {
join = arg === "$ " ? "" : `${arg}`;
tempOutput = `${prev}${join}${next}`;
} else if (typeof prev === "string" amp;amp; typeof next === "object") {
tempOutput = `${prev}${join}${joinArgs(
next.args,
output
)}`;
} else if (typeof prev === "object" amp;amp; 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-"
]
}
]
];
Ожидаемый результат:
const output = [
"one (two three 4 3 $1-) four five (six seven 3 4 $1-)",
"($1text$2) $3-",
"(onetwo,threefour 4 5 $1-)"
];
Выходные данные должны быть объединены в соответствии с этими правилами:
- Если аргумент является строкой и является a
$
, объедините предыдущее и следующее значения без пробела между ними(prevnext
) . - Если аргумент является строкой и не является a
$
, объедините предыдущее, текущее и следующее значения с пробелом между ними(prev current next
) . - Если аргумент является объектом, получите его аргументы из
args
свойства и повторите по мере необходимости, однако:- Если идентификатор объекта
$
равен, объедините все аргументы без пробелов, повторяя по мере необходимости для каждого аргумента. - Если идентификатор объекта
$chr
равен, возвращайтесьString.fromCharCode()
с единственным аргументом, предоставленным объекту, заданному этому статическому методу. - Если идентификатор объекта
$id1
равен, обработайте первый аргумент как массив в соответствии с приведенными выше правилами (если это массив), а все остальные аргументы объедините пробелом и заключите все в круглые скобки. - Технически возможно, что идентификатор объекта может не совпадать ни с одной из предыдущих трех возможностей, но такая вероятность не является проблемой, поскольку функция, вызываемая объектом, всегда будет возвращать строку.
- Если идентификатор объекта
Ответ №1:
Эта версия разделяет обработку общего значения на обработку массивов, простых объектов и других значений (которые только что возвращаются). Основная joinArg
функция вызывает joinObject
и joinArray
, каждая из которых может рекурсивно вызывать обратный 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)))
Это обрабатывает предоставленные вами образцы. Мне любопытно посмотреть, полностью ли он отражает ваши потребности или его все еще нужно будет настроить для других случаев.
Большой трюк здесь — взаимная рекурсия. Это может значительно упростить код, который вы пишете в подобных обстоятельствах. Другим полезным методом является объект, содержащий обработчики для запуска для разных id
значений объекта. Я нахожу это очень полезным для отделения основных частей логики от того, что склеивает их вместе.
Ответ №2:
Это работает для всех предоставленных вами тестовых примеров.
Всякий $
раз, когда появляется, просто оставьте его в строке, а затем замените все его вхождения в конце. Это избавляет от необходимости иметь дело с выполнением любых «предыдущих» или «следующих» вещей, которые imo действительно усложняют. В случае приведенного ниже фрагмента кода он удаляет их каждый раз прямо перед возвратом рекурсивной функции.
Ключ в том, чтобы помнить, что с рекурсивными функциями сначала возвращается самый глубокий рекурсивный вызов функции, и в этом случае продолжает наращивать большую строку вверх и вверх по стеку. Эта строка кода,
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);
});
Комментарии:
1. Вы, очевидно, знаете, как работает рекурсия. Мне, вероятно, не нужно было ничего объяснять