【问题标题】:Replace nested string with some rules用一些规则替换嵌套字符串
【发布时间】:2019-12-18 16:21:44
【问题描述】:

字符串中有3条规则:

  1. 包含单词或组(用括号括起来),组可以嵌套;

  2. 如果单词或组之间有空格,则这些单词或组应附加“+”。

例如:

"a b" needs to be "+a +b"
"a (b c)" needs to be "+a +(+b +c)"
  1. 如果单词或组之间有|,则这些单词或组应该用括号括起来。 例如:
"a|b" needs to be "(a b)"
"a|b|c" needs to be "(a b c)"

考虑所有规则,这里是另一个例子:

"aa|bb|(cc|(ff gg)) hh" needs to be "+(aa bb (cc (+ff +gg))) +hh"

我尝试过使用正则表达式、堆栈和recursive descent parser logic,但仍然无法完全解决问题。

谁能分享一下这个问题的逻辑或伪代码?

新编辑: 一个更重要的规则:竖线具有更高的优先级。

例如:

aa|bb hh cc|dd (a|b) 必须是+(aa bb) +hh +(cc dd) +((a b))

(aa dd)|bb|cc (ee ff)|(gg hh) 必须是+((+aa +dd) bb cc) +((+ee +ff) (+gg +hh))

新编辑: 为了解决优先级问题,我想办法在调用 Sunil Dabburi 的方法之前添加括号。

例如:

aa|bb hh cc|dd (a|b) 将是 (aa|bb) hh (cc|dd) (a|b)

(aa dd)|bb|cc (ee ff)|(gg hh) 将是 ((aa dd)|bb|cc) ((ee ff)|(gg hh))

由于性能对我的应用程序来说不是一个大问题,因此这种方式至少可以使它对我有用。我想JavaCC 工具可以很好地解决这个问题。希望其他人可以继续讨论并贡献这个问题。

【问题讨论】:

  • 你的堆栈有什么问题?
  • 我使用堆栈检查括号是否平衡。但我不知道如何将所有段正确地推入堆栈。所以我使用树状结构来存储字符串的所有段。但是后来我不知道如何构造输出。

标签: java parsing syntax nested stack


【解决方案1】:

这是我的尝试。根据您的示例和我提出的一些示例,我相信在规则下它是正确的。我通过将问题分成两部分解决了这个问题。

  1. 解决我假设字符串仅包含单词或仅包含单词的组的情况。
  2. 通过替换子组来解决单词和组,使用 1) 部分并使用子组递归地重复 2)。
    private String transformString(String input) {
        Stack<Pair<Integer, String>> childParams = new Stack<>();
        String parsedInput = input;
        int nextInt = Integer.MAX_VALUE;
        Pattern pattern = Pattern.compile("\\((\\w|\\|| )+\\)");
        Matcher matcher = pattern.matcher(parsedInput);
        while (matcher.find()) {
            nextInt--;
            parsedInput = matcher.replaceFirst(String.valueOf(nextInt));
            String childParam = matcher.group();
            childParams.add(Pair.of(nextInt, childParam));
            matcher = pattern.matcher(parsedInput);
        }

        parsedInput = transformBasic(parsedInput);
        while (!childParams.empty()) {
            Pair<Integer, String> childGroup = childParams.pop();
            parsedInput = parsedInput.replace(childGroup.fst.toString(), transformBasic(childGroup.snd));
        }
        return parsedInput;
    }

    // Transform basic only handles strings that contain words. This allows us to simplify the problem
    // and not have to worry about child groups or nested groups.
    private String transformBasic(String input) {
        String transformedBasic = input;
        if (input.startsWith("(")) {
            transformedBasic = input.substring(1, input.length() - 1);
        }

        // Append + in front of each word if there are multiple words.
        if (transformedBasic.contains(" ")) {
            transformedBasic = transformedBasic.replaceAll("( )|^", "$1+");
        }
        // Surround all words containing | with parenthesis.
        transformedBasic = transformedBasic.replaceAll("([\\w]+\\|[\\w|]*[\\w]+)", "($1)");
        // Replace pipes with spaces.
        transformedBasic = transformedBasic.replace("|", " ");
        if (input.startsWith("(") && !transformedBasic.startsWith("(")) {
            transformedBasic = "(" + transformedBasic + ")";
        }
        return transformedBasic;
    }

通过以下测试用例验证:

@ParameterizedTest
    @CsvSource({
            "a b,+a +b",
            "a (b c),+a +(+b +c)",
            "a|b,(a b)",
            "a|b|c,(a b c)",
            "aa|bb|(cc|(ff gg)) hh,+(aa bb (cc (+ff +gg))) +hh",
            "(aa(bb(cc|ee)|ff) gg),(+aa(bb(cc ee) ff) +gg)",
            "(a b),(+a +b)",
            "(a(c|d) b),(+a(c d) +b)",
            "bb(cc|ee),bb(cc ee)",
            "((a|b) (a b)|b (c|d)|e),(+(a b) +((+a +b) b) +((c d) e))"
    })
    void testTransformString(String input, String output) {
        Assertions.assertEquals(output, transformString(input));
    }

    @ParameterizedTest
    @CsvSource({
            "a b,+a +b",
            "a b c,+a +b +c",
            "a|b,(a b)",
            "(a b),(+a +b)",
            "(a|b),(a b)",
            "a|b|c,(a b c)",
            "(aa|bb cc|dd),(+(aa bb) +(cc dd))",
            "(aa|bb|ee cc|dd),(+(aa bb ee) +(cc dd))",
            "aa|bb|cc|ff gg hh,+(aa bb cc ff) +gg +hh"
    })
    void testTransformBasic(String input, String output) {
        Assertions.assertEquals(output, transformBasic(input));
    }

【讨论】:

  • 我使用占位符的方式不是理想的解决方案,因为可能存在歧义。您始终可以通过在占位符上附加和前置其他字符来解决这个问题,以使它们独一无二。
  • 你解决了我的问题真是太棒了!我已经测试过了,它奏效了!我唯一修改的是让它支持Unicode字符和一些特殊符号,就是把正则表达式从\\w改成a-zA-Z0-9*?$\u0080-\u9fff-。非常感谢,我会将此标记为我接受的答案。
【解决方案2】:

我试图解决这个问题。不确定它是否适用于所有情况。使用问题中给出的输入进行验证,效果很好。

  1. 我们需要先格式化管道。这将有助于添加必要的括号和间距。
  2. 作为管道处理的一部分生成的空间可能会干扰我们表达式中可用的实际空间。所以用 $ 符号来掩盖它们。
  3. 要处理空格,需要单独处理括号,这很棘手。所以我遵循的方法是从外到内找一组括号。

所以通常我们有&lt;left_part&gt;&lt;parantheses_code&gt;&lt;right_part&gt;。现在left_part 可以为空,类似right_part 可以为空。我们需要处理这种情况。 此外,如果right_part 以空格开头,我们需要根据空格要求在left_part 中添加“+”。

注意:我不确定(a|b) 的预期是什么。如果结果应该是((a b))(a b)。我纯粹根据它的定义选择((a b))

现在这里是工作代码:

public class Test {

    public static void main(String[] args) {
        String input = "aa|bb hh cc|dd (a|b)";
        String result = formatSpaces(formatPipes(input)).replaceAll("\\$", " ");
        System.out.println(result);
    }

    private static String formatPipes(String input) {

        while (true) {
            char[] chars = input.toCharArray();
            int pIndex = input.indexOf("|");

            if (pIndex == -1) {
                return input;
            }

            input = input.substring(0, pIndex) + '$' + input.substring(pIndex + 1);

            int first = pIndex - 1;
            int closeParenthesesCount = 0;
            while (first >= 0) {

                if (chars[first] == ')') {
                    closeParenthesesCount++;
                }

                if (chars[first] == '(') {
                    if (closeParenthesesCount > 0) {
                        closeParenthesesCount--;
                    }
                }

                if (chars[first] == ' ') {
                    if (closeParenthesesCount == 0) {
                        break;
                    }
                }
                first--;
            }

            String result;

            if (first > 0) {
                result = input.substring(0, first + 1) + "(";
            } else {
                result = "(";
            }

            int last = pIndex + 1;
            int openParenthesesCount = 0;
            while (last <= input.length() - 1) {

                if (chars[last] == '(') {
                    openParenthesesCount++;
                }

                if (chars[last] == ')') {
                    if (openParenthesesCount > 0) {
                        openParenthesesCount--;
                    }
                }

                if (chars[last] == ' ') {
                    if (openParenthesesCount == 0) {
                        break;
                    }
                }
                last++;
            }

            if (last >= input.length() - 1) {
                result = result + input.substring(first + 1) + ")";
            } else {
                result = result + input.substring(first + 1, last) + ")" + input.substring(last);
            }

            input = result;
        }
    }

    private static String formatSpaces(String input) {

        if (input.isEmpty()) {
            return "";
        }

        int startIndex = input.indexOf("(");
        if (startIndex == -1) {
            if (input.contains(" ")) {
                String result = input.replaceAll(" ", " +");
                if (!result.trim().startsWith("+")) {
                    result = '+' + result;
                }
                return result;
            } else {
                return input;
            }
        }

        int endIndex = startIndex + matchingCloseParenthesesIndex(input.substring(startIndex));

        if (endIndex == -1) {
            System.out.println("Invalid input!!!");
            return "";
        }

        String first = "";
        String last = "";
        if (startIndex > 0) {
            first = input.substring(0, startIndex);
        }
        if (endIndex < input.length() - 1) {
            last = input.substring(endIndex + 1);
        }

        String result = formatSpaces(first);
        String parenthesesStr = input.substring(startIndex + 1, endIndex);
        if (last.startsWith(" ") && first.isEmpty()) {
            result = result + "+";
        }

        result = result + "("
                + formatSpaces(parenthesesStr)
                + ")"
                + formatSpaces(last);

        return result;
    }

    private static int matchingCloseParenthesesIndex(String input) {
        int counter = 1;
        char[] chars = input.toCharArray();
        for (int i = 1; i < chars.length; i++) {
            char ch = chars[i];
            if (ch == '(') {
                counter++;
            } else if (ch == ')') {
                counter--;
            }

            if (counter == 0) {
                return i;
            }
        }
        return -1;
    }

}

【讨论】:

  • 非常感谢!解决问题的方法很封闭! (a|b) 的期望可以是 ((a b)) 或 (a b),没关系。但我忘了提到垂直线的优先级高于空格。例如:aa|bb hh cc|dd (a|b) 需要为+(aa bb) +hh +(cc dd) +((a b))
  • 根据您的解决方案,我尝试为 | 添加括号在调用您的方法之前进行分组。结果现在看起来不错。 :) 再次感谢!
  • 明白。我已经更新了我的代码以满足要求。请测试并告诉我。您不需要在 | 周围添加括号了。代码应该这样做。谢谢!
  • 感谢您的更新!我对它进行了一些测试,发现如果中间有很多垂直线,它会产生很多括号。例如:a|b|c|d|e => ((((a b c d e))))。如果可以是a b c d e,那就太好了
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多