从Counting Partition-数字分割的多种解法看Python
Partition分割贯穿CS61A课程中,把我学得天昏地暗,这里作一总结。
第一次出现:
L10:Tree Recursion
#Tree Recursion
def count_partitions_treerecur(n,m):
if n == 0:
return 1
elif n < 0:
return 0
elif m == 0:
return 0
else:
with_m = count_partitions_treerecur(n-m, m)
without_m = count_partitions_treerecur(n, m-1)
return with_m + without_m
仅仅(6,4)程度的partition递归,在python-tutor上就跑了393步,如果增加到(10,4),则会直接报错数据大小超过2MB的限制,这其中涉及的大量操作都是对于每个有无最大值m的分割操作返回值的判断。其基本逻辑是,将一个复杂的大问题转化为小问题,至少用到一个m分割(此时待分割n变成n-m),或者以最大分区为m-1开始一个新分割(此时n不变)。之后只需要进行基本条件的判断:
1,待分割n为0,分割成功。
2,待分割n<0,一定是之前的某个步骤出错,分割失败。
3,最大分区为0,无法分割,分割失败。
第二次出现
SICP 2.3.6 Trees
先了解一下python Tree一些构造函数和选择器。
def tree(label,branches=[]):
for branch in branches:
assert is_tree(branch)
return [label]+list[branches]
def label(tree):
return tree[0]
def branches(tree):
return tree[1:]
def is_tree(tree):
if type(tree)!=list or len(tree)<1:
return False
for branch in branches(tree):
if not is_tree(branch):
return False
return True
def is_leaf(tree):
return not branches(tree)
现在请大家思考,如何将数字分割表现在树中。
Partition trees. Trees can also be used to represent the partitions of an integer. A partition tree for n using parts up to size m is a binary (two branch) tree that represents the choices taken during computation. In a non-leaf partition tree:
- the left (index 0) branch contains all ways of partitioning
nusing at least onem, - the right (index 1) branch contains partitions using parts up to
m-1, and - the root label is
m.
注意:根节点为m
The labels at the leaves of a partition tree express whether the path from the root of the tree to the leaf represents a successful partition of n.
>>> def partition_tree(n, m):
"""Return a partition tree of n using parts of up to m."""
if n == 0:
return tree(True)
elif n < 0 or m == 0:
return tree(False)
else:
left = partition_tree(n-m, m)
right = partition_tree(n, m-1)
return tree(m, [left, right])
>>> partition_tree(2, 2)
[2, [True], [1, [1, [True], [False]], [False]]]
Printing the partitions from a partition tree is another tree-recursive process that traverses the tree, constructing each partition as a list. Whenever a True leaf is reached, the partition is printed.
>>> def print_parts(tree, partition=[]):
if is_leaf(tree):
if label(tree):
print(' + '.join(partition))
else:
left, right = branches(tree)
m = str(label(tree))
print_parts(left, partition + [m])
print_parts(right, partition)
>>> print_parts(partition_tree(6, 4))
4 + 2
4 + 1 + 1
3 + 3
3 + 2 + 1
3 + 1 + 1 + 1
2 + 2 + 2
2 + 2 + 1 + 1
2 + 1 + 1 + 1 + 1
1 + 1 + 1 + 1 + 1 + 1
书中用树的左右分支表示前面提到的两种选择,如果从一棵树(子树)的根到叶结点相加等于n,那么在它的叶节点后加上True节点,标记一条正确的路径。False同理。打印时,只需要判断叶结点的bool值就可以展现出分割方法。
第三次出现
SICP 2.3.7 Linked Lists
先了解一下python Linked List一些构造函数和选择器。
>>> empty = 'empty'
>>> def is_link(s):
"""s is a linked list if it is empty or a (first, rest) pair."""
return s == empty or (len(s) == 2 and is_link(s[1]))
>>> def link(first, rest):
"""Construct a linked list from its first element and the rest."""
assert is_link(rest), "rest must be a linked list."
return [first, rest]
>>> def first(s):
"""Return the first element of a linked list s."""
assert is_link(s), "first only applies to linked lists."
assert s != empty, "empty linked list has no first element."
return s[0]
>>> def rest(s):
"""Return the rest of the elements of a linked list s."""
assert is_link(s), "rest only applies to linked lists."
assert s != empty, "empty linked list has no rest."
return s[1]
#迭代实现
>>> def len_link(s):
"""Return the length of linked list s."""
length = 0
while s != empty:
s, length = rest(s), length + 1
return length
>>> def getitem_link(s, i):
"""Return the element at index i of linked list s."""
while i > 0:
s, i = rest(s), i - 1
return first(s)
#递归实现
>>> def len_link_recursive(s):
"""Return the length of a linked list s."""
if s == empty:
return 0
return 1 + len_link_recursive(rest(s))
>>> def getitem_link_recursive(s, i):
"""Return the element at index i of linked list s."""
if i == 0:
return first(s)
return getitem_link_recursive(rest(s), i - 1)
>>> def extend_link(s, t):
"""Return a list with the elements of s followed by those of t."""
assert is_link(s) and is_link(t)
if s == empty:
return t
else:
return link(first(s), extend_link(rest(s), t))
>>> extend_link(four, four)
[1, [2, [3, [4, [1, [2, [3, [4, 'empty']]]]]]]]
>>> def apply_to_all_link(f, s):
"""Apply f to each element of s."""
assert is_link(s)
if s == empty:
return s
else:
return link(f(first(s)), apply_to_all_link(f, rest(s)))
>>> apply_to_all_link(lambda x: x*x, four)
[1, [4, [9, [16, 'empty']]]]
>>> def keep_if_link(f, s):
"""Return a list with elements of s for which f(e) is true."""
assert is_link(s)
if s == empty:
return s
else:
kept = keep_if_link(f, rest(s))
if f(first(s)):
return link(first(s), kept)
else:
return kept
>>> keep_if_link(lambda x: x%2 == 0, four)
[2, [4, 'empty']]
>>> def join_link(s, separator):
"""Return a string of all elements in s separated by separator."""
if s == empty:
return ""
elif rest(s) == empty:
return str(first(s))
else:
return str(first(s)) + separator + join_link(rest(s), separator)
>>> join_link(four, ", ")
'1, 2, 3, 4'
在链表表示中,我们将递归过程视为依次嵌套连接的链表,比如partitions(6,4)的结果就是这样:
link(
link(4, link(2, empty)),
link(
link(4, link(1, link(1, empty))),
link(
link(3, link(3, empty)),
link(
link(3, link(2, link(1, empty))),
link(
link(2, link(2, link(2, empty))),
link(
link(2, link(2, link(1, link(1, empty)))),
link(
link(2, link(1, link(1, link(1, empty)))),
link(1, link(1, link(1, link(1, link(1, empty))))))
)
)
)
)
)
)
代码实现:
>>> def partitions(n, m):
"""Return a linked list of partitions of n using parts of up to m.
Each partition is represented as a linked list.
"""
if n == 0:
return link(empty, empty) # A list containing the empty partition
elif n < 0 or m == 0:
return empty
else:
using_m = partitions(n-m, m)
with_m = apply_to_all_link(lambda s: link(m, s), using_m)
without_m = partitions(n, m-1)
return extend_link(with_m, without_m)
>>> def print_partitions(n, m):
lists = partitions(n, m)
strings = apply_to_all_link(lambda s: join_link(s, " + "), lists)
print(join_link(strings, "\n"))
>>> print_partitions(6, 4)
4 + 2
4 + 1 + 1
3 + 3
3 + 2 + 1
3 + 1 + 1 + 1
2 + 2 + 2
2 + 2 + 1 + 1
2 + 1 + 1 + 1 + 1
1 + 1 + 1 + 1 + 1 + 1
第四次出现
Lecture 17 : Generators
在python中,函数用yield返回一个生成器对象,可以用来迭代。配合next(iterator)yield会惰性执行函数。(完成一次执行之后暂停,知道要求下一个值)。
也就是说:在本例中,countdown函数会返回一个生成器对象(迭代器)
def countdown(k):
if k>0:
yield k
yield countdown(k-1)
t = countdown(3)
print(next(t)) # 输出 3
print(next(t)) # 输出 countdown(2)(一个迭代器对象)
print(next(next(t))) # 进入 countdown(2) 迭代器,输出 2
A yield from statement yields all values from an iterator or iterable (Python 3.3)
而使用yield from时:可以正常输出
def countdown(k):
if k>0:
yield k
"""另一种写法是for循环:
for x in countdown(k-1):
yield k """
yield from countdown(k-1)
t = countdown(3)
再看几个yield from示例
def prefixes(s):
if s:
yield from prefixes(s[:-1])
yield from s
>>>list(prefixes('both'))
['b','bo','bot','both']
def substrings(s):
if s:
yield from prefixes(s)
yield from substrings(s[1:])
>>>list(substrings('tops'))
['t','to','top','tops','o','op','ops','p','ps','s']
在开始yield_partiton之前,我们先对之前的partition进行几次升级。
def count_partition(n,m):
"""Count partitions.
>>> count_partitions(6,4)
9
"""
if n < 0 or m == 0:
return 0
else:
exact_match = 0
if n == m:
exact_match = 1
with_m = count_partition(n-m,m)
without_m = count_partition(n,m-1)
return exact_match + with_m + without_m
def list_partition(n,m):
"""list partitions.
>>> for p in list_partitions(6,4) : print(p)
[2, 4]
[1, 1, 4]
[3, 3]
[1, 2, 3]
[1, 1, 1, 3]
[2, 2, 2]
[1, 1, 2, 2]
[1, 1, 1, 1, 2]
[1, 1, 1, 1, 1, 1]
"""
if n < 0 or m == 0:
return []
else:
exact_match = []
if n == m:
exact_match = [[m]]
with_m = [p + [m] for p in list_partition(n-m,m)]
without_m = list_partition(n,m-1)
return exact_match + with_m + without_m
def strings_partition(n,m):
"""strings partitions.
>>> for p in list_partitions(6,4) : print(p)
2+4
1+1+4
3+3
1+2+3
1+1+1+3
2+2+2
1+1+2+2
1+1+1+1+2
1+1+1+1+1+1
"""
if n < 0 or m == 0:
return []
else:
exact_match = []
if n == m:
exact_match = [str(m)]
with_m = [p + ' + ' + str(m) for p in strings_partition(n-m,m)]
without_m = strings_partition(n,m-1)
return exact_match + with_m + without_m
def yield_partition(n,m):
"""yield partitions.
>>> for p in list_partitions(6,4) : print(p)
2 + 4
1 + 1 + 4
3 + 3
1 + 2 + 3
1 + 1 + 1 + 3
2 + 2 + 2
1 + 1 + 2 + 2
1 + 1 + 1 + 1 + 2
1 + 1 + 1 + 1 + 1 + 1
"""
if n > 0 and m > 0:
if n == m:
yield str(m)
for p in yield_partition(n-m,m):
yield p + ' + ' + str(m)
yield from yield_partition(n,m-1)
使用yield的好处在于:我们只需要关注当前的元素,当你需要在某个位置停止它,我们可以控制它执行的次数。而不是想之前的partiton函数,只能一次性输出完所有的结果。在交互模式中的运行结果:
#例如partition(60,50)结果将近100万中,我们只想取其中10种会很快。
>>> s = list(yield_partition(60,50))
>>> len(s)
966370
>>> s[0]
'10 + 50'
>>> s[2025]
'1 + 2 + 4 + 13 + 40'
>>> next(yield_partition(60,50))
'10 + 50'
>>> t = yield_partition(60,50)
>>> for _ in range(10):
... print(next(t))
...
10 + 50
1 + 9 + 50
2 + 8 + 50
1 + 1 + 8 + 50
3 + 7 + 50
1 + 2 + 7 + 50
1 + 1 + 1 + 7 + 50
4 + 6 + 50
1 + 3 + 6 + 50
2 + 2 + 6 + 50
So if you're ever writing a program where there are many possibilities and you only want a few of them, sometimes this generator function approach can not only be easier to write, easier to read, but also dramatically faster to run.
经过对这一问题的思考,我不禁感叹我所知之渺小,这不会是终点,只是对过去的思考和总结,我将继续在学习中完善我对编程的理解。相关代码已经上传GitHub:github.com/kair998/Pyt…
如果对你有帮助的话,请关注我的账号或者星标我的GitHub仓库,我会持续输出学习编程的所思所想。