Pythonのリストとfor文の組み合わせってよく見かけます。みんなよくやりますよね。でも、for文と組み合わせなくてもやれることって意外と多いんです。今回はそんな問題。
以下はリストmylistから重複する要素を取り除いた結果を要素とするリストuniq_listを作成するコードである。ただし、重複する要素を取り除くコードはもっとシンプルに1行で記述できる。どんなコードにすればよいだろうか。ただし、要素の順序については考慮しなくてもよいものとする。
mylist = [6, 9, 9, 9, 6, 9, 7, 1, 9, 8]
uniq_list = []
for num in mylist:
if num not in uniq_list:
uniq_list.append(num)
print(uniq_list)
どうもHPかわさきです。
三寒四温も3月までかと思いきや、4月になっても続いていますねぇ。それでも、筆者のところには「春眠暁を覚えず」の時期がやってまいりました。アラームが1個じゃ足りないので、もう2つくらい設定しないとヤヴァいです。いつも眠いんですけどね。
それはともかく、リストから重複する要素を取り除く上のコード。分かりやすくてよいと思います。が、とあるデータ構造の特性を知っていると、for文を使わなくてもカンタンに同じことができちゃうんですよね。さて何でしょう?
以下に正解のコード例を示します。
mylist = [6, 9, 9, 9, 6, 9, 7, 1, 9, 8]
uniq_list = list(set(mylist))
print(uniq_list)
元のリストmylistから集合を作成し、それ基にリストを作成するだけで、重複する要素を排除したリストが得られます。
ここでおさらいです。集合はリストやタプルと同様に、Pythonに組み込みのコレクションです。その特徴としては次のようなことが挙げられます。
これらのことを踏まえて、最初のリストを見てみましょう。
mylist = [6, 9, 9, 9, 6, 9, 7, 1, 9, 8]
uniq_list = []
for num in mylist:
if num not in uniq_list:
uniq_list.append(num)
print(uniq_list)
ちなみにこの実行結果は次のようになります。
mylistの要素は、ハッシュ可能な整数値です(hash関数で「ハッシュ値=一種のデータの識別番号」を得られます)。Pythonの集合(set、重複のないデータの集まり)は内部でこのハッシュ値を使って要素を管理しているため、ハッシュ可能な整数値は集合の要素にできるということです。
そのため、mylistをset関数に渡すと、重複する要素が排除された集合ができます。試してみましょう。
mylist = [6, 9, 9, 9, 6, 9, 7, 1, 9, 8]
s = set(mylist)
print(s)
実行結果は次の通りです。
この通り、重複要素が取り除かれた集合が作成されました。
読者が上のコードを試した場合、集合の要素の並び方が画像に示したものとは異なるかもしれないことには注意してください。これは「集合の要素には順序がない」という性質からくるものです。
ただし、欲しいのは集合ではなくリストなので、得られた集合からリストを作成する必要があります。このことから、正解例のコードのように集合を作成して、そこからさらにリストを作成するコードを書くことになります。
mylist = [6, 9, 9, 9, 6, 9, 7, 1, 9, 8]
uniq_list = list(set(mylist))
print(uniq_list)
実行結果は次のようになります。
これで重複を排除したリストが完成しました。ただし、集合を作成した時点での画像からも分かるように、問題文のコードの実行結果([6, 9, 7, 1, 8]」というリスト)と上のコードの実行結果(「[1, 6, 7, 8, 9]」というリスト)では要素の順序が異なります。順序も考慮したいのであれば、別の方法が必要です。
その方法とは……別の問題にすることも考えましたが……辞書が持つfromkeysメソッド(クラスメソッド)を使うことです。fromkeysメソッドは反復可能オブジェクトを渡すと、それらをキーとする辞書を作成するものです。キーの値はfromkeysメソッドのvalueパラメーターに指定します(デフォルト引数値はNone)。
実際のコードは次のようになります。
mylist = [6, 9, 9, 9, 6, 9, 7, 1, 9, 8]
uniq_list = list(dict.fromkeys(mylist))
print(uniq_list)
ここでのポイントは以下の3つです。
mylistには重複する要素が複数あります。そこで、リストの先頭から順番に「リストの要素をキーとして、さらに何らかの値をそのキーの値として」辞書に追加していくことを考えてみましょう。つまり、「6/何らかの値」→「9/何らかの値」→「9/何らかの値」→「9/何らかの値」→……→「9/何らかの値」→「8/何らかの値」という順に辞書に要素を追加していくことを考えてみます。
1つ目のポイントから、辞書は「リストの要素(キー)/何らかの値」の挿入順序を覚えています。2つ目のポイント「辞書に1つのキーが複数存在することはない」というのは、辞書に何らかのキーが既に存在している場合に、同一のキー/新たな値の組をその辞書に挿入しようとすると、既存のキーの値が新しい値に書き換えられるということです。
つまり、「9/何らかの値1」という要素を辞書に追加した後に、「9/何らかの値2」という要素を追加しようとすると、既に存在するキー「9」の値が「何らかの値1」から「何らかの値2」に変更されるということです。このときに、キーの順序は以前のままです。そして、ここで「何らかの値」と書いているのは、fromkeysメソッドのvalueパラメーターに指定する値のことで、デフォルトではNoneになります。
これを踏まえて、問題文にあった元のリストをfromkeysメソッドに渡してみましょう。
mylist = [6, 9, 9, 9, 6, 9, 7, 1, 9, 8]
d = dict.fromkeys(mylist)
print(d)
実行結果は次の通りです。
リストの要素から目視で重複要素を取り除いていくと「6→9→7→1→8」となりますが、辞書のキーがこの通りに並んでいることに注目してください。
また、ポイントの3つ目に挙げたように、辞書を単純に反復可能オブジェクトとして関数などに渡すと、反復されるのはそのキーです。つまり、この辞書をlist関数に渡すと、キーが「6→9→7→1→8」という順番で取り出されてリストの要素になるということです。
uniq_list = list(d)
print(uniq_list)
実行すると、次のように元のリストであるmylistの要素の順序が維持されたリストが得られます(問題文のコードの実行結果「[6, 9, 7, 1, 8]」と同じ)。
まとめると、要素の順序を無視してよければ以下のように集合を作成して、そこからさらにリストを作成します。
mylist = [6, 9, 9, 9, 6, 9, 7, 1, 9, 8]
uniq_list = list(set(mylist))
print(uniq_list)
一方、要素の順序が重要な場合は以下のようにfromkeysメソッドで辞書を作成して、そこからさらにリストを作成します。
mylist = [6, 9, 9, 9, 6, 9, 7, 1, 9, 8]
uniq_list = list(dict.fromkeys(mylist))
print(uniq_list)
実はset関数で集合を作る方法も、fromkeysメソッドで辞書を作成する方法もうまくかない場合があります。これについてはまた別の問題として皆さんに見ていただこうと考えています。その日を楽しみにしていてください。
今回はset関数とlist関数、またはdict.fromkeysメソッドとlist関数を組み合わせるだけだから、そんなに書くことないだろうと予想していたのですが、意外に文量が多くなっちゃいました。少し長い記事なので読んでいると眠くなっちゃうかもしれません。でも、それは春が原因であって、記事のせいじゃないですからね(多分……)。
あ、忘れていました。集合については「Python入門」の「集合」で、辞書については「辞書」で説明しているので、よろしければそちらもぜひ。
初心者向け、データ分析・AI・機械学習・Pythonの勉強方法 @ITのDeep Insiderで学ぼう
Copyright© Digital Advantage Corp. All Rights Reserved.
Deep Insider 髫ェ蛟�スコ荵斟帷ケ晢スウ郢ァ�ュ郢晢スウ郢ァ�ー