如何解决递归数组串联错误JavaScript
任务:给定格式为foo(bar)
的字符串,请在括号内将字符串取反。
我的解决方案有一个错误,我无法理解其原因。
这是我的代码:
const inputs = ["foo(bar)","(bar)","foo(bar)blim","foo(foo(bar))blim","foo(foo(bar)ddas)blim","foo(foo(b(tu)ar)ddas)blim"]
const expected = ["foorab","rab","foorabblim","foobaroofblim","foosaddbaroofblim","foosaddbutaroofblim"]
function reverseParans(input) {
firstLeftParans = input.indexOf('(')
lastRightParans = input.lastIndexOf(')')
if (firstLeftParans == -1) {
if (lastRightParans > -1) {
return ['<MALFORMED>']
} else {
return input.reverse()
}
} else {
if (lastRightParans == -1) {
return ['<MALFORMED>']
} else {
left = input.slice(0,firstLeftParans)
right = input.slice(lastRightParans+1).slice()
middle = reverseParans(input.slice(firstLeftParans + 1,lastRightParans))
return [...left,...middle,...right].reverse()
}
}
}
function process(str) {
let input = str.split('');
let firstLeftParans = input.indexOf('(');
let lastRightParans = input.lastIndexOf(')');
if (firstLeftParans == -1 && lastRightParans == -1) {
return input
} else if ((firstLeftParans > -1 && lastRightParans == -1) || (lastRightParans > -1 && firstLeftParans == -1)) {
return "<MALFORMED>"
}
result = input.slice(0,firstLeftParans).concat(reverseParans(input.slice(firstLeftParans + 1,lastRightParans)),input.slice(lastRightParans + 1))
return result.join('')
}
除最后一个带有嵌套级别的输入外,所有输入均成功。
Input: foo(foo(b(tu)ar)ddas)blim Result: foorabutarbblim Expected: foosaddbutaroofblim Pass: false
我认为我在进行递归调用时错误地变异了一个值,但是我不确定在哪里。
解决方法
当存在多套未套用的括号时,关于此问题的其他一些答案会产生错误的结果-
console.log(process("hello(world)yes(no)yang(yin)"))
// helloniy(gnaynosey)dlrow
console.log(process("ab(cd(ef(gh)))ij(k(lm)n)opqr(stu(vw)x)yz"))
// abxefn<MALFORMED>ji)))hgopqr(stu(vwdcyz
// OH NO!
lex
让我们尝试另一种解决问题的方法。首先lexer
创建一个词素流-
function* lexer(s = "")
{ const leftParen =
{ type: "leftParen" }
const rightParen =
{ type: "rightParen" }
const str = value =>
({ type: "str",value })
let r =
""
for (const c of s)
if (c === "(")
(yield str(r),yield leftParen,r = "")
else if (c === ")")
(yield str(r),yield rightParen,r = "")
else
r = r + c
yield str(r)
}
让我们看看lexer
的工作原理-
lexer("this(is(very(nested)))but(nothere)") // => ...
{ type: "str",value: "this" }
{ type: "leftParen" }
{ type: "str",value: "is" }
{ type: "leftParen" }
{ type: "str",value: "very" }
{ type: "leftParen" }
{ type: "str",value: "nested" }
{ type: "rightParen" }
{ type: "str",value: "" }
{ type: "rightParen" }
{ type: "str",value: "but" }
{ type: "leftParen" }
{ type: "str",value: "nothere" }
{ type: "rightParen" }
{ type: "str",value: "" }
解析
接下来,parser
接受词素并生成抽象语法树-
function parser(lexemes)
{ const concat = _ =>
({ type: "concat",values: [] })
const rev = _ =>
({ type: "rev",values: [] })
const r =
[ concat() ]
for (const t of lexemes)
if (t.type === "str")
r[0].values.unshift(t)
else if (t.type === "leftParen")
r.unshift(rev())
else if (t.type === "rightParen")
if (r.length <= 1)
throw Error("unexpected ')'")
else
r[1].values.unshift(r.shift())
else
throw Error("unexpected lexeme")
if (r.length > 1)
throw Error("expected ')'")
else
return r[0]
}
现在让我们看看parser
的效果-
parser(lexer("this(is(very(nested)))but(nothere)")) // => ...
{ type: "concat",values:
[ { type: "str",value: "" },{ type: "rev",values:
[ { type: "str",value: "nothere" }
]
},{ type: "str",value: "but" },values:
[ { type: "str",values:
[ { type: "str",values:
[ { type: "str",value: "nested" }
]
},value: "very" }
]
},value: "is" }
]
},value: "this" }
]
}
评估
最后eval
递归地评估AST并给出一个值-
function eval(e)
{ if (e.type === "str")
return e.value
else if (e.type === "concat")
return reverse(e.values).map(eval).join("")
else if (e.type === "rev")
return e.values.map(_ => reverse(eval(_))).join("")
else
throw Error(`unexpected expression: ${e}`)
}
让我们看看eval
如何处理我们的示例-
eval(parser(lexer("this(is(very(nested)))but(nothere)"))) // => ...
"thisverydetsensibuterehton"
过程
现在process
只是lexer
,parser
和eval
的组合-
const process = (s = "") =>
eval(parser(lexer(s)))
该程序的工作方式与以前一样-
const inputs =
["foo(bar)","(bar)","foo(bar)blim","foo(foo(bar))blim","foo(foo(bar)ddas)blim","foo(foo(b(tu)ar)ddas)blim"]
const expected =
["foorab","rab","foorabblim","foobaroofblim","foosaddbaroofblim","foosaddbutaroofblim"]
const result =
inputs.map(process) // <-- call process for each input
console.log(expected.join(","))
console.log(result.join(","))
输出-
foorab,rab,foorabblim,foobaroofblim,foosaddbaroofblim,foosaddbutaroofblim
foorab,foosaddbutaroofblim
除了现在,我们的程序适用于更复杂的输入-
console.log(process("hello(world)yes(no)yang(yin)"))
// hellodlrowyesonyangniy
console.log(process("ab(cd(ef(gh)))ij(k(lm)n)opqr(stu(vw)x)yz"))
// abefhgdcijnlmkopqrxvwutsyz
您刚刚编写了自己的编程语言!
char = "a" | "b" | "c" | "d" ...
| "A" | "B" | "C" | "D" ...
| "1" | "2" | "3" | "4" ...
| "!" | "@" | "#" | "$" ...
| ...
string = ""
| char + string
expr = string
| expr + "(" + expr + ")" + expr
在您自己的浏览器中验证结果-
const reverse = (s = "") =>
s.length > 1
? reverse(s.slice(1)).concat(s.slice(0,1))
: s
function* lexer(s = "")
{ const leftParen =
{ type: "leftParen" }
const rightParen =
{ type: "rightParen" }
const str = value =>
({ type: "str",r = "")
else
r = r + c
yield str(r)
}
function parser(lexemes)
{ const concat = _ =>
({ type: "concat",values: [] })
const r =
[ concat() ]
for (const t of lexemes)
if (t.type === "str")
r[0].values.unshift(t)
else if (t.type === "leftParen")
r.unshift(rev())
else if (t.type === "rightParen")
if (r.length <= 1)
throw Error("unexpected ')'")
else
r[1].values.unshift(r.shift())
else
throw Error("unexpected lexeme")
if (r.length > 1)
throw Error("expected ')'")
else
return r[0]
}
function eval(e)
{ if (e.type === "str")
return e.value
else if (e.type === "concat")
return reverse(e.values).map(eval).join("")
else if (e.type === "rev")
return e.values.map(_ => reverse(eval(_))).join("")
else
throw Error(`unexpected expression: ${e}`)
}
const process = (s = "") =>
eval(parser(lexer(s)))
const inputs =
["foo(bar)","foosaddbutaroofblim"]
console.log(expected.join(","))
console.log(inputs.map(process).join(","))
console.log("hellodlrowyesonyangniy")
console.log(process("hello(world)yes(no)yang(yin)"))
console.log("abefhgdcijnlmkopqrxvwutsyz")
console.log(process("ab(cd(ef(gh)))ij(k(lm)n)opqr(stu(vw)x)yz"))
,
“谢谢”的第二个答案可以教给您很多有关编写解析器的知识。我强烈推荐它。
这里的基本问题不能用任何简单的正则表达式解决。括号内的语言不是常规语言;它是上下文无关的语言。但是,我们可以编写一个相当简单的版本,该版本使用采用正则表达式测试和替换的递归调用:
// or use the recursive version from @Thankyou
const reverse = (s) =>
[...s] .reverse () .join ('')
const parens = /\(([^)(]*)\)/
const process = (s) =>
parens .test (s)
? process (s .replace (parens,(_,inner) => reverse (inner)))
: s
// Original sample cases
const inputs = ["foo(bar)","foo(foo(b(tu)ar)ddas)blim"]
const expected = ["foorab","foosaddbutaroofblim" ]
console .log (expected .join (","))
console .log (inputs .map (process) .join (","))
// Extended sample cases from @Thankyou
console. log ("hellodlrowyesonyangniy")
console .log (process ("hello(world)yes(no)yang(yin)"))
console .log ("abefhgdcijnlmkopqrxvwutsyz")
console .log (process ("ab(cd(ef(gh)))ij(k(lm)n)opqr(stu(vw)x)yz"))
parens
是一个正则表达式,它与一个以开放括号开头的子字符串匹配,然后捕获一组零个或多个不包含任何封闭或开放括号的字符,然后是封闭括号。它像这样分解:
// const parens = /\(([^)(]*)\)/
// / / -- a regular expression matching a sub-string
// \( -- starting with an open parenthesis
// ( ) -- then capturing a group of
// * -- zero or more
// [^ ] -- characters not including
// )( -- any close or open parentheses
// \) -- followed by a close parenthesis
换句话说,我们可以使用它来找到任何嵌套括号的最内层,并捕获它们之间的内容。
函数process
使用它来测试是否有任何这样的子字符串。如果没有,我们就完成了,我们将字符串原样返回。如果有的话,我们用内容的相反版本替换该组括号,然后用结果递归调用process
。
您弄乱了范围,因为您没有将变量声明为reverseParans
的局部变量。修改后的代码有效:
const inputs = ["foo(bar)","foosaddbutaroofblim"]
function reverseParans(input) {
let firstLeftParans = input.indexOf('(')
let lastRightParans = input.lastIndexOf(')')
if (firstLeftParans == -1) {
if (lastRightParans > -1) {
return ['<MALFORMED>']
} else {
return input.reverse()
}
} else {
if (lastRightParans == -1) {
return ['<MALFORMED>']
} else {
let left = input.slice(0,firstLeftParans)
let right = input.slice(lastRightParans+1).slice()
let middle = reverseParans(input.slice(firstLeftParans + 1,lastRightParans))
return [...left,...middle,...right].reverse()
}
}
}
function process(str) {
let input = str.split('');
let firstLeftParans = input.indexOf('(');
let lastRightParans = input.lastIndexOf(')');
if (firstLeftParans == -1 && lastRightParans == -1) {
return input
} else if ((firstLeftParans > -1 && lastRightParans == -1) || (lastRightParans > -1 && firstLeftParans == -1)) {
return "<MALFORMED>"
}
result = input.slice(0,firstLeftParans).concat(reverseParans(input.slice(firstLeftParans + 1,lastRightParans)),input.slice(lastRightParans + 1))
return result.join('')
}
,
通过数学归纳递归可以简化您的程序。下面编号的注释对应于代码中的数字-
- 如果找不到左括号和右括号;返回输入字符串
s
- (归纳),如果仅找到一个父级;返回格式错误的字符串结果
- (归纳)左派和右派都找到了;返回左括号前的字符串部分,加上递归结果,再加上右括号后的字符串部分
function process(s = "")
{ const l = s.indexOf("(") // "left"
const r = s.lastIndexOf(")") // "right"
if (l === -1 && r === -1)
return s // 1
else if (l === -1 || r === -1)
return "<MALFORMED>" // 2
else
return s.substring(0,l) // 3
+ reverse(process(s.substring(l + 1,r)))
+ s.substr(r + 1)
}
我们可以将reverse
定义为-
const reverse = (s = "") =>
s.length
? reverse(s.substr(1)) + s.substr(0,1)
: ""
const inputs =
["foo(bar)",foosaddbutaroofblim
展开代码段以在浏览器中验证结果-
const reverse = (s = "") =>
s.length
? reverse(s.substr(1)) + s.substr(0,1)
: ""
function process(s = "")
{ const l = s.indexOf("(")
const r = s.lastIndexOf(")")
if (l === -1 && r === -1)
return s
else if (l === -1 || r === -1)
return "<MALFORMED>"
else
return s.substring(0,l)
+ reverse(process(s.substring(l + 1,r)))
+ s.substr(r + 1)
}
const inputs =
["foo(bar)","))
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。