检查字符串中是否包含特定字符

5

我需要在一个字符串中找到并计算出可以找到多少个字符。我已将这些字符分成了chars1[a:m]和chars2[n:z],并且有两个计数器。

输出应该是0/14,但实际却是0/1。我认为它只检查是否包含一个且仅一个项目,然后退出循环。是这样吗?

以下是代码。

string_1 = "aaabbbbhaijjjm"

def error_printer(s):
    chars1 = "abcdefghijklm"
    chars2 = "nopqrstuvwxyz"
    counter1 = 0
    counter2 = 0

    if ((c in s) for c in chars1):
        counter1 += 1
    elif ((c in s) for c in chars2):
        counter2 += 1
    print(str(counter2) + "/" + str(counter1))

error_printer(string_1)

4
(c in s) for c in chars1 是一个生成器,它恰好会求值为真,因此 counter1 += 1 总是会被执行。但是由于这是一个 if 语句 而不是循环,所以它只会被执行一次。 - poke
2
在你对循环有更好的理解之前,最好远离生成器表达式(“z for x in y”)。 - Sneftel
答案是指 0 / 14 还是 0 / 6。你只是在迭代 charsX 并计算这些项。 - AChampion
6个回答

7

chars1/chars2 中出现在 s 中的字符数

这很有意义,因为你使用了带有 if 条件的递增。由于 if 不在循环中,您可以递增一次。

现在我们可以将生成器展开为 for 循环。这将解决问题的一部分并生成 0/6

<b>for c in chars1:
    if c in s:
        counter1 += 1</b>
<b>for c in chars2:
    if c in s:</b>
        counter2 += 1

然而,这仍不是非常高效的:它需要 O(n) 的最坏情况来检查字符是否在字符串中。您可以首先构造一个包含字符串中字符的 set,然后执行查找操作(通常平均情况下为 O(1)):

def error_printer(s):
    <b>sset = set(s)</b>
    chars1 = "abcdefghijklm"
    chars2 = "nopqrstuvwxyz"
    counter1 = 0
    counter2 = 0
    for c in chars1:
        if c in <b>sset</b>:
            counter1 += 1
    for c in chars2:
        if c in <b>sset</b>:
            counter2 += 1
    print(str(counter2) + "/" + str(counter1))

现在我们已经提高了效率,但是它仍然不够优雅: 它需要大量的代码,而且还需要检查代码才能知道它的作用。我们可以使用sum(..)结构来计算满足某些约束条件的元素数量,例如:

def error_printer(s):
    sset = set(s)
    chars1 = "abcdefghijklm"
    chars2 = "nopqrstuvwxyz"
    <b>counter1 = sum(c in sset for c in chars1)
    counter2 = sum(c in sset for c in chars2)</b>
    print(str(counter2) + "/" + str(counter1))

这会产生0/6的结果,因为在s中有六个字符在[A-M]范围内出现,而在s中没有任何一个字符在[N-Z]范围内出现。

s中出现在char1/char2中的字符数

根据问题的主体部分,您想要计算在两个不同范围内出现在s中的字符数

另一个相关问题是计算在char1/char2中出现的字符数。在这种情况下,我们只需要交换循环

def error_printer(s):
    chars1 = <b>set(</b>"abcdefghijklm"<b>)</b>
    chars2 = <b>set(</b>"nopqrstuvwxyz"<b>)</b>
    counter1 = sum(c in <b>chars1</b> for c in <b>s</b>)
    counter2 = sum(c in <b>chars2</b> for c in <b>s</b>)
    print(str(counter2) + "/" + str(counter1))

这将产生0/14的结果,因为字符串s中有14个字符在[A-M]范围内(如果'a's中出现两次,则计算两次),而s中没有字符在[N-Z]范围内。

使用范围检查

由于我们正在使用范围,因此我们可以使用比较而不是元素检查,并通过两个比较检查使其运行,如下所示:
def error_printer(s):
    counter1 = sum(<b>'a' <= c <= 'm'</b> for c in s)
    counter2 = sum(<b>'n' <= c <= 'z'</b> for c in s)
    print(str(counter2) + "/" + str(counter1))

6
尝试使用if条件在单个循环中递增s。
for c in s:
    if c in char1:
        counter1 += 1
    if c in char2:
        counter2 += 1

我建议将char1char2转换为set,以在这里提高一点性能。 - Rafael Barros

4

替代for循环的方法:

string_1 = "aaabbbbhaijjjm"

def error_printer(s):
    chars1 = "abcdefghijklm"
    chars2 = "nopqrstuvwxyz"

    counter1 = sum(s.count(c) for c in chars1)
    counter2 = sum(s.count(c) for c in chars2)

    print(str(counter2) + "/" + str(counter1))

error_printer(string_1)

您需要计算输入字符串中出现的"a""b""c"……次数,然后将它们相加。

虽然仍然不够高效,但利用了string.countsum函数,使代码变得更易读和理解正在发生的事情。


2

一个for循环遍历s,用于两个计数器:

string_1 = "aaabbbbhaijjjm"

def error_printer(s):
    chars1 = "abcdefghijklm"
    chars2 = "nopqrstuvwxyz"
    counter1 = 0
    counter2 = 0
    for c in s:
        if c in chars1:
            counter1 += 1
        if c in chars2:
            counter2 += 1
    print(str(counter2) + "/" + str(counter1))

error_printer(string_1)

2
你可以使用 collections.Counter 结合 sum 来实现:
from collections import Counter

def error_printer(s):
    cnts = Counter(s)
    chars1 = "abcdefghijklm"
    chars2 = "nopqrstuvwxyz"
    print(sum(cnts[c] for c in chars2), '/', sum(cnts[c] for c in chars1))

>>> error_printer("aaabbbbhaijjjm")
0 / 14

1
cnts[<anykey>] already returns 0 if it doesn't exist - doesn't it? E.g. sum(cnts[c] for c in chars2) - AChampion

1

正如已经建议的那样,for循环是正确的方法。您将生成器表达式用作if语句的布尔值,这只会运行一次。第一个表达式被评估为True,但这不会使其运行所包含的代码多次。然而,因为第一个if运行了,elif甚至没有评估它的条件。这就是为什么您需要使用for循环,但您不应该循环char1和char2,而是要循环s:

for c in s:
    if c in char1:
        counter1 += 1
    if c in char2:
        counter2 += 1
print(str(counter2) + "/" + str(counter1))

这提示我们有几种更加流畅的方法来完成此操作,首先是使用 c in charX 作为迭代器:
for c in s:
    counter1 += c in char1
    counter2 += c in char2

现在情况有点不太清楚,但我们可以通过添加第二个for循环来使其更加清晰:
char = [‘abcdefghijklm’,’nopqrstuvwxyz’]
counter = [0,0]
for c in s:
    for i in [0,1]:
        counter[i] += c in char[i]

这可能有点过分,但我希望它能帮助你看到如何在Python中重新排列这些内容!

不,更糟糕的是:生成器从未运行过,也从未循环遍历任何项。OP的if条件仅检查(未执行的)生成器对象的真值。而那恰好是真的。 - poke
@StefanPochmann 没事,我对“第一个表达式”这个术语的含义产生了混淆。 - poke
@StefanPochmann 是的,那就是我想说的,抱歉没有表达得更明确。这是我的第一篇帖子! - Chris McElroy
@StefanPochmann 哦,是的,你关于顺序的说法是对的。抱歉,我弄混了,因为打印是反向的。我会编辑我的回答。我仍然认为第二个表达式被评估了,但由于每个字符 (c in s) for c in chars2 都为假,所以没有触发 if 语句。我相信整个生成的表达式是隐含的“或”逻辑,尽管我还没有测试过。 - Chris McElroy
@ChrisMcElroy 不,第二个表达式没有被评估。完全没有。只有在“if”失败时,“elif”才会被检查。但它没有失败。就像Poke所说的那样,两个生成器甚至从未运行过。生成器是真的。句号。不管生成器中有什么,“in”都不会被检查。 - Stefan Pochmann
@StefanPochmann 哦,对了,我忘记了elif。我之前注意到了它,然后又忘了。谢谢你纠正我!(我也会再次编辑,供其他人查看) - Chris McElroy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接