用Python自动化枯燥的工作:逗号代码

5

我目前正在学习这本入门书籍,并完成了一个名为“逗号代码”的练习项目,该项目要求用户构建一个程序,其功能如下:

将列表作为参数传递给函数,并返回一个字符串,其中所有项都用逗号和空格分隔,最后一项前插入“and”。例如,将下面的 spam 列表传递给函数将返回'apples,bananas,tofu,and cats'。但是您的函数应能够处理传递给它的任何列表值。

spam = ['apples', 'bananas', 'tofu', 'cats']

我对这个问题的解决方案(已完美运行):

spam= ['apples', 'bananas', 'tofu', 'cats']
def list_thing(list):
    new_string = ''
    for i in list:
        new_string = new_string + str(i)
        if list.index(i) == (len(list)-2):
            new_string = new_string + ', and '
        elif list.index(i) == (len(list)-1):
            new_string = new_string
        else:
            new_string = new_string + ', '
    return new_string

print (list_thing(spam))

我唯一的问题是是否有办法缩短我的代码?或者让它更符合'Pythonic'的风格?

以下是我的代码。

def listTostring(someList):
    a = ''
    for i in range(len(someList)-1):
        a += str(someList[i])
    a += str('and ' + someList[len(someList)-1])
    print (a)

spam = ['apples', 'bananas', 'tofu', 'cats']
listTostring(spam)

输出:苹果,香蕉,豆腐和猫


4
如果你有可运行的代码,那么这更适合在http://codereview.stackexchange.com/上进行审查。 - EdChum
请注意,您的代码无法正常工作,因为列表中的最后一个字符串是先前元素的重复。 - Daniel Roseman
@EdChum 抱歉,不会再发生了,谢谢您的提示。 - Sutharsan Sethurayar
@DanielRoseman,我甚至没有意识到这一点,感谢您让我知道! - Sutharsan Sethurayar
请注意样本输出使用牛津逗号:“'apples,bananas,tofu和cats'”,因此在“tofu”后面有一个逗号。这使问题变得有点棘手... - PM 2Ring
32个回答

16

使用str.join()函数可以将一系列字符串按照一个分隔符连接起来。如果你需要把所有单词都用同一种方式连接,但最后一个单词与前面的不同,你可以在前面插入' and '

def list_thing(words):
    if len(words) == 1:
        return words[0]
    return '{}, and {}'.format(', '.join(words[:-1]), words[-1])

将其分解:

  • words[-1] 取列表的最后一个元素。 words[:-1] 通过切片操作,生成一个新列表,其中包含除了最后一个单词之外的所有单词。

  • ', '.join() 生成一个新字符串,将作为参数传递给 str.join() 的所有字符串使用 ', ' 连接起来。如果输入列表中只有一个元素,则该元素将未连接返回。

  • '{}, and {}'.format() 插入逗号连接的单词和最后一个单词到模板中(包括牛津逗号)。

如果传入空列表,则上述函数将引发 IndexError 异常;如果您认为空列表是该函数的有效用例,则可以在函数中特别测试该情况。

因此,上述代码将除了最后一个单词之外的所有单词使用', '连接起来,然后使用' and '将最后一个单词添加到结果中。

请注意,如果只有一个单词,则只会返回该单词;在这种情况下没有什么需要连接。如果有两个单词,则会返回 'word1 and word2'。更多单词将产生 'word1, word2, ... and lastword'

演示:

>>> def list_thing(words):
...     if len(words) == 1:
...         return words[0]
...     return '{}, and {}'.format(', '.join(words[:-1]), words[-1])
...
>>> spam = ['apples', 'bananas', 'tofu', 'cats']
>>> list_thing(spam[:1])
'apples'
>>> list_thing(spam[:2])
'apples, and bananas'
>>> list_thing(spam[:3])
'apples, bananas, and tofu'
>>> list_thing(spam)
'apples, bananas, tofu, and cats'

我猜你一定是单元测试方面的高手吧 ;) (顺便问一下:空列表怎么处理?) - Cyrbil
@Cyrbil:忽略那个;那是一个错误,应该这样做。 - Martijn Pieters
3
一个小细节:我认为牛津逗号不会在“苹果和香蕉”这样的句子中使用。 - bereal
1
@bereal:那么这就是原始任务的问题,它没有指定任何这样的要求。 - Martijn Pieters
1
@aurumpurum,实际上,我非常不同意您的观点:将参数转换为正确的类型是调用者需要承担责任的事情。而一个空列表并不是一个句子,我在早些时候的评论中提到过,我认为应该引发异常,并在我的答案中明确表示了这一点。 - Martijn Pieters
显示剩余6条评论

5

我采用了一种不同的方法。由于我是初学者,所以不知道这是否是最干净的方法。对我来说,它似乎是最简单的方法:

spam = ['apples', 'pizza', 'dogs', 'cats']

def comma(items):
    for i in range(len(items) -2):
        print(items[i], end=", ")# minor adjustment from one beginner to another: to make it cleaner, simply move the ', ' to equal 'end'. the print statement should finish like this --> end=', '
    print(items[-2] + 'and ' + items[-1]) 

comma(spam)

这将会输出:

apples, pizza, dogs and cats

1
我认为这是一个非常扎实的初学者方法,特别是当你使用从书中学到的代码时,但该项目确实表示它应该适用于传递给它的任何列表值。如果我只有spam = ['apples'],那么列表索引将超出范围。 - A B
此外,任务要求函数需要返回一个字符串。但是你的函数返回了 None - PM 2Ring

4
这里有一个处理牛津逗号的解决方案。它还能应对空列表,此时返回一个空字符串。请看以下内容:

牛津逗号 是指在列表中使用逗号和“和”字来分隔项目,例如:“苹果、橙子和香蕉”。这个解决方案可以正确处理这种情况。

def list_thing(seq):
    return (' and '.join(seq) if len(seq) <= 2
        else '{}, and {}'.format(', '.join(seq[:-1]), seq[-1]))

spam = ['apples', 'bananas', 'tofu', 'cats']

for i in range(1 + len(spam)):
    seq = spam[:i]
    s = list_thing(seq)
    print(i, seq, repr(s))

输出

0 [] ''
1 ['apples'] 'apples'
2 ['apples', 'bananas'] 'apples and bananas'
3 ['apples', 'bananas', 'tofu'] 'apples, bananas, and tofu'
4 ['apples', 'bananas', 'tofu', 'cats'] 'apples, bananas, tofu, and cats'

以下是一个稍微可读性更高的版本,使用if-else语句而不是条件表达式:

def list_thing(seq):
    if len(seq) <= 2:
        return ' and '.join(seq)
    else:
        return '{}, and {}'.format(', '.join(seq[:-1]), seq[-1])    

以下是使用 f-string 编写的较不易读的版本:

def list_thing(seq):
    if len(seq) <= 2:
        return ' and '.join(seq)
    else:
        return f"{', '.join(seq[:-1])}, and {seq[-1]}"   

请注意,Martijn的代码从包含2个项的列表中生成 'apples, and bananas'。我的答案在英语上更加符合语法规范,但是Martijn的答案在技术上更加准确,因为它完全按照OP的引用进行操作(尽管我不同意他对空列表的处理)。

3

我尝试了一下,希望这就是你要找的:

spam= ['apples', 'bananas', 'tofu', 'cats']

def list_thing(list):

#creating a string then splitting it as list with two items, second being last word
    new_string=', '.join(list).rsplit(',', 1)    

#Using the same method used above to recreate string by replacing the separator.

    new_string=' and'.join(new_string)
    return new_string

print(list_thing(spam))

1
使用 list 作为变量名并不是一个好主意,因为它会遮盖内置的 list 类型。这里不会引起问题,但它确实会让其他读者感到困惑,并且可能导致神秘的错误。 - PM 2Ring
我完全同意你的观点。虽然我的本意并不是使用“list”作为变量名,但我只是根据自己的理解修改了相关代码,并保留了问题中提到的变量名,以便解释概念。 - Utkarsh

3
我的理解是,单个列表项也可以是最后一个列表项,因此需要在其前插入“and”,并在两个项目列表中使用', and'返回两个项目。因此无需单独处理单个或两个项目列表,只需处理前n个项目和最后一个项目。 我还要指出的是,虽然很棒,但许多其他项目使用模块和函数,在学生遇到这个问题时,这些模块和函数在《自动化无聊的事情》文本中尚未教授(像我这样的学生已经在其他地方看到了join.format,但仅尝试使用已在文本中教授的内容)。
def commacode(passedlist):
    stringy = ''
    for i in range(len(passedlist)-1):
        stringy += str(passedlist[i]) + ', '
        # adds all except last item to str
    stringy += 'and ' + str(passedlist[len(passedlist)-1])
    # adds last item to string, after 'and'
    return stringy

您可以通过以下方式处理空列表的情况:

def commacode(passedlist):
    stringy = ''
    try:
        for i in range(len(passedlist)-1):
            stringy += str(passedlist[i]) + ', '
            # adds all except last item to str
        stringy += 'and ' + str(passedlist[len(passedlist)-1])
        # adds last item to string, after 'and'
        return stringy
    except IndexError:
        return '' 
        #handles the list out of range error for an empty list by returning ''

2

其他人提供了很好的一行解决方案,但是改进你的实现方式并修复在元素重复时无法正常工作的问题的好方法是在for循环中使用enumerate来跟踪索引,而不是使用index,后者总是找到目标的第一个出现。

for counter, element in enumerate(list):
    new_string = new_string + str(element)
    if counter == (len(list)-2):
        ...

2

格式语句更加简洁。

这个方法对我也有效:

def sentence(x):
    if len(x) == 1:
        return x[0]
    return (', '.join(x[:-1])+ ' and ' + x[-1])

吹毛求疵:,和 - tiwo

1
由于函数必须适用于传递给它的所有列表值,包括整数,因此它应该能够返回/打印所有值,即作为str()。我的完全工作代码如下:
spam = ['apples', 'bananas', 'tofu', 'cats', 2]

def commacode(words):

    x = len(words)

    if x == 1:
        print(str(words[0]))
    else:
        for i in range(x - 1):
            print((str(words[i]) + ','), end=' ')
        print(('and ' + str(words[-1])))

commacode(spam)

1
这是我想到的方法。可能有更简洁的写法,但只要列表中至少有一个元素,这个方法就可以适用于任何大小的列表。
spam = ['apples', 'oranges' 'tofu', 'cats']
def CommaCode(list):
    if len(list) > 1 and len(list) != 0:
        for item in range(len(list) - 1):
            print(list[item], end=", ")
        print('and ' + list[-1])
    elif len(list) == 1:
        for item in list:
            print(item)
    else:
        print('List must contain more than one element')
CommaCode(spam)

1
我正在阅读同一本书,并提出了以下解决方案: 这允许用户输入一些值并从输入中创建一个列表。
userinput = input('Enter list items separated by a space.\n')
userlist = userinput.split()

def mylist(somelist):
    for i in range(len(somelist)-2): # Loop through the list up until the second from last element and add a comma
        print(somelist[i] + ', ', end='')
    print(somelist[-2] + ' and ' + somelist[-1]) # Add the last two elements of the list with 'and' in-between them

mylist(userlist)

示例:

用户输入: one two three four five 输出: one,two,three,four和five


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