リストのソートと、最初から使えるメソッドたち
sorted()と.sort()の違い、max/min/sum/count/indexが返す答え、key=でルールを変えること、そしてjoin()でリストを読みやすいテキストにすること。
どうやって学びますか?
解決する問題
7人分の点数がリストに入っています。答えるべき質問は3つです:最高点はいくつか、平均はいくつか、上位3人は誰か。
第12章の道具があれば、最初の2つはループでできます — 手間はかかりますが、可能です。3つ目は違います。「上位3人」とはまず順番に並べるということですが、私たちはまだ何かを並べる方法を知らないのです。
こういう仕事はとても頻繁に出てくるので、Pythonは最初から入れてくれています。今日の章はその道具の一覧です — そして、ほぼ全員が一度は落ちる罠が1つあります:同じ操作には2つの形があります。1つはリストを変え、もう1つは新しいリストを返します。そして間違った方を選んでも、エラーはまったく出ません。
この章を終える頃には
sorted()と.sort()の違いを説明でき、それぞれの仕事にどっちが合うか言えるmax、min、sum、count、indexでリストから答えを出せるkey=でソートのルールを変えられるjoin()でリストを読みやすいテキストに変えられる- 型が混ざったリストをソートすると
TypeErrorが出る理由を説明できる
前提条件: リスト — 名前1つにたくさんの値.
sorted()と.sort() — この章の核心
どちらもソートします。違うのは返ってくるものです。
marks = [72, 45, 90, 61]
ordered = sorted(marks)
print(ordered)
print(marks)
marks.sort()
print(marks)[45, 61, 72, 90]
[72, 45, 90, 61]
[45, 61, 72, 90]この3行の出力をよく見てください。
sorted(marks)は新しいソート済みリストを作って返してきます。元のmarksは触られません — 2行目がその証拠です。
marks.sort()は何も作りません。元のリストをその場でソートし、古い順序は永遠に消えます。
これは第12章のルールがまた登場したものです:リストを変えるメソッドは何も返しません。
marks = [72, 45, 90, 61]
marks = marks.sort()
print(marks)Noneappendとまったく同じです。.sort()では結果を保存してはいけません;sorted()では保存する必要があり、さもなくばせっかくの作業が捨てられてしまいます。
どっちを、いつ使う? 元の順序がもう要らなければ、.sort()のほうが簡単で軽いです。元のリストを別の場所で使う予定があるとき、あるいは自信がなければsorted()を使いましょう — 誰のものも奪いません。迷ったらsorted()です。
逆順と、数の答え
marks = [72, 45, 90, 61]
print(sorted(marks, reverse=True))
print(max(marks), min(marks), sum(marks))
print(round(sum(marks) / len(marks), 2))[90, 72, 61, 45]
90 45 268
67.0max、min、sumはメソッドではなく関数です。つまりmarks.max()ではなくmax(marks)と書きます。平均にも特別なものは何もありません:合計を個数で割ったものがそのまま平均で、round(..., 2)は表示用に丸めるだけです。
第10章の累積ループが1行に畳まれたことに気づきましたか。あれを学んだのは無駄ではありません — sumがしていることはまさにあのループですし、いつかPythonに用意された関数がないものを累積する必要が出てきます。
数えることと、探すこと
marks = [72, 45, 90, 45, 61]
print(marks.count(45))
print(marks.index(45))
print(marks.index(90))2
1
2count(x)はxが何回現れるかを教えます。index(x)は最初のものの場所を教えます — 45が2つあってもインデックス1を返し、2つ目には決して触れません。
そして存在しないものを探すと、プログラムは停止します:
marks = [72, 45, 90]
print(marks.index(50))ValueError: 50 is not in listremoveとまったく同じです。まずif 50 in marks:で確認する習慣をつけましょう。
テキストのソートとkey=
names = ["rafi", "Bilal", "ahmed"]
print(sorted(names))
print(sorted(names, key=str.lower))['Bilal', 'ahmed', 'rafi']
['ahmed', 'Bilal', 'rafi']人はこの最初の結果を見て、Pythonが間違えたと考えます。違います — Pythonは文字コードでソートしていて、そこでは大文字がすべて小文字より前に来ます。だからBilalが先頭に跳ねたのです。
key=str.lowerはこう言います:ソート中は、各名前を小文字であるかのように扱え。名前自体は変わりません — Bilalは今も大文字のBで表示されます — 比較にだけ、小文字化した形が使われるのです。
keyには任意のルールを与えられます:
words = ["bag", "pen", "notebook", "ink"]
print(sorted(words, key=len))['bag', 'pen', 'ink', 'notebook']長さでソートされました。bag、pen、inkはいずれも3文字ですが、もともといた順序のまま残ったことに注目してください — Pythonは同点を乱しません。この性質は安定ソートと呼ばれ、これが複数の条件でソートできるようにしているものです。
型が混ざったリストはソートできない
values = [3, "1", 2]
print(sorted(values))TypeError: '<' not supported between instances of 'str' and 'int'ソートはペアで比べることですが、第8章で、数とテキストは<で比べられないことを確かめました。メッセージも一字一句同じものです。これはCSVファイルから読んだリストで日常的に起きます — そこではすべての数が、本当はテキストなのです。
リストをテキストに — join
リストをそのまま表示すると、括弧とクォートが付いてきます。これは人に見せるものではありません:
names = ["rafi", "ahmed", "bilal"]
print(", ".join(names))
print(" | ".join(names))rafi, ahmed, bilal
rafi | ahmed | bilalこの行は初めて読むと逆に見えます:結合するテキストが先に来、リストは括弧の中に入ります。覚え方は — 「これらをこのもので結合せよ」です。
条件が1つあり、これが厳しいです:すべての値がテキストでなければなりません。
marks = [72, 45, 90]
print(", ".join(marks))TypeError: sequence item 0: expected str instance, int found数のリストを結合するには、まず各数をテキストに変えます:
marks = [72, 45, 90]
print(", ".join(str(m) for m in marks))72, 45, 90str(m) for m in marksの部分は新しい書き方で、後の章で詳しく学びます。当面は次のように読みます:「marksの中のそれぞれのmに対して、str(m)を作る」。そしてjoinがその文字列たちを結合します。下の完全な例でも、同じ行を使っています。
完全な例
report.py:
# A marks report: order them, summarise them, and show the top three
marks = [72, 45, 90, 61, 88, 45, 33]
ordered = sorted(marks, reverse=True)
print("Marks :", marks)
print("Ordered :", ordered)
print("Count :", len(marks))
print("Highest :", max(marks))
print("Lowest :", min(marks))
print("Total :", sum(marks))
print("Average :", round(sum(marks) / len(marks), 2))
print("How many 45s:", marks.count(45))
top_three = ordered[:3]
print("Top three:", top_three)
print("As text :", ", ".join(str(m) for m in top_three))
passed = []
for mark in marks:
if mark >= 40:
passed.append(mark)
print("Passed :", len(passed), "of", len(marks))Marks : [72, 45, 90, 61, 88, 45, 33]
Ordered : [90, 88, 72, 61, 45, 45, 33]
Count : 7
Highest : 90
Lowest : 33
Total : 434
Average : 62.0
How many 45s: 2
Top three: [90, 88, 72]
As text : 90, 88, 72
Passed : 6 of 7気づく価値のある設計の判断が2つあります。
.sort()ではなくsorted()を選んだのは、1行目でもう一度元のmarksを表示するからです。.sort()だったら「Marks」の行と「Ordered」の行が同じものを表示し、誰も何も文句を言わなかったはずです。まさにこの章の冒頭で話した、黙った間違いそのものです。
上位3人はスライスで取り出しました — ordered[:3]です。「上位3人」用の特別な関数は必要ありません:降順のリストの先頭3つが、そのまま上位3人です。何かがソートされると、驚くほど多くの質問がスライス1つで解決します。
うまくいかないとき
ソートしたらリストがNoneになった その行がmarks = marks.sort()でした。.sort()はリスト自体をソートしてNoneを返します — marks.sort()とだけ書きましょう。新しいリストが欲しいならordered = sorted(marks)です。
ソートしたら元のリストまで変わった その場でソートする.sort()を使いました。元のリストを触らずにすませるにはsorted()を使いましょう。
TypeError: '<' not supported between instances of 'str' and 'int' リストに数とテキストが混ざっています。print(marks)を実行して、どの値にクォートが付いているか見てみましょう。ファイルから来たものなら、たぶん全部がテキストで、int()が必要です。
ValueError: 50 is not in list index()かremove()に、リストに存在しない値を渡しました。まずinで確認しましょう。
TypeError: sequence item 0: expected str instance, int found join()に数のリストを渡しました。各数をstr()で包みましょう。
名前をソートしたら、先頭に大文字始まりが並んだ これはバグではなく、正しい挙動です。人が期待する順序でソートしたいときはsorted(names, key=str.lower)です。
ステップ 4 / 6 — 予想する
理解度をチェック
sorted()を使いました。2行は何と表示されますか?
marks = [72, 45, 90]
ordered = sorted(marks)
print(ordered)
print(marks)- A[45, 72, 90] [72, 45, 90]
- B[45, 72, 90] None
- C[45, 72, 90] [45, 72, 90]
- D[72, 45, 90] [45, 72, 90]
.sort()の結果をmarksに保存しています。何と表示されますか?
marks = [72, 45, 90]
marks = marks.sort()
print(marks)- A`AttributeError`
- B[72, 45, 90]
- C[45, 72, 90]
- DNone
数のリストをカンマで結合しようとしています。何が起きますか?
marks = [72, 45, 90]
print(", ".join(marks))- A`TypeError` — `join`はテキストしか結合できません
- B724590
- C72, 45, 90
- D[72, 45, 90]
回答にはアカウントが必要です
ログインして答えを確認
問題は上にあります。頭の中で答えを考えることがいちばん大切です。ログインすると、正解・解説・3 段階のヒントを確認できます。
やってみよう
inventory.pyというファイルを書いて、7個以上の商品名のリストと、その価格のリストを保存しましょう。
価格から次を表示します:
- 最高、最低、合計、平均(小数第2位まで)
- ソート済みの新しいリスト — 次に、元が変わっていないことを証明する
- スライスを使って、最も高い3つ
名前から次を表示します:
- 大文字小文字を無視したアルファベット順
- 名前の長さ順
joinを使って、名前をすべてカンマ区切りで1行に
そして実験を1つしてください:価格に.sort()を呼び、1の行をもう一度表示します。どの数字が変わり、どの数字は変わらなかったでしょう? 変わらなかった数字がなぜ変わらなかったのかを、自分で説明してみてください — その答えを握りしめておけば、この章の罠には二度と引っかかりません。