为什么这个算法不符合其规范?如何尝试证明其正确性?

时间:2017-11-28 20:40:53

标签: java computer-science correctness proof-of-correctness

  

编写返回String的方法 getLargeAndSmallLetters   包含以下所有大写和小写ASCII字母   形式(由空格分隔的大写和小写字母,字母   用逗号分隔的组,最后没有逗号):

     

a a,B b,C c,D d,E e,...,Z z

算法规范没有详细说明实现细节,即StringBuilder结构的使用,算法必须满足的效率指南或任何有关实现可读性的最佳实践。

我编译并返回所需输出的算法版本

public static String getLargeAndSmallLetters() {
    StringBuilder builder = new StringBuilder();
    for (int i = 64; i < 96; ++i) { // WRONG: hard coded
        if (Character.isLetter(i)) {
            String upper = Character.toString((char)i);
            String lower = Character.toString((char)(i+32));
            builder.append(upper + " " + lower + ", "); // WRONG: in loop
        }
    }
    String result = builder.toString();
    result = result.substring(0, result.length() - 2);
    return result;
 }

我的讲解员可能希望看到的是这样的for循环

for (char lower = 'a'; lower <= 'z'; ++lower) {
    Character upper = Character.toUpperCase(lower);
    builder.append(upper)
           .append(" ")
           .append(lower)
           .append(", ");
}

为什么算法会在学术上成为&#34;这样更正确吗?这也可以通过简单的字符串连接来解决。

我首先想了解为什么我的解决方案不是最佳(但正确 IMO)。拥有C#背景,并且只能粗略地猜测为什么我的代码不是最佳的原因:

  • for循环经历了太多次迭代。我纠正了这个丑陋的&#34;但使用if (Character.isLetter(i))的正确方法。
  • 它不可读
  • 它将int转换为char s。是否会发生任何影响性能的拳击/拆箱?
  • 在Java中实现String的方式,我是通过在我的String内使用+运算符的字符串连接在字符串池中创建更多不可变for的环?使用StringBuilder会更有效率,因为它会在内部更有效地重用" "", " String吗?

现在我还想做的是提供一个&#34;学术论点&#34;这证明了我答案的正确性。这里有一些想法:

  1. 编译器优化了我的错误&#34;离开并实际为&#34;错误&#34;生成相同的字节代码。并且&#34;纠正&#34;回答
  2. JIT编译器为&#34;正确&#34;执行相同的机器代码。并且&#34;错误&#34;回答实施
  3. 提供算法的数学proof of correctness
  4. 选项3似乎是证明我的答案正确性的最直接的方式。

    对于选项1和2,我认为我需要将for循环更正为for (int i = 65; i < 91; ++i)并删除if (Character.isLetter(i))语句。

    我将非常感谢您对我的想法的任何建议,补充和更正。

2 个答案:

答案 0 :(得分:1)

  

编译器优化了我的错误&#34;离开并实际为&#34;错误&#34;生成相同的字节代码。并且&#34;纠正&#34;答案

不,编译器通过优化来非常少。它可能会将您的某些字符串连接更改为StringBuilder appends,但这是关于它的;否则,只需忠实地以字节码形式再现您在代码中编写的内容。基本上所有的智能都发生在JIT中。

您可以很容易地看到他们不会生成相同的字节码:使用javap -c <YourClass>对其进行反编译。

  

JIT编译器为&#34;正确&#34;执行相同的机器代码。并且&#34;错误&#34;回答实施

JIT最终会执行代码。对于这样的事情,它可能会执行一次,因此不值得JIT&#39。

但我强烈怀疑JIT会为这两种方法生成相同的代码,因为这两种方法完全不同。

  

提供算法正确性的数学证明

Java的正式证明很难。在证明中你必须考虑到许多微妙的语义;或者,至少证明那些语义不涉及代码的执行。

你所能做的就是断言它在功能上是等价的。如同,不能证明它是正确的,但证明它不正确。

为此编写测试非常容易,因为您知道正确的输出:

A a, B b, C c, ...

因此,请检查您的代码是否产生了该代码,并且您已完成。或者,当然,检查您的代码是否产生与模型答案相同的输出。

您的答案实际上与模型答案完全相同。你从最后砍掉了,;模型答案没有。

答案 1 :(得分:1)

你正在打一场失败的战斗。如果您的“docent”无法检查您的代码是否产生正确的输出,那么他就不会费心去阅读算法正确性的证明。如果你从中学到一件事,那就是你经常要遭遇傻瓜,所以要善于做。

也就是说,通过显示(a)它不接受任何输入(b)它不受副作用的影响(更现实地,不是那些也不影响你的讲解员解决方案的那些),可以证明代码的正确性。然后通过显示(c)算法的实际执行跟踪得出正确的输出(正确的是它产生与你的讲师相同的东西)来结束。

鉴于此,没有充分的理由选择一种实施方式。如果您更喜欢性能,简洁或优雅的模型答案,那么这个问题的解决方案比您或您的医生找到的更好,更简单:

public static String getLargeAndSmallLetters() {
    return "A a, B b, C c, D d, E e, F f, G g, H h, I i, J j, K k, L l, M m, N n, O o, P p, Q q, R r, S s, T t, U u, V v, W w, X x, Y y, Z z";
}

你可以使它更具可读性:

public static String getLargeAndSmallLetters() {
    return String.format("%s%s",
        "A a, B b, C c, D d, E e, F f, G g, H h, I i, J j, K k, L l, M m, ",
        "N n, O o, P p, Q q, R r, S s, T t, U u, V v, W w, X x, Y y, Z z");
}

注意:for - 循环遍历char是非常糟糕的,你不应该这样做。我不能说我的同事在他的代码中尝试了这一点,但如果那一天到来,我会感谢同行代码审查。