表示器中的归一化


为了让只有非本质差异的解答拥有相同的表示,表示器应该应用规范化。一般来说,创建规范化表示的过程如下:

  • 将解答的代码解析为抽象语法树(AST)
  • 对 AST 应用规范化
  • 将规范化后的 AST 转换为字符串
  • 将规范化后的 AST 字符串写入名为representation.txt的文件(参见接口)

不过要注意,表示并不_一定_非得是 AST,它也可以是规范化后的普通代码,哪种最适合你的学习路径就用哪种。

为了帮你上手,下面我们来介绍一些常见的规范化策略。

注意 1:这些规范化示例之间没有递进关系,每个示例只展示一种具体的规范化。真正的表示器会希望依次应用它们。

注意 2:这些指南中的代码将使用 C# 编写,但指南本身与具体语言无关。

规范化标识符

为了让表示与命名无关,用户自定义的名称(例如变量、函数等)可以替换为占位符。这种情况下,应该生成一个mapping.json(参见接口)。

需要注意的是,所有相同的名称都必须替换为同一个占位符,无论它们位于哪个作用域。

源代码

public static class Fake
{
    public static int Test(int input)
    {
        var test = input + 2;
        return test;
    }
}

表示

public static class PLACEHOLDER_1
{
    public static int PLACEHOLDER_2(int PLACEHOLDER_3)
    {
        var PLACEHOLDER_4 = PLACEHOLDER_3 + 2;
        return PLACEHOLDER_4;
    }
}

规范化空白

空白不一致的情况非常常见,因此规范化空白是很常见的一个规范化步骤。 行尾符也应该规范化。

源代码

using   System;

     public static    class Fake
{
    public     static   DateTime Add    (DateTime    birthDate)
    {
        return birthDate.Add( TimeSpan.FromSeconds   (   10  ) )   ;
    }
}

表示

public static class Fake
{
    public static DateTime Add(DateTime birthDate)
    {
        return birthDate.Add(TimeSpan.FromSeconds(10));
    }
}

规范化代码块

在很多语言中,用户可以自行决定如何定义代码块(或作用域)。例如,在大多数类 C 语言中,作用域是用大括号括起来的。通常,把大括号放在同一行还是下一行都无所谓,因此可以对此进行规范化。

源代码

public static class Fake {
    public static int Test() {
        if (1 > 2) {
            return 0;
        }

        return 1;
    }
}

表示

public static class Fake
{
    public static int Test()
    {
        if (1 > 2)
        {
            return 0;
        }

        return 1;
    }
}

移除无关紧要的代码

并非所有代码片段对表示器来说都有意义,这些片段可以被移除。举例来说,在大多数语言中,注释都无关紧要,可以安全地移除。

源代码

/*
   These are some very nice
   comments spanning multiple lines
*/
public static class Fake
{
    // Nice method
    public static string Test()
    {
        return "Test"; // This is very nice
    }
}

表示

public static class Fake
{
    public static string Test()
    {
        return "Test";
    }
}

在顺序无关紧要时规范化顺序

在某些情况下,代码的顺序无关紧要。为了防止同样的代码因顺序不同而产生不同的表示,对它进行排序可能会有用。通常需要排序的是函数或声明。找到排序所依据的衡量标准可能很棘手,实现起来也同样棘手。一种衡量标准可以是节点包含多少个 AST 子节点,另一种可以是第一个子节点的类型名称。

这个示例按照某种函数长度进行排序:

源代码

public static class Fake
{
    public static string Test2()
    {
        int a = "Test2";
        return a;
    }

    public static string Test()
    {
        return "Test";
    }
}

表示

public static class Fake
{
    public static string Test()
    {
        return "Test";
    }

    public static string Test2()
    {
        int a = "Test2";
        return a;
    }
}