我正在尝试计算一个项目在序列中出现的次数,无论它是数字列表还是字符串,它适用于数字但我在尝试找到像{{1}这样的字母时遇到错误在一个字符串中:
"i"
TypeError:+:'int'和'NoneType'不支持的操作数类型
答案 0 :(得分:2)
使用递归比使用递归更常用:使用内置的count
方法计算出现次数。
def count(str, item):
return str.count(item)
>>> count("122333444455555", "4")
4
但是,如果您想使用 iteration 进行此操作,则可以应用类似的原则。将其转换为列表,然后遍历列表。
def count(str, item):
count = 0
for character in list(str):
if character == item:
count += 1
return count
答案 1 :(得分:1)
问题是您的第一个if
,其中显式检查输入是否为空列表:
if s == []:
return 0
如果您希望它与str
和list
一起使用,您只需使用:
if not s:
return s
简而言之,根据truth value testing in Python,任何空序列都被视为假,并且任何非空序列都被视为真。如果您想了解更多信息,我添加了相关文档的链接。
你也可以在这里省略while
循环,因为它不必要,因为它总会在第一次迭代中返回,因此离开循环。
所以结果将是这样的:
def count(f, s):
if not s:
return 0
elif f == s[0]:
return 1 + count(f, s[1:])
else:
return 0 + count(f, s[1:])
示例:
>>> count('i', 'what is it')
2
如果您不仅对使其成功感兴趣,而且还有兴趣让它变得更好,那么有几种可能性。
在Python中,布尔值只是整数,所以当你算术时它们的行为就像整数一样:
>>> True + 0
1
>>> True + 1
2
>>> False + 0
0
>>> False + 1
1
因此,您可以轻松地内联if
else
:
def count(f, s):
if not s:
return 0
return (f == s[0]) + count(f, s[1:])
因为f == s[0]
返回True
(行为类似于1),如果它们相等或False
(行为类似于0),如果它们不是。括号不是必需的,但为了清楚起见,我添加了它们。因为基本情况总是返回一个整数,所以这个函数本身总会返回一个整数。
由于以下原因,您的方法会创建大量输入副本:
s[1:]
除第一个元素外,这将创建整个列表(或字符串,...)的浅表副本。这意味着你实际上有一个操作使用O(n)
(其中n
是元素的数量)时间和内存在每个函数调用中,因为你递归地执行这个操作,时间和内存的复杂性将是{{1 }}
您可以避免这些副本,例如,通过传递索引:
O(n**2)
因为我需要传入当前索引,所以我添加了另一个获取索引的函数(我假设你可能不希望索引在你的公共函数中传入),但如果你想要你可以使用它一个可选参数:
def _count_internal(needle, haystack, current_index):
length = len(haystack)
if current_index >= length:
return 0
found = haystack[current_index] == needle
return found + _count_internal(needle, haystack, current_index + 1)
def count(needle, haystack):
return _count_internal(needle, haystack, 0)
然而,可能有更好的方法。你可以将序列转换为迭代器并在内部使用它,在函数的开头你从迭代器中弹出下一个元素,如果没有元素就结束递归,否则你比较元素然后递归到剩余的迭代器:
def count(needle, haystack, current_index=0):
length = len(haystack)
if current_index >= length:
return 0
return (haystack[current_index] == needle) + count(needle, haystack, current_index + 1)
当然,如果你想避免def count(needle, haystack):
# Convert it to an iterator, if it already
# is an (well-behaved) iterator this is a no-op.
haystack = iter(haystack)
# Try to get the next item from the iterator
try:
item = next(haystack)
except StopIteration:
# No element remained
return 0
return (item == needle) + count(needle, haystack)
调用开销,你也可以使用内部方法,只有在第一次调用函数时才需要。然而,这可能不会导致显着更快执行的微优化:
iter
这两种方法都具有以下优点:它们不会使用(多)额外的内存并且可以避免副本。所以它应该更快,占用更少的内存。
但是对于长序列,由于递归会导致问题。 Python有一个递归限制(可调,但只是在某种程度上):
def _count_internal(needle, haystack):
try:
item = next(haystack)
except StopIteration:
return 0
return (item == needle) + _count_internal(needle, haystack)
def count(needle, haystack):
return _count_internal(needle, iter(haystack))
有一些方法可以缓解(只要你使用递归就无法解决递归深度问题)这个问题。经常使用的方法是分而治之。它基本上意味着你将你拥有的任何序列分成2个(有时更多)部分,然后用这些部分中的每个部分调用函数。当只剩下一个项目时,递归窗台结束:
>>> count('a', 'a'*10000)
---------------------------------------------------------------------------
RecursionError Traceback (most recent call last)
<ipython-input-9-098dac093433> in <module>()
----> 1 count('a', 'a'*10000)
<ipython-input-5-5eb7a3fe48e8> in count(needle, haystack)
11 else:
12 add = 0
---> 13 return add + count(needle, haystack)
... last 1 frames repeated, from the frame below ...
<ipython-input-5-5eb7a3fe48e8> in count(needle, haystack)
11 else:
12 add = 0
---> 13 return add + count(needle, haystack)
RecursionError: maximum recursion depth exceeded in comparison
递归深度现在从def count(needle, haystack):
length = len(haystack)
# No item
if length == 0:
return 0
# Only one item remained
if length == 1:
# I used the long version here to avoid returning True/False for
# length-1 sequences
if needle == haystack[0]:
return 1
else:
return 0
# More than one item, split the sequence in
# two parts and recurse on each of them
mid = length // 2
return count(needle, haystack[:mid]) + count(needle, haystack[mid:])
更改为n
,这允许进行之前失败的调用:
log(n)
然而,因为我使用切片,它将再次创建大量副本。使用迭代器会很复杂(或者不可能),因为迭代器不具有(通常)大小但是它易于使用的索引:
>>> count('a', 'a'*10000)
10000
在这种情况下使用内置方法(或函数)可能看起来很愚蠢,因为已经有一个内置的方法来解决问题而没有递归但是在这里它是使用def _count_internal(needle, haystack, start_index, end_index):
length = end_index - start_index
if length == 0:
return 0
if length == 1:
if needle == haystack[start_index]:
return 1
else:
return 0
mid = start_index + length // 2
res1 = _count_internal(needle, haystack, start_index, mid)
res2 = _count_internal(needle, haystack, mid, end_index)
return res1 + res2
def count(needle, haystack):
return _count_internal(needle, haystack, 0, len(haystack))
方法字符串和列表都有:
index
递归真的很强大但是在Python中你必须对抗递归限制,因为在Python中没有tail call优化它通常很慢。这可以通过使用迭代而不是递归来解决:
def count(needle, haystack):
try:
next_index = haystack.index(needle)
except ValueError: # the needle isn't present
return 0
return 1 + count(needle, haystack[next_index+1:])
如果您更有利,可以将{(3)}与<{3}}一起使用生成器表达式:
def count(needle, haystack):
found = 0
for item in haystack:
if needle == item:
found += 1
return found
同样,这依赖于布尔表现为整数的事实,因此def count(needle, haystack):
return sum(needle == item for item in haystack)
将所有出现(1)与所有非出现(零)相加,从而得出总计数。
但如果一个人已经在使用内置插件,那么更不用说内置方法(字符串和列表都有):sum
:
count
此时你可能不再需要将它包装在一个函数中,而只需直接使用该方法。
如果您想进一步计算所有元素,可以使用内置集合模块中的def count(needle, haystack):
return haystack.count(needle)
:
Counter
我经常提到副本及其对记忆和表现的影响,我实际上想要提供一些定量结果,以表明它实际上有所作为。
我在这里使用了一个有趣的项目>>> from collections import Counter
>>> Counter('abcdab')
Counter({'a': 2, 'b': 2, 'c': 1, 'd': 1})
(它是第三方软件包,所以如果你想运行它,你必须安装它):
simple_benchmarks
它的对数日志缩放以有意义的方式显示值范围,而更低意味着更快。
可以清楚地看到原始方法对于长输入变得非常慢(因为它复制了它在def count_original(f, s):
if not s:
return 0
elif f == s[0]:
return 1 + count_original(f, s[1:])
else:
return 0 + count_original(f, s[1:])
def _count_index_internal(needle, haystack, current_index):
length = len(haystack)
if current_index >= length:
return 0
found = haystack[current_index] == needle
return found + _count_index_internal(needle, haystack, current_index + 1)
def count_index(needle, haystack):
return _count_index_internal(needle, haystack, 0)
def _count_iterator_internal(needle, haystack):
try:
item = next(haystack)
except StopIteration:
return 0
return (item == needle) + _count_iterator_internal(needle, haystack)
def count_iterator(needle, haystack):
return _count_iterator_internal(needle, iter(haystack))
def count_divide_conquer(needle, haystack):
length = len(haystack)
if length == 0:
return 0
if length == 1:
if needle == haystack[0]:
return 1
else:
return 0
mid = length // 2
return count_divide_conquer(needle, haystack[:mid]) + count_divide_conquer(needle, haystack[mid:])
def _count_divide_conquer_index_internal(needle, haystack, start_index, end_index):
length = end_index - start_index
if length == 0:
return 0
if length == 1:
if needle == haystack[start_index]:
return 1
else:
return 0
mid = start_index + length // 2
res1 = _count_divide_conquer_index_internal(needle, haystack, start_index, mid)
res2 = _count_divide_conquer_index_internal(needle, haystack, mid, end_index)
return res1 + res2
def count_divide_conquer_index(needle, haystack):
return _count_divide_conquer_index_internal(needle, haystack, 0, len(haystack))
def count_index_method(needle, haystack):
try:
next_index = haystack.index(needle)
except ValueError: # the needle isn't present
return 0
return 1 + count_index_method(needle, haystack[next_index+1:])
def count_loop(needle, haystack):
found = 0
for item in haystack:
if needle == item:
found += 1
return found
def count_sum(needle, haystack):
return sum(needle == item for item in haystack)
def count_method(needle, haystack):
return haystack.count(needle)
import random
import string
from functools import partial
from simple_benchmark import benchmark, MultiArgument
funcs = [count_divide_conquer, count_divide_conquer_index, count_index, count_index_method, count_iterator, count_loop,
count_method, count_original, count_sum]
# Only recursive approaches without builtins
# funcs = [count_divide_conquer, count_divide_conquer_index, count_index, count_iterator, count_original]
arguments = {
2**i: MultiArgument(('a', [random.choice(string.ascii_lowercase) for _ in range(2**i)]))
for i in range(1, 12)
}
b = benchmark(funcs, arguments, 'size')
b.plot()
中执行的列表),而其他方法表现为线性。可能看起来很奇怪的是,分而治之的方法执行速度较慢,但这是因为它们需要更多的函数调用(并且函数调用在Python中很昂贵)。但是,在它们达到递归限制之前,它们可以处理比迭代器和索引变体更长的输入。
改变分而治之的方法很容易让它跑得更快,想到一些可能性:
但鉴于这可能只是一种递归练习,有点超出了范围。
然而,它们的表现都比使用迭代方法更糟糕:
特别是使用列表的O(n**2)
方法(也是字符串之一)和手动迭代要快得多。
答案 2 :(得分:1)
错误是因为有时你只是没有返回值。因此,在函数末尾返回0可修复此错误。在python中有很多更好的方法可以做到这一点,但我认为它只是用于训练递归编程。
答案 3 :(得分:0)
在我看来,你正在以艰难的方式做事。
你可以使用集合中的Counter来做同样的事情。
from collections import Counter
def count(f, s):
if s == None:
return 0
return Counter(s).get(f)
Counter将返回一个dict对象,该对象包含s对象中所有内容的计数。在dict对象上执行.get(f)将返回您要搜索的特定项目的计数。这适用于数字列表或字符串。
答案 4 :(得分:0)
如果你受到约束并决定用递归来做,我会强烈建议将问题减半,而不是逐个削减它。减半允许您处理更大的案例,而不会遇到堆栈溢出。
g = O(f)