[英]Recursive array concatenation error (JavaScript)
Task: Given a string of the form foo(bar)
reverse the string inside the parentheses.任务:给定一个
foo(bar)
形式的字符串,反转括号内的字符串。
My solution has an error that I'm unable to understand the cause of.我的解决方案有一个我无法理解原因的错误。
Here's my code:这是我的代码:
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('')
}
All inputs succeed except the last one with an extra level of nesting.除了最后一个具有额外嵌套级别的输入之外,所有输入都成功。
Input: foo(foo(b(tu)ar)ddas)blim Result: foorabutarbblim Expected: foosaddbutaroofblim Pass: false
I think I'm mistakenly mutating a value while making the recursive calls but I'm not sure where.我认为我在进行递归调用时错误地改变了一个值,但我不确定在哪里。
Some other answers on this question produce an incorrect result when there are multiple sets of unnested parens -当有多组未嵌套的括号时,有关此问题的其他一些答案会产生不正确的结果-
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法
Let's try another way of approaching the problem.让我们尝试另一种解决问题的方法。 First
lexer
creates a stream of lexemes -第一个
lexer
创建一个词位的 stream -
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)
}
Let's take a look at how lexer
works -让我们看看
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: "" }
{ type: "rightParen" }
{ type: "str", value: "but" }
{ type: "leftParen" }
{ type: "str", value: "nothere" }
{ type: "rightParen" }
{ type: "str", value: "" }
parse解析
Next, parser
takes the lexemes and produces an abstract syntax tree -接下来,
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]
}
Let's see the effect of parser
now -现在让我们看看
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" },
{ type: "rev"
, values:
[ { type: "str", value: "" }
, { type: "rev"
, values:
[ { type: "str", value: "" }
, { type: "rev"
, values:
[ { type: "str", value: "nested" }
]
}
, { type: "str", value: "very" }
]
}
, { type: "str", value: "is" }
]
}
, { type: "str", value: "this" }
]
}
eval评估
Finally eval
recursively evaluates the AST and spits out a value -最后
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}`)
}
Let's see how eval
handles our example -让我们看看
eval
如何处理我们的示例 -
eval(parser(lexer("this(is(very(nested)))but(nothere)"))) // => ...
"thisverydetsensibuterehton"
process过程
Now process
is simply a combination of lexer
, parser
, and eval
-现在
process
只是lexer
、 parser
和eval
的组合 -
const process = (s = "") =>
eval(parser(lexer(s)))
The program works just like before -该程序就像以前一样工作 -
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(", "))
Output - Output -
foorab, rab, foorabblim, foobaroofblim, foosaddbaroofblim, foosaddbutaroofblim
foorab, rab, foorabblim, foobaroofblim, foosaddbaroofblim, foosaddbutaroofblim
Except now our program works for more complicated inputs -除了现在我们的程序适用于更复杂的输入 -
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
You just made your own programming language!您刚刚制作了自己的编程语言!
char = "a" | "b" | "c" | "d" ...
| "A" | "B" | "C" | "D" ...
| "1" | "2" | "3" | "4" ...
| "!" | "@" | "#" | "$" ...
| ...
string = ""
| char + string
expr = string
| expr + "(" + expr + ")" + expr
Verify the results in your own browser -在您自己的浏览器中验证结果 -
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", 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) } 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] } 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)", "(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"] 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"))
The second answer from "Thank you" can teach you a great deal about writing parsers. “谢谢”的第二个答案可以教你很多关于编写解析器的知识。 I highly recommend it.
我强烈推荐它。
And the basic problem here cannot be solved with any simple regular expression.而且这里的基本问题不能用任何简单的正则表达式来解决。 The language of balanced parentheses is not a regular language;
平衡括号的语言不是常规语言; it is a context-free language.
它是一种上下文无关的语言。 However, we can write a fairly simple version that uses recursive calls employing regular expression tests and replacements:
但是,我们可以编写一个使用正则表达式测试和替换的递归调用的相当简单的版本:
// 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)", "(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" ] 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
is 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. parens
是一个正则表达式,匹配以左括号开头的子字符串,然后捕获一组零个或多个字符,不包括任何右括号或左括号,然后是右括号。 It breaks down like this:它像这样分解:
// 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
In other words, we can use this to find the innermost of any nested set of parentheses, and capture the content between them.换句话说,我们可以使用它来查找任何嵌套括号的最里面,并捕获它们之间的内容。
The function process
uses this to test if there is any such sub-string. function
process
使用它来测试是否存在任何此类子字符串。 If there is not, we're done and we return the string intact.如果没有,我们就完成了,我们原封不动地返回字符串。 If there is we replace that set of parentheses with a reversed version of the contents and recursively call
process
with the result.如果有,我们用内容的反转版本替换那组括号,并用结果递归调用
process
。
You messed up your scoping, because you didn't declare your variables as being local to reverseParans
.您搞砸了范围,因为您没有将变量声明为
reverseParans
的本地变量。 This modified code works:此修改后的代码有效:
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) {
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('')
}
Recursion by mathematical induction can simplify your program.数学归纳法递归可以简化你的程序。 The numbered comments below correspond to the numbers in the code -
下面的编号注释对应于代码中的数字 -
s
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)
}
We can define reverse
as -我们可以将
reverse
定义为 -
const reverse = (s = "") =>
s.length
? reverse(s.substr(1)) + s.substr(0, 1)
: ""
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(", "))
Output - Output -
foorab, rab, foorabblim, foobaroofblim, foosaddbaroofblim, foosaddbutaroofblim
foorab, rab, foorabblim, foobaroofblim, foosaddbaroofblim, foosaddbutaroofblim
Expand the snippet to verify the result in your browser -展开代码段以在浏览器中验证结果 -
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)", "(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(", "))
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.