2011年7月

什么是字母算术?或者可以被称为 Cryptarthms 或者 Alphametics,如下面这样的就是:

HAWAII + IDAHO + IOWA + OHIO == STATES
510199 + 98153 + 9301 + 3593 == 621246

其字母与数字的对应关系为:

H = 5
A = 1
W = 0
I = 9
D = 8
O = 3
S = 6
T = 2
E = 4

solve() 函数

import re
import itertools
def solve(puzzle):
words = re.findall('[A-Z]+', puzzle.upper())
unique_characters = set(''.join(words))
assert len(unique_characters) <= 10, 'Too many letters'
first_letters = {word[0] for word in words}
n = len(first_letters)
sorted_characters = ''.join(first_letters) + 
''.join(unique_characters - first_letters)
characters = tuple(ord(c) for c in sorted_characters)
digits = tuple(ord(c) for c in '0123456789')
zero = digits[0]
for guess in itertools.permutations(digits, len(characters)):
if zero not in guess[:n]:
equation = puzzle.translate(dict(zip(characters, guess)))
if eval(equation):
return equation
if __name__ == '__main__':
import sys
for puzzle in sys.argv[1:]:
print(puzzle)
solution = solve(puzzle)
if solution:
print(solution)

solve() 函数详解

words = re.findall('[A-Z]+', puzzle.upper())
unique_characters = set(''.join(words))

获取到所有字母,再将其保存到一个 SET 中,这个是该程序要做的第一件事情,在这里我们就使用re 模块的 findall() 函数,它接受一个正则表达式和一个字符串作为参数,并且取出所有该字符串中出现该模式的地方并保存到一个列表中。

对于取出之后的列表,我们还不能接着下一步工作,因为我们需要知道每一个字母对该的数字是什么,而不是一个字符串对应什么数字,所以我们需要将所有字符串中出现的字母取出来,这个时候可以用到 set ,它可以去除重复的值,还使用了字符串的 join() 函数,它将多个字符串使用一个空字符连接到一起,由于在Python中,字符串就是一个字符的序列,所以它可以直接被用来作为 set() 的参数,我们最后得到的将是一个以字母为元素的 Set 实例。

>>> words = re.findall('[A-Z]+', 'SEND + MOre = Money'.upper())
>>> words
['SEND', 'MORE', 'MONEY']
>>> set(words)
{'MONEY', 'SEND', 'MORE'}
>>> unique_charactors = set(''.join(words))
>>> unique_charactors
{'E', 'D', 'M', 'O', 'N', 'S', 'R', 'Y'}

接下来我们需要做的是判断字母数量是否大于 10 个,这个原因是:每一个不同的字母都必须对应一个不同的数字,然后总共只有 0 – 9 这 10 个数字,所以,如果字母数大于 10 个的话,那肯定会有多余的字母没有数字与之对应,也就不可能形成字母算术了。

这里使用了一个 assert 语句,该语句后面可以跟任何全法的 Python 表达式,如果表达式的结果为 True,那么该 assert 语句就与普通的表达式一样,但是如果运行结果为 False 的话,该语句将会抛出AssertionError 异常,assert 语句后面还可以再添加一个异常说明的字符串,当抛出异常时,该字符串将一并返回:

>>> assert len(unique_charactors) <= 10
>>> assert len(unique_charactors) >= 10, 'Aha, AssertionError has been catched'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AssertionError: Aha, AssertionError has been catched

上面产生错误的代码其实与下面这个代码是待价的:

if len(unique_charactors) < 10:
raise AssertionError('Aha, AssertionError has been catched')

再接下来,就是很有趣的东西了,这是一个排列组合的问题,先获得总计有多少个字母,然后将就可以知道,对多有多少种可能的结果了,因为每一个字母对应一个数字,所以,我们可以把问题科化为0-9十个数字有多少种 N 个值的排列方法,这个N 就是字母的个数,在这里我们使用了一个十分有趣的函数: itertools.permutations()

itertools.permutaions() 函数

先来看一段代码:

>>> for i in itertools.permutations('ABC',2): print(i)
...
('A', 'B')
('A', 'C')
('B', 'A')
('B', 'C')
('C', 'A')
('C', 'B')
>>> for i in itertools.permutations('ABC',3): print(i)
...
('A', 'B', 'C')
('A', 'C', 'B')
('B', 'A', 'C')
('B', 'C', 'A')
('C', 'A', 'B')
('C', 'B', 'A')
>>> for i in itertools.permutations('ABC',4): print(i)
...

从上面的代码中可以看到,该函数接受一个序列(可以是任何迭代器或者列表等数据)以及一个排序的元素数目的参数,比如上例中第一段代码,我们就是将’A’,‘B’,‘C’三个字母进行排列,排列仅仅只有两个元素,而且每一个元素不能相同,permutations 会将鳘次排列返回,这个要让我们自己去写排列的话,还真的不简单,不过 Python 已经帮我们把这些都得很好了。

在本文的代码中,将每一个排列进行测试,如果得到正确的结果,刚停止运行并返回结果。

把所有东西放在一起

总的来说: 这个程序通过暴力解决字母算术谜题, 也就是通过穷举所有可能的解法。为了达到目的,它

  1. 通过re.findall()函数找到谜题中的所有字母
  2. 使用集合和set()函数找到谜题出现的所有不同的字母
  3. 通过assert语句检查是否有超过10个的不同的字母 (意味着谜题无解)
  4. 通过一个生成器对象将字符转换成对应的ASCII码值
  5. 使用itertools.permutations()函数计算所有可能的解法
  6. 使用translate()字符串方法将所有可能的解转换成Python表达式
  7. 使用eval()函数通过求值Python 表达式来检验解法
  8. 返回第一个求值结果为True的解法

最近遇到的一个问题是这样的:应用有一个 config.py 文件,在这个文件里面定义了下面这个参数:

#: Installed modules
INSTALLED_MODULES = [
{'name': 'main', 'prefix': ''},
]

这是我的一个 Flask 项目中使用到的,在我的项目中,有很多个模块,比如 user , group 等,但是我并不是在任何一个站点上都需要所有的这些模块,所以我就在配置文件里面加入了上面的配置,只有这里面的模块才被安装,并且,这里面没有的模块我就不再导入进来,所以就需要动态的导入模块了,这个时候我以前使用的 import 或者 from package import module 等都不能工作。

要解决这个问题,有两个办法,一个是使用 exec 声明,如下面这样的:

>>> sys
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'sys' is not defined
>>> s = 'import sys'
>>> exec s
>>> sys
<module 'sys' (built-in)>

这个办法可以解决,但是还有另一个更好的办法就是使用 __import__() 函数:

该函数与 import 声明很相似,但是它是一个实实在在的函数,我们可以对导入过程更加细致的控制,比如我最上面所说的需求可以像下面这样实现:

for m in INSTALLED_MODULES:
mod = __import__(m['name'])
app.register_blueprint('mod',prefix = m['prefix'])

另外,如果我们有另一个需求,需要导入一个指定的列表内的所有的模块,还可以使用一个叫做map() 的函数:

>>> module_names = ['sys','os','re','unittest']
>>> module_names
['sys', 'os', 're', 'unittest']
>>> modules = map(__import__, module_names)
>>> modules
[<module 'sys' (built-in)>, <module 'os' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/os.pyc'>, <module 're' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/re.pyc'>, <module 'unittest' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/unittest/__init__.pyc'>]
>>> modules[0].version
'2.7.1 (r271:86832, Jun 16 2011, 16:59:05) n[GCC 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2335.15.00)]'
>>> import sys
>>> sys.version
'2.7.1 (r271:86832, Jun 16 2011, 16:59:05) n[GCC 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2335.15.00)]'