简体   繁体   English

递归数组连接错误 (JavaScript)

[英]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只是lexerparsereval的组合 -

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 -下面的编号注释对应于代码中的数字 -

  1. If neither left nor right paren is found;如果既没有找到左括号也没有找到右括号; return the input string, s返回输入字符串, s
  2. (induction) if only one paren is found; (归纳)如果只找到一个括号; return malformed string result返回格式错误的字符串结果
  3. (induction) both left and right paren are found; (归纳)左右括号都找到了; return the string portion before the left paren, plus the recursive result, plus the string portion after the right paren返回左括号之前的字符串部分,加上递归结果,加上右括号之后的字符串部分
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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM