Microbubu的迷你实验室

尽可能让代码自然语言化

字数统计: 1.5k阅读时长: 6 min
2018/12/29 Share

前几天同学过来,于是请了几天假,加上紧接着的元旦假期,工作时间比较闲暇,因此有时间上网看看资料,学学Python。尽管Python相对简单,但是对于我这种一直使用C#的程序员来说,使用起来并不是很舒服,具体体现在下面几点:

  1. 相比C#和Java,Python规范性上欠佳,比如其特殊的双下划线方法,用起来非常不自在,需要一段时间适应;
  2. Python中有非常优秀的列表推导及生成器等语法,之前没有这么用过,所以不习惯,然而我非常喜欢这样的写法;
  3. 没有了强大的Visual Studio支撑,编码效率比不上C#,但是Python作为脚本语言完成任务所需的代码量却要比C#、Java这种大型语言少很多,因为一般的小型任务一般不需要考虑写一堆类出来,这一点也是其优势。但是得益于VS Code强大的扩展,使用VS Code编写Python代码也能达到VS的80%的水平。

啃了两天《流畅的Python》之后发现这本书还是偏向高级内容,所以我又找了本《Python基础教程》看。概览后发现,不管是《流畅的Python》还是《Python基础教程》,在很多地方都体现出了Python的理念——将代码语言化,这也是很多其他高级语言追求的目标,但Python在这方面做的更好,特别是其列表推导式:

1
numbers_less_than_10 = [num for num in range(10)]

上面这行代码浅显易懂,可以理解为:对于0~9中的每一个数,将其加入到一个列表中,完全比其他语言的for循环在语义上要明确很多,比如C#写出来语义就没这么接近人类的自然语言:

1
2
3
4
5
List<int> numbersLessThan10 = new List<int>();
for(int index = 0; index < 10; index++)
{
numbersLessThan10.Add(index);
}

虽然你也可以不这么麻烦的写成类似下面这样(需要using System.Linq):

1
List<int> numbersLessThan10 = Enumerable.Range(0, 10).ToList();

但是这里的List<int>还是没有Python中的方括号简单直接。既然了解到了将代码写的更加自然语言化所带来的优势,那么平时的代码编写就需要仔细思考、多多总结。

恰好昨天看到一个比较简单的编程题目(一道华为的社招机试题),我这里就尽可能用比较能接近人类自然语言的方式将其代码化:

1
2
3
4
5
6
7
8
9
10
骰子有6个面,现在用1,2,3,4,5,6分别代表一个骰子的左,右,前,后,上,下的初始位置。
骰子置于桌面,从顶部观察。用R代表向右滚动一次,用L代表向左滚动一次,用F表示向前翻转1次,B表示向后翻转1次,
用A表示逆时针旋转90度,用C表示顺时针旋转90度。
现从初始状态开始,根据输入的动作序列,计算得到最终的状态。
输入描述:
初始状态:123456
输入只包含LRFBAC的字母序列,最大长度为50,可重复
输出描述:输出最终状态
输入例子:RA
输出例子:436512

现用C#语言实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
public class DiceSurfaceStatus
{
//Left,Right,Front,Back,Top,Bottom
private int[] surfaceStatus = { 1, 2, 3, 4, 5, 6 };

private void SwapArrayTwoElements(int[] array, int index1, int index2)
{
int temp = array[index1];
array[index1] = array[index2];
array[index2] = temp;
}

private void SwapSurface(Surface surface1, Surface surface2)
=> SwapArrayTwoElements(surfaceStatus, (int)surface1, (int)surface2);

public string RotateTo(string directions)
{
foreach (var item in directions.ToUpper())
Rotate(item);
var result = string.Empty;
foreach (var item in surfaceStatus)
result += item.ToString();
return result;
}

private void Rotate(char direction)
{
switch (direction)
{
case 'L':
TurnLeft();
break;
case 'R':
TurnRight();
break;
case 'F':
TurnFront();
break;
case 'B':
TurnBack();
break;
case 'A':
RotateLeft();
break;
case 'C':
RotateRight();
break;
}
}

private void TurnLeft()
{
SwapSurface(Surface.Top, Surface.Left);
SwapSurface(Surface.Top, Surface.Bottom);
SwapSurface(Surface.Top, Surface.Right);
}

private void TurnRight()
{
SwapSurface(Surface.Top, Surface.Right);
SwapSurface(Surface.Top, Surface.Bottom);
SwapSurface(Surface.Top, Surface.Left);
}

private void TurnFront()
{
SwapSurface(Surface.Front, Surface.Bottom);
SwapSurface(Surface.Front, Surface.Back);
SwapSurface(Surface.Front, Surface.Top);
}

private void TurnBack()
{
SwapSurface(Surface.Front, Surface.Top);
SwapSurface(Surface.Front, Surface.Back);
SwapSurface(Surface.Front, Surface.Bottom);
}

//逆时针旋转
private void RotateLeft()
{
SwapSurface(Surface.Front, Surface.Right);
SwapSurface(Surface.Front, Surface.Back);
SwapSurface(Surface.Front, Surface.Left);
}

//顺时针旋转
private void RotateRight()
{
SwapSurface(Surface.Front, Surface.Left);
SwapSurface(Surface.Front, Surface.Back);
SwapSurface(Surface.Front, Surface.Right);
}

enum Surface
{
Left,
Right,
Front,
Back,
Top,
Bottom
}
}

这里我为了代码阅读起来更像书籍,做了一些额外的工作:

  1. 使用枚举表示骰子的六个面,使枚举的值正好对应到保存骰子状态的数组surfaceStatus的下标,这样后续的交换操作只需要传入当前要交换的面的枚举值即可完成;

  2. 将switch块中的内容概括为独立的方法,增强可读性。比如向左翻转(TurnLeft)、向右翻转(TurnRight)、向前翻转(TurnFront)、向后翻转(TurnBack)以及顺时针旋转(RotateRight)和逆时针旋转(RotateLeft);

  3. 各个方法的名称需要尽量能概括出所对应的操作。比如2中列举的几个方法,另外还有翻转一次(Rotate)和按照一定的顺序翻转到一个新状态(RotateTo)等方法;

  4. 尽可能自然语言化代码。比如交换数组中两个元素的方法(SwapArrayTwoElements),虽然名称已经足够清楚并易于使用,但是将其扩展成新方法(SwapSurface)之后会使其意图更加清楚,调用前者需要使用数字下标(SwapArrayTwoElements(array,0,1)),但是使用后者一看就知道是在做什么(SwapSurface(Surface.Front,Surface.Left),将前面的面和左边的面交换位置)。

目前为止,虽然我对自己这样的实现方法比较满意,但是这种语言化代码的思想需要今后的经验积累,这也是我的一项长期目标——没有标准答案,尽力而为。

CATALOG