9.第九章 案例分析-文字游戏

9. 案例分析-文字游戏

本章介绍第二个案例分析, 讲述的示通过搜索具有某种特性的单词来解决单词谜题这一话题.
例如, 我们会寻找英语单词中最长的回文单词, 还有搜索那些其字母按字母表顺序排列的单词.
另外, 我会介绍另一种程序开发计划: 缩减问题规模, 回归成之前解决过的问题.
9.1 读取单词列表
为本章的练习, 我们需要准备一个英文单词列表.
互联网上有很多可用的单词列表, 但最适合我们的目标的单词列表, 
是有Grady Ward收集整理并作为Moby词典项目(参看: http://wikipedia.org/wiki/Moby_Project)
的一部分贡献给公共域的. 它包含113 809个正式的填字游戏用词,
即那些认为可以用于横纵填字游戏和其他类型文字游戏用词. 
在Moby集合中, 文件名是113808of.fic;
可以从 https://github.com/AllenDowney/ThinkPython2/blob/master/code/words.txt
下载一个副本, 但文件名是更简单的words.txt.
这个文件是纯文本, 所以可以使用文本编辑器打开, 也可以使用Python读入它.
内置函数open接收文件名作为参数, 并返回一个文件对象(file object), 可以用来读取文件.
# 文件的路径字符串前面加上一个r, 后续会学习到的. 可以看得出我的文件放在桌面了.
>>> fin = open(r'C:\Users\13600\Desktop\words.txt')

fin是用来文件对象作为输入源时常用的名称.
文件对象提供了几个方法用于读取内容, 包括readline, (读取文件中一行的数据)
它会从文件里读入字符, 直到获得换行符为止, 并读入的结果作为一个字符串返回.
>>> fin.readline()
'aa\n'

在这个特定的列表中, 第一个单词是'aa', 它是一种火山熔岩.
序列'\n'表示一个空格字符, 表示换行. 用于把这个单词和其他单词分隔开.
(老的系统上可能是\r\n, 表示回车与换行. 新系统中\n包含了前面两个动作.)
文件对象会记录它读到文件的哪个位置, 因此如果再次调用readline, 会得到下一个单词:
>>> fin.readline()
'aah\n'

下一个单词是'aah', 也是一个完全合法的单词, 所以别用奇怪的眼光看着我.
或者, 如果是那个几个空白字符在干扰你, 可以使用字符串的方法strip去掉它们:
>>> line = fin.readline()
# strip()默认移除前后的空格字符, \n很特殊属于空格字符, 所以不需要 str.strip('\n').
>>> word = line.strip()
>>> word
'aahed'

你也可以在for循环中使用文件对象. 下面的代码读入words.txt并每行打印出一个单词:
fin = open(r'C:\Users\13600\Desktop\words.txt')
count = 0
# for循环遍历得到的对象line, 等同于readline()得到的值.
for line in fin:
    word = line.strip()
    count = count + 1
    print(word)

print(count)  # 113 783个单词.

9.2 练习
在下一节里有这些练习的解答. 在继续阅读解答之前应当至少尝试一些每一个练习.
1. 练习1
编程一个程序, 读入words.txt并且打印出那些长度超出20个字符的单词(不算空白字符).
fin = open(r'C:\Users\13600\Desktop\words.txt')
for line in fin:
	word = line.strip()
	if len(word) > 20:
        print(word)
    
# 终端运行显示:
counterdemonstrations
hyperaggressivenesses
microminiaturizations
2. 练习2
1939, Ernest Vincent Wright出版了一本5万字的小说Gadsby, 这本书里没有包含字母'e'.
因为'e'是英语中最常见的字母, 所以这并不是件容易的事.

实际上, 不使用这最常见的字母的话, 仅仅是构建一条单独的构思也是很难的事情.
开始时会很慢很艰难, 但保持谨慎和长时间的训练, 你可以渐渐掌握方法. (很多废话不写了)
写一个函数has_no_a, 当给定的单词不包含字母'e', 返回True.
def has_no_e(word):
    
	for letter in word:
		if letter == 'e':
			return False
        
	return True
    
修改前一节练习中的代码, 打印出不含'e'的单词, 并计算这种单词在整个单词表中的百分比.
def has_no_e(word):
    for letter in word:
        if letter == 'e':
            return False

    return True


# 计数器1, 统计所有的单词, 初始化变量的值为0.
count = 0
# 计数器2, 统计不含e的单词, 初始化变量的值为0.
count_not_e = 0

fin = open(r'C:\Users\13600\Desktop\words.txt')

# 遍历文件对象.
for line in fin:
    word = line.strip()
    count = count + 1
    if has_no_e(word):
        count_not_e = count_not_e + 1

print('单词总数:', count)  # 单词总数: 113783
print('不含e的单词总数:', count_not_e)  # 不含e的单词总数: 37621
print('不含e的单词百分比', count_not_e / count * 100, '%')  
# 不含e的单词百分比 33.063814453828776 %

3. 练习3
编写一个函数avoids, 接收一个单词, 以及一个包含禁止字母的字符串, 当单词不含任何禁止字母时, 返回True.
def avoids(word, letter):
    if letter not in word:
        return True


print(avoids('hello', 'e'))  # None
print(avoids('hello', 'a'))  # True

修改你的程序, 提示用户输入包含禁止字母的字符串(输入多个字母), 并打印出不包含任何禁止字母的单词个数.
prohibit_character = input('输入禁止的字母>>>:')


def avoids(word, prohibit_letter):
    for letter in prohibit_letter:
        if letter in word:
            return False

    return True


fin = open(r'C:\Users\13600\Desktop\words.txt')

prohibit_words_num = 0

# 遍历文件对象.
for line in fin:
    line = line.strip()
    is_prohibit_words = avoids(line, prohibit_character)
    if is_prohibit_words:
        prohibit_words_num = prohibit_words_num + 1

print('不包含任何禁止字母的单词个数为:', prohibit_words_num)

能不能找到5个禁止字母的组合, 它们排除的单词最少? (计算出所有单词字母出现最少的, 把他们禁止掉).
fin = open(r'C:\Users\13600\Desktop\words.txt')
# fin.read() 读取所有数据.
all_data = fin.read()
for i in range(97, 123):
    print(chr(i), all_data.count(chr(i)))
    
a 68574
b 17794
c 34281
d 34548
e 106752
f 12706
g 27832
h 20186
i 77392
j 1780
k 9366
l 47003
m 24739
n 60505
o 54538
p 25789
q 1632
r 64963
s 86526
t 57029
u 31151
v 9186
w 8533
x 2700
y 13473
z 3750
其中出现最少的5个字母为:
w	8533
z	3750
x	2700
j	1780
q	1632

4. 练习4
编写一个名为user_only的函数, 接收一个单词以及字母组成的字符串, 当单词只由这些单词组成时返回True.
你可以造一个句子, 其单词只由字母acefhlo组成吗? 除了'Hoe alfalfa'之外呢? (英语不好就不纠结了)
def user_only(word1, word2):
    # 遍历第一个单词的元素.
    for i in word1:
        # 单词的元素不在字母序列中, 返回False.
        if i not in word2:
            return False

    return True


print(user_only('hello', 'eloh'))  # True
print(user_only('helloA', 'eloh'))  # False

5. 练习5
编写一个名为user_all的函数, 接收一个单词以及有需要的字母组成的字符串,
当单词中所有需要的字母都出现了至少一次时返回True(单词必须由第二个参数提供的字母组成). 
有多少单词使用了所有的元音字母aeiou? 而aeiouy呢?
def user_all(word1, word2):
    for letter in word2:
        if letter not in word1:
            return False

    return True


print(user_all('hellohello', 'helo'))  # True

def user_all(word1, word2):
    for letter in word2:
        if letter not in word1:
            return False

    return True


fin = open(r'C:\Users\13600\Desktop\words.txt')

count = 0
for line in fin:
    word = line.strip()
    is_qualified = user_all(line, 'aeiou')
    if is_qualified:
        count = count + 1

print(count)  

# aeiou  的结果为 598
# aeiouy 的结果为 42

6. 练习6
编写一个名叫is_abecedarian的函数, 如果单词中的字母是按照字母表顺序排列的(两个重复字母也可以),
则返回True, 有多少这样的单词?
def is_abecedarian(word):
    j = 0
    for i in word:
        # a-z, 越后面的单词, 其对应的十进制越大.
        letter_num = ord(i)
        if letter_num >= j:
            j = letter_num

        else:
            return False

    return True


print(is_abecedarian('abc'))  # True
print(is_abecedarian('abca'))  # False

def is_abecedarian(word1):
    j = 0
    for i in word1:
        # a-z, 越后面的单词, 其对应的十进制越大.
        letter_num = ord(i)
        if letter_num >= j:
            j = letter_num

        else:
            return False

    return True


fin = open(r'C:\Users\13600\Desktop\words.txt')

count = 0
for line in fin:
    # 必须去除\n
    word = line.strip()
    if is_abecedarian(word):
        count = count + 1

print(count)  # 596

9.3 搜索
前面一节中所有练习都有一个共同点; 他们可以使用我们在8.6节中介绍的搜索模式来解决. 最简单的例子是:
# 字符串不能含有e.
def has_no_e(word):
    for letter in word:
        if letter == 'e':
            return False

    return True


print(has_no_e('hello'))  # False

for循环遍历单词word中的字符. 如果我们找打字母'e', 可以立刻返回False; 否则只能继续下一个字母.
如果正常退出了循环, 则说明我们没有找打'e', 所以返回True.
使用in操作符, 可以把这个函数写得更简洁. 
上面这个示例没有写得更简洁是因为想要展现搜索模式的逻辑.

avoids是has_no_e的更通用的版本, 它们的结构相同:
# 子符串, 禁止的字母 组. 
def avoids(word, forbidden):
    for letter in word:
        if letter in forbidden:
            return False

    return True


print(avoids('hello', 'helo'))  # False

一旦发现一个禁止的字母, 可以理解返回False; 如果运行到循环结束, 则返回True.
uses_only函数也类似, 只是它条件判断的意思是相反的:
# 字符串, 必须出现的字母 组.
def uses_only(word, available):
    for letter in word:
        # not in 字母不在这个字母组中.
        if letter not in available:
            return False

    return True


print(uses_only('hello', 'helo'))  # True

它接收的参数并不是一个禁止的字母列表, 而是一个可用的字母列表available.
如果我们发现单词中遇到了并不输入available的字母, 则可以返回False.

uses_all函数也类似, 但单词和字母列表的角色相反.
# 字符串, 组成字符串的字母.
def user_all(word, required):
    # 从字母组中遍历字母
    for letter in required:
        # 判断字母是否在word中, 有一个字母不在, 则返回False.
        if letter not in word:
            return False
	
    # 全部存在则返回True.
    return True


print(user_all('hello', 'h'))  # True

我们不再遍历单词word中的字母, 而是循环遍历必需的单词列表required.
如果单词列表中有任何字母没有出现在单词中, 我们可以返回False.

如果你真的像计算机科学家那样思考的话, 
因该已经发现, uses_all实际上是以及解决的问题的一个特例, 并且可以这么写:
def uses_only(word, available):
    for letter in word:
        # not in 字母不在这个字母组中.
        if letter not in available:
            return False

    return True


def uses_all(word, required):
    return uses_only(required, word)


print(uses_all('hello', 'h'))  # True

这是被称为问题回归到已解决问题↓, 的程序开发计划的一个例子.
(reduction to a previously solved problem)

意即你需要识别出的当前问题是一个已经解决的问题的特例, 从而可以直接利用现有的解决方案.
9.4 使用下标循环
在前面一节的例子中, 我使用for循环进行遍历, 因为只需要字符串中的字符, 而不需要操作下标.
但对is_abecedarian函数我们需要比较相邻的字母, 使用for循环比较困难:
def is_abecedarian(word):
    previous = word[0]

    for c in word:
        if c < previous:
            return False
        previous = c

    return True


print(is_abecedarian('abc'))  # True
print(is_abecedarian('aabbcc'))  # True
print(is_abecedarian('acb'))  # False

或者也可以使用递归:
def is_abecedarian(word):
    if len(word) <= 1:
        return True

    if word[0] > word[1]:
        return False

    return is_abecedarian(word[1:])


print(is_abecedarian('abc'))  # True
print(is_abecedarian('aabbcc'))  # True
print(is_abecedarian('acb'))  # False

还有一个办法是使用while循环:
def is_abecedarian(word):
    i = 0
    while i < len(word) - 1:
        if word[i + 1] < word[i]:
            return False
        i = i + 1

    return True


print(is_abecedarian('abc'))  # True
print(is_abecedarian('aabbcc'))  # True
print(is_abecedarian('acb'))  # False

循环开始于i = 0, 并结束于 i = len(word) -1.
每次跌代时, 比较第i个字符(可以看成是当前字符)和i+1个字符(可以看成是下一个字符).

如果下一个字符比当前小(即按照字母顺序在前), 则我们发现了一个破坏字母顺序的断点, 可以返回False.

如果我们没有找打任何断点而结束循环, 则这个单词通过了测试.
为了说服自己循环是正确结束, 可以考虑像'flossy'这样的例子.
这个单词的长度是6, 所以最后一次循环时i是4, 即是倒数第二个和最后一个字符, 这正是我们所期待的.
下面是is_palindrome函数(参考练习6-3)的一个版本, 它使用两个下标;
一个从0开始递增; king一个从最后开始递减.
# 练习6-3.
def first(word):
    """返回字符串的第一个字符."""
    return word[0]


def last(word):
    """返回字符串的最后一个。"""
    return word[-1]


def middle(word):
    """返回字符串的除第一个和最后一个字符之外的所有字符."""
    return word[1:-1]


def is_palindrome(word):
    """如果单词是回文,则返回True."""
    # word的长度为0或1, 返回True. 0个单词和1个单词就是回文(正看, 反看都一样).
    if len(word) <= 1:
        return True
    # 第一个字符串不等于最后一个字符串, 返回False.
    if first(word) != last(word):
        return False
    # 递归调用, 排查第一个字符串和最后一个字符串, 
    # 如allen,则去掉首尾得到'lle', 将'lle'最为参数继续递归.
    return is_palindrome(middle(word))


print(is_palindrome('allen'))  # False

# 新版本.
def is_palindrome(word):
    i = 0
    j = len(word) - 1
    # 结束的条件为 i = j
    while i < j:
        if word[i] != word[j]:
            return False

        i = i + 1
        j = j - 1

    return True


print(is_palindrome('allen'))  # False

print(is_palindrome('bob'))  # True

或者, 我们可以将其回归到已解决的问题, 可能这么写: (使用8-11调试中的is_reverse)
# 修改正确的is_reverse函数.
def is_reverse(word1, word2):
    if len(word1) != len(word2):
        return False

    i = 0
    j = len(word2) - 1

    while j >= 0:

        if word1[i] != word2[j]:
            return False
        i = i + 1
        j = j - 1
    return True


def is_palindrome(word):
    print(is_reverse(word, word))


is_palindrome('allen')  # False
is_palindrome('bob')  # True

9.5 调试
测试程序很难. 本章中的函数相对容易测试, 因为可以简单地手动验证结果.
即便如此, 要选择一组可以测试到所有可能的错误的单词, 也是很困难的, 甚至是不可能的.
举has_no_e作为例子, 有两个很明显的用例可以检查: 
包含'e'的单词因该返回False;
不包含'e'的应当返回True.
为这两种情况找打具体的单词没有问题.
但对每种情况来说, 也存在一些不那么明显的具体情况.
在所有包含'e'的单词中, 你因该测试以'e'在单词中部的情况.
你应当测试长单词, 短单词以及非常短的单词, 如空字符串.
空字符串是特殊情形(special case)的一个例子. 
特殊情形往往不那么明显, 但有常常隐藏着错误.
除了自己生成的测试用例之外, 还可以使用类似words.txt这样的单词表来测试你的程序.
通过扫描输出, 可能会发现错误, 但请注意: 
你可能发现一种类型的错误(不应该被包含但却被包含的单词),
但对另一种类型的则不能发现(应该被包含, 但却没有出现的单词).
后两段话的意思是:
使用单词表进行测试可以发现一些类型的错误,
例如单词表中不应该包含但程序输出结果中却包含的单词. 
但是, 使用单词表进行测试可能无法发现其他类型的错误.
例如单词表中应该包含但程序输出结果中却没有出现的单词.
因此, 在进行测试时需要综合考虑多种测试方法, 并注意测试的局限性.

单词表测试并不能保证发现所有类型的错误.
单词表可能包含某些类型的单词, 但不包含其他类型的单词.
因此, 使用单词表进行测试时, 需要注意这一点, 同时需要采用其他的测试方法,
如随机生成输入数据, 边界条件测试等.

此外, 当使用单词表进行测试时, 需要考虑单词表本身的质量.
单词表中可能包含一些错误的单词, 或者存在大小写, 拼写等方面的问题, 这些都可能影响测试的质量.
因此, 在选择单词表时需要仔细选择, 并进行必要的处理和过滤.
总之, 测试可以帮助你发现bug, 但生成一组号的测试用例并不容易.
而且, 即使有好的测试用例, 也无法确定程序是完全正确的.
引用一个计算机科学家的话: 程序测试可以用来显示bug的存在, 但无法显示它们的缺席! 
9.6 术语表
文件对象(file object): 用来表示一个打开的文件的值.

将问题回归到已解决问题(reduction to a previously solved problem):
	通过把问题表述为已经解决的某个问题的特例 解决问题的一种方式.

特殊情形(special case): 一种不典型或者不明显(因此更可能没有正确)的测试用例.
9.7 练习
1. 练习7
本练习中的问题是基于广播节目<<车迷天下>>(Car Talk)中出现的一个谜题而设计的
(http://www.cartalk.com/content/puzzlers):
给我一个包含3组连续成对的单词. 我会给你几个几乎可以达到要求却还差一点儿的词作为例子.
例如, 单词committer, 即c-o-m-m-i-t-t-e-e. 除了i不满足条件外, 这个单词是一个好例子.
或者Mississippi: M-i-s-s-i-s-s-i-p-p-i. 如果你能够拿掉其中的i, 则它也符合要求.
但确实有这么一个单词, 并且就我所知, 它可能是满足这个条件的唯一的单词.
当然也可能存在500, 但我只能想到一个它是什么呢?
编写一个程序来找打它, 解答: https://github.com/AllenDowney/ThinkPython2/blob/master/code/cartalk1.py
def is_triple_double(word):
    i = 0
    letter_num = len(word) - 2
    double_num = 0

    while i <= letter_num:
        j = i + 1
        print('True', i, j)
        if word[i] == word[j]:
            # 但比较出一组相同的字符后, 比较它后面两组的字符是否相同.
            double_num = double_num + 1
            i = i + 2

            if double_num == 3:
                return word

        else:
            # i = i + 1 减去2 * double_num, 程序末尾详细解答.
            i = i + 1 - 2 * double_num
            print('False', i)
            # 重置计算器
            double_num = 0


def find_triple_double():
    """读取单词列表并打印带有三个双字母的单词."""
    fin = open(r'C:\Users\13600\Desktop\words.txt')
    for line in fin:
        word = line.strip()
        if is_triple_double(word):
            print(word)


print('三个连续的双字母:')
find_triple_double()

"""
b o o k k a e e p e r
0 1 2 3 4 5 6 7 8 9 10

第一次比较: i = 0, j = 1, 比较的结果为False, double_num = 0, 执行 i = 0 + 1 - (2 * 0), i为1.
第二次比较: i = 1, j = 2, 比较的结果为True, double_num = 1, 执行 i = 1 + 2, i为3.
第三次比较: i = 3, j = 4, 比较的结果为True, double_num = 2, 执行 i = 3 + 2, i为5.
第四次比较: i = 5, j = 6, 比较的结果为False, double_num = 2, 执行 i = 5 + 1 - 2 * 2, i为2,
double_num重置为0.

b o o o k k e e p e r 
0 1 2 3 4 5 6 7 8 9 10

当 i = 1, j = 2 比较时, 计较结果为True. i 加 2.
当 i = 3, j = 4 比较时, 计较结果为False, 可2和3还是连续的, 所以将i设置为合适的值.
找到合适的规律, 
第一次找到两个重复的单词并直接去判断后两组单词是否为重复的.
重复被打断了的后, 就回到第一次重复的的单词的后一个字母处, 让他与自己相邻的(后一个)字母做比较.

"""

2. 练习2
下面是另一个<<车迷天下>>中的谜题(http://www.cartalk.com/content/puzzlers):
有一天我正在高速公路上开车, 碰巧注意到里程表. 和大部分里程表一样, 它显示6位整数的英里数.
所以, 例如我的车有300 000英里里程, 则会看到3-0-0-0-0-0.
那天我看到里程数很有意思. 我发现最后4位数是回文的, 也就是说, 它们不论是正序还是逆序地看都一样.
例如, 5-4-4-5是一个回文, 所以我的里程表可能显示为 3-1-5-4-4-5.
1英里之后, 5位数组成一个回车. 例如, 它可以是3-6-5-4-4-5.
在过1英里, 6位数的中间4位是一个回文. 而接下来, 你准备好了吗? 1英里过去, 所有的6位数都成了回文!
问题是, 我第一次看里程表时, 它的显示数是多少?

编写一个Python程序, 检测全部的6位数, 并打印出可以满足上面这些要求的数字.
解答: https://raw.githubusercontent.com/AllenDowney/ThinkPython2/master/code/cartalk2.py
第一次, 后四位是回文,
(行驶了一英里后)
第二次, 后五位是回文.
(行驶了一英里后)
第三次, 中间四位是回文.
(行驶了一英里后)
第四次, 6位数是回文.
# 回文检测
def is_palindrome(mile, start, end):
    # 将数字转为字符串, 再切片.
    num_str = str(mile)
    # 切片后最短也要4位数. (这里不理解你删除代码看看结果就明白了)
    if len(num_str) >= 4:
        slice_str = num_str[start:end]
        if slice_str == slice_str[::-1]:
            return True


s1 = '第一回看到的英里数可能是(它后四位是一个回文):'
s2 = '再行驶一英里后, 后五位是一个回文:'
s3 = '再行驶一英里后, 中间四位是一个回文:'
s4 = '再行驶一英里后, 六位数是一个回文:'

# 循环 000 000 - 999 999
num = 0
while num < 999999:
    # 回文检查, 检查后四位为回文
    if is_palindrome(num, 2, 6) and \
            is_palindrome(num + 1, 1, 6) and \
            is_palindrome(num + 2, 1, 5) and \
            is_palindrome(num + 3, 0, 6):
        print(s1, num)
        print(s2, num + 1)
        print(s3, num + 2)
        print(s4, num + 3)
        print('=' * 40)

    num = num + 1

# 运行终端显示:
第一回看到的英里数可能是(它后四位是一个回文): 198888
再行驶一英里后, 后五位是一个回文: 198889
再行驶一英里后, 中间四位是一个回文: 198890
再行驶一英里后, 六位数是一个回文: 198891
========================================
第一回看到的英里数可能是(它后四位是一个回文): 199999
再行驶一英里后, 后五位是一个回文: 200000
再行驶一英里后, 中间四位是一个回文: 200001
再行驶一英里后, 六位数是一个回文: 200002
========================================
3. 练习3
下面是另一个<<车迷天下>>的谜题, 你可以使用一个搜索来解决(http:/www.cartalk.com/content/puzzlers)
最近我去看母亲时, 我发现自己的年龄的两位数正好是母亲的年龄的两位数的倒序.
例如, 如果她是73, 我是37.
我们好奇这种事情这些年来发生过几次, 但很快我们的话题就偏转到其它地方, 所以没有得到答案.
我回家后, 发现我们的年龄互为倒序的事情至今为止发生过6.
我还发现, 如果顺利的话接下来几年还会再遇到一次.
换句话说, 它总共可能发生8, 所以问题是, 我现在的年龄多大?

编写一个Python程序, 为这个谜题搜索答案. 提示: 你可能会发现字符串方法zfill有用.
解答: https://raw.githubusercontent.com/AllenDowney/ThinkPython2/master/code/cartalk3.py
那么需要注意的是 2 20是倒序的, 那么怎么把2变成02, 作者提示使用字符串方法zfill.
zfill方法: 在左边加上一个数字字符串, 填充给定宽度的字段. 字符串永远不会被截断.
s = '2'.zfill(2)
print(s)  # 02
# 判断互为倒序.
def is_reciprocal(age, difference):
    """
    :param age: int 孩子的年龄
    :param difference: int 孩子和他妈妈的年龄差
    :return: bool
    """
    # 孩子的年龄转为字符串.
    children_age_str = str(age).zfill(2)
    # 她妈妈的年龄转为字符串.
    mom_age_str = str(age + difference)

    # 互为倒序判断.
    if children_age_str == mom_age_str[::-1]:
        # 打印互为倒数的年龄.
        print(children_age_str, mom_age_str)
        return True


# 孩子和母亲的年轻差:18 - 70.
age_difference = 18

while age_difference <= 70:
    # 计算器.
    count = 0
    # 孩子最小1岁.
    children_age = 1
    # 他妈最大99, 不然没法倒序了, 100岁你和位数的怎么倒序, 倒个几百数出来...
    mom_age_max = 99

    # 那么相对的孩子的年龄最大到 99 - 年龄差就不用比较了.
    children_max_age = mom_age_max - age_difference

    # 孩子从1岁到孩子的最大可比较的年龄.
    for i in range(children_max_age):
        # 判断两个数是否为倒数.
        if is_reciprocal(i, age_difference):
            # 统计这个年龄差, 互为倒序的次数.
            count = count + 1

    # 年龄互为倒序 可能是8次.
    if count > 0:
        print('年龄差为', age_difference, '岁时', '年龄互为倒序的次数为:', count)
        print('=' * 40)

    # 孩子的年龄增加.
    age_difference = age_difference + 1

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值