コンマコード - つまらないものを自動化する

私は現在自動化の第4章の練習を進めています。

"リスト値が次のようになっているとします。 spam = ['apples'、 'bananas'、 'tofu'、 'cats']

     

リスト値を引数として取り、最後の項目の前にを挿入して、すべての項目をカンマとスペースで区切った文字列を返す関数を作成します。たとえば、前の spam リストを関数に渡すと、 'りんご、バナナ、豆腐、および猫' が返されます。しかし、あなたの関数はそれに渡されたどんなリスト値でも働くことができるべきです。」

私はPython(およびプログラミング全般)の絶対的な初心者なので、このコードをきれいにする方法についていくつかのアドバイスを得たいと思いました。それは任意のサイズのリストで動作しますが、私はSOに関するいくつかの他の解決策を検討しました、そしてこれを構築するための百万の方法があるようです。どうすればこれをもっと簡単にすることができますか?

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

def commaCode(listVar):
    if len(listVar) == 0: # condition for an empty list
        print('There are no items in this list.') 

    if len(listVar) == 1: # condition for a list with 1 item
        return (str(listVar[0])+'.')

    if len(listVar) == 2: # condition for a list with 2 items
        return (str(listVar[0]) + ' and ' + str(listVar[1]) + '.')

    if len(listVar) >=3: # conditions for lists with 3+ items
        listDisp = ''
        for i in range(len(listVar[:-1])):
            listDisp = listDisp + str(listVar[i]) + ', '
        listDisp = listDisp + 'and ' + str(listVar[-1])
        return (listDisp)

commaCode(spam)
12
あなたが他のコーディング言語がこれをするのを見ることに興味があるかどうか私にはわからない(私は物事がどうやって行われるか見ることができるので他の言語を見ることができる)コード行数 eval.in/986967
追加された 著者 dqd,
私の知る限りでは、タスクの説明では、1項目または2項目の入力リストの場合、結果の文字列の最後にドットを追加する必要はありません。加えて、私見の場合、len(listVar)== 2の場合はを分ける必要はありません。最後の if は2項目のケースを扱うことができるようです。
追加された 著者 C Nick,
追加された 著者 Chinky019,
オックスフォードコンマは大丈夫です。私は実際に私自身が最近ではないよりも頻繁に彼らに向かっている傾向があるのを見つけます…
追加された 著者 mike,

6 答え

  1. You want to look at str.join
  2. by the last case, len(listVar) is run 4 times, which is very repetitive. Better to set it to a temporary variable, and check that each time.
  3. Python has some binary convenience operators for common things like appending. Instead of listDisp = listDisp + foo use listDisp += foo
  4. instead of calling str() on everything as you build the string, why not call it up front, so it can't be missed somewhere along the way. Something at the top like stringList = [ str(i) for i in listVar ] will remove the need for all those str() calls sprinkled throughout the code.
  5. Naming: listVar is a variable. It doesn't need to have 'var(iable)' in its name. How about 'inputList'? Names are important - they're messages to the future (you or whoever reads your code) about what you are/were trying to accomplish. Similar issue with the name commaCode.
  6. range(len(listVar[:-1])) is the same as range(len(listVar)-1), which is both easier to read, and can take advantage of point 2 and end up written as range(listVarLen-1) which is definitely clearer.

全体的には、初心者のために、悪いことではありません!

18
追加された
「名前は重要です - それらは未来へのメッセージです」 - よく言われました!
追加された 著者 davitenio,
+ = は単項演算子ではなく、二項演算子です。
追加された 著者 user52915,
これらはすべて本当に役に立ちました、ありがとうございます!
追加された 著者 user166673,

@pjzには非常に良い点がいくつかありましたので、レビューでそれらをスキップします。

  1. 関数名と変数名はどちらもpythonの snake_case に書かれています。
  2. エラーメッセージを表示する代わりに、適切な例外を発生させます。
  3. 長さ1と2の入力にピリオドを追加しますが、それ以上の出力には追加しません。
  4. Pythonリストスライスを使用すると、特別な場合を実際に削除できます。

これは2つの提案された改善、1つはより簡単、もう1つはpythonicです。また、例外が発生したことを検証するための短いテストスニペットも含めました。

def comma_code(input_list):
    if len(input_list) == 0:
        # Raise an exception rather than just printing the error
        raise ValueError('List cannot be empty')
    # Convert everything to string (could also raise an exception if not string)
    string_list = [str(i) for i in input_list]
    # Handle the trivial case
    if len(string_list) == 1:
        return string_list[0]

    # This could be even more pythonic, but it would lose its readability
    more_than_two_items = len(string_list) > 2
    first_part = ', '.join(string_list[:-2])
    optional_separator = ', ' * more_than_two_items
    last_part = ', and '.join(string_list[-2:])

    formatted_string = first_part + optional_separator + last_part
    return formatted_string     

def comma_code_pythonic(input_list):
    if len(input_list) == 0:
        raise ValueError('List cannot be empty')

    string_list = [str(i) for i in input_list]

    last_part = ', and '.join(string_list[-2:])
    first_part = string_list[:-2]

    formatted_string = ', '.join(first_part + [last_part])
    return formatted_string     


# Try to place non-global variables below the function
spam = ['apples', 'bananas', 'tofu', 'cats']

for i in range(5):
    try:
        print("comma_code:", comma_code(spam[:i]))
    except ValueError as e:
        print(repr(e))

for i in range(5):
    try:
        print("comma_code_pythonic:", comma_code_pythonic(spam[:i]))
    except ValueError as e:
        print(repr(e))
7
追加された
素晴らしいアドバイスです。軽微な問題:なぜ例外なのか空のリストを連結することは完全に有効な操作です。結局、Pythonの、 '.join([])も動作します。 '' を返すだけです。
追加された 著者 Brad Tutterow,
ありがとうございました!元の質問でも同様の実装があったため、例外を含めましたが、質問者が例外の概念に精通していないようでした。自分でコードを解決しようとしているのであれば、それを含めないでください。ただし、エラーメッセージを出力するのであれば、例外を発生させます。
追加された 著者 maxb,

この答えは、あなたが作成したコードに関するものではありませんが、それをもう一度やろうとした場合の問題への取り組み方に関するものです。

Pythonスタイルガイドでは、 camelCase の命名規則よりも snake_case が推奨されているので、ここで使用します。

"リストの値が次のようになっているとします。spam = ['apples'、 'bananas'、 'tofu'、 'cats']

     

引数としてリスト値を取り、すべての項目をカンマとスペースで区切った文字列を返し、最後の項目の前に挿入する関数を作成します。

ここで実行する必要がある主な機能は何ですか?

簡単に言うと、コードの主な機能は、リスト内の値を取得してこれを文字列に変換することです。
これは、すぐにstring.join()がこれに適した関数になることを示唆しているはずです。

結局のところ、文字列を好きな部分文字列と結合することができます。

', '.join(list_of_strings)

次のような変換が得られます。

['apples', 'bananas', 'tofu', 'cats'] -> 'apples, bananas, tofu, cats'

これでほぼすべての作業が完了しました。 ( 'join()'は0と1の長さのリスト配列を正しく処理するので、必要はありません)
'と' を挿入するだけです。

A quick analysis of the problem shows that we only need the 'and ' when there are at least two items, so we write a modification to do just that.
We could just add 'and' in the penultimate location in the list, but we don't want to end up with ['x', 'y'] -> 'x, and, y' so the simple solution to this is to replace the final input in this case with 'and ' plus the input.

次のいずれかの行でこれを実行できます。

#python 2+:
list_of_strings[-1] = 'and %s' % list_of_strings[-1]
#python 2.6+:
list_of_strings[-1] = 'and {}'.format(list_of_strings[-1])
#python 3.6+:
list_of_strings[-1] = f'and {list_of_strings[-1]}'

これによって入力が変更されないようにするため(他の場所で再利用される可能性があります)、まずそのコピーを作成してください。簡単な方法は、元のものから新しいリストを作成することです。

list_of_strings = list(input_list_of_strings)

これらすべてをまとめると、結果としてかなり単純な関数が得られます。

def comma_code(input_list_of_strings):
    list_of_strings = list(input_list_of_strings)
    if len(list_of_strings) > 1:
        list_of_strings[-1] = f'and {list_of_strings[-1]}'
    return ', '.join(list_of_strings)
3
追加された
list_of_strings [-1] = 'and {}'。format(list_of_strings [-1])Py2.6 +
追加された 著者 Chuck M,
@TemporalWolfはそうします。仕事中の私のマシンの1つは空隙があり、2.4しか動作しません...私はそのシステムに%表記を使うことに慣れてきました。私は他のどこでも3.6を使っています。
追加された 著者 mike,

すでに与えられた他の良い答えを補強するために、これはすでに与えられたアイデアのいくつかを利用する短いバージョンと、さらにいくつかのアイデアです:

>>> spam = ['apples', 'bananas', 'tofu', 'cats']
>>> comma_code = lambda l: ", ".join(l[:-1] + ['and ' + l[-1]])
>>> comma_code(spam)
'apples, bananas, tofu, and cats'

lambda 節は、完全な def ... 構文を使用するのではなく、より小さな関数を定義するための簡単な方法です。

また、リストスライス、および文字列とリストの両方の連結も使用します。入力リストが文字列のみであることを知っている限り、これで問題ありませんが、特にリストの最後の要素がわからない場合は、 .format()を使用する方法の1つが考えられます。同様に統合されています。

更新:2項目未満のリストではこれが失敗することに気付いたばかりです。それで、それはあなたがあなたの入力でなさなければならないもう一つの仮定です。上記の2つのケースを扱うわずかに大きい関数で上記をラップするのは比較的簡単です。

2
追加された
@victorantunes技術的ではない英語の文法でも、豆腐の後のカンマは広く受け入れられている使い方で、オックスフォードのカンマです。
追加された 著者 David K,
tofu の後の最後のカンマは、この答えを誤りにします。 'りんご、バナナ、豆腐、猫'
追加された 著者 Alex Cater,
私の悪い私は完全にそのスニペットを飛ばして、そして直接文法モードに入りました。
追加された 著者 Alex Cater,
自己メモ:コメントする前に古いページを更新してください
追加された 著者 mike,
かなりありません。 comma_code(['' cats '])を試してみてください。'とcats 'が返されますが、これは「cats」であるはずです。/i>しかしあなたのコードは空の入力リストを持つIndexErrorを投げます。これは "あなたの関数はそれに渡されたどんなリスト値でも動作できるはずです。"
追加された 著者 mike,
@Baldrickkしたがって、 "Update:"で始まる私の編集...
追加された 著者 princè dube,
@victorantunes私の最初の質問ではありませんが、希望する出力をりんご、バナナ、豆腐、猫として明示的にリストしています。
追加された 著者 princè dube,

他の答えについていくつかの優れた指針があります、しかし私はそれらが最もpythonic解決策を欠いていると感じます:

def comma_code(words):
    *head, final = words

    if head:
        final = f'and {final}'

    return ', '.join(*head, final)

この解決法はどの読者にとっても明らかであるべきです、そして私には問題定義のエンコーディングとして読んでください:空でない単語のリストを与えられたら、それらをコンマで結合してください。複数ある場合は、最後の単語の前に単語「and」を追加します。

アンパック操作は空の入力に対してValueErrorを送出するので、このコードは空のリストを特別な場合に扱う必要はありません。これが望ましい動作であればこれをキャッチして空の文字列を返すことができます。あるいは、varadiac引数を使用してインターフェイスを明確にすることもできます。

def comma_code_args(first, *rest):
    *head, final = first, *rest

    if head:
        final = f'and {final}'

    return ', '.join(*head, final)

これは次のように呼ばれます。

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

>>> comma_code(spam)
'apples, bananas, tofu, and cats'

>>> comma_code_args('apples', 'bananas', 'tofu', 'cats')
'apples, bananas, tofu, and cats'

>>> comma_code_args(*spam)
'apples, bananas, tofu, and cats'
0
追加された
>>> spam = ['apples', 'bananas', 'tofu', 'cats']
>>> print(', '.join(spam[:-2] + [spam[-2] + ' and ' + spam[-1]]))
apples, bananas, tofu and cats

tofu の後のコンマは存在しません。

ステップ:

  1. ', '.join(...) creates a comma-separated string of the values contained in spam
  2. spam[:-2] slices spam and creates a sublist from the first to the second-to-last item. Have a look at python's slice notation.
  3. [spam[-2] + ' and ' + spam[-1]] creates a list containing: spam's second-to-last item ('tofu'), the word 'and' and spam's last item ('cats'). Creating a list is necessary because python doesn't know how to concatenate a list + another type of object. We can only concatenates lists.

Edit: Oxford Comma is a thing, apparently. Well, hooray english.

>>> spam = ['apples', 'bananas', 'tofu', 'cats']
>>> print(', '.join(spam[:-1] + [' and ' + spam[-1]]))
apples, bananas, tofu, and cats
0
追加された
私の母国語(ドイツ語)では、これも間違いです。
追加された 著者 searlea,
tofu の後のコンマはオックスフォードのコンマです。それを使用するかどうかは興味深い質問ですが、ここでは議論の余地があります。仕様に含まれているからです。
追加された 著者 searlea,
@Graipherが毎日何か新しいことを学んでいると思います。英語は私の最初の言語ではないので、私の最初の本能は '正しくないと叫ぶこと'でした。
追加された 著者 Alex Cater,