目次
-並び替え問題
-問題の内容
-悩んだ事
-ついに解決
-最後に
-補足
並び替え問題
コーディングテストに向けて、コーディングの問題集を解いている時に様々な要素に基づいて、並び替えを行う問題にチャレンジしました。
なんとか正解することはできたのだが、そこで見つけたコツがあったので紹介します。
問題は著作権に配慮して、元の問題の一部を抜粋したオリジナルです。
初心者のためもっといい書き方があれば、コメント欄でご教示いただけると嬉しいです。
私のレベルとしてはAtCoderでC~D、paizaで簡単なAランク問題をなんとか解く事ができるくらいのレベル感です。
※使用する言語はPython3ですが、他の言語でも本質は変わらないかと思いますので是非一読ください。
問題の内容
あるモデル事務所では、男性モデルの名前(name)と身長(height)と誕生年(year)を以下のコードのように集計しており、このデータを有る条件に基づいて並び替える必要があります。
条件は、
・身長の低い順に並べる。
・身長が同じ場合は、誕生年が大きい順に並べる。
・誕生年も同じ場合は、名前をアルファベット順に並べる。
people_list = [
{'Name': 'satoshi', 'height': 183, 'year': 2000},
{'Name': 'toru', 'height': 183, 'year': 2003},
{'Name': 'kento', 'height': 173, 'year': 1998},
{'Name': 'toshiki', 'height': 174, 'year': 2001},
{'Name': 'yuki', 'height': 178, 'year': 1996},
{'Name': 'hiroto', 'height': 187, 'year': 2000},
{'Name': 'kanji', 'height': 165, 'year': 1999},
{'Name': 'motoyuki', 'height': 171, 'year': 1999},
{'Name': 'hiroshi', 'height': 187, 'year': 2000},
{'Name': 'haruki', 'height': 173, 'year': 2006}
]
悩んだ事
まずsorted関数を使って、並び替えたら楽そうだと考えて、以下のようなコードを書いた。
import operator
people_list = sorted(people_list, key=operator.itemgetter('height', 'year', 'Name'))
[{'Name': 'kanji', 'height': 165, 'year': 1999},
{'Name': 'motoyuki', 'height': 171, 'year': 1999},
{'Name': 'kento', 'height': 173, 'year': 1998},
{'Name': 'haruki', 'height': 173, 'year': 2006},
{'Name': 'toshiki', 'height': 174, 'year': 2001},
{'Name': 'yuki', 'height': 178, 'year': 1996},
{'Name': 'satoshi', 'height': 183, 'year': 2000},
{'Name': 'toru', 'height': 183, 'year': 2003},
{'Name': 'hiroshi', 'height': 187, 'year': 2000},
{'Name': 'hiroto', 'height': 187, 'year': 2000}]
operator.itemgetterでkeyを指定すると、height、year、Nameの順に優先順位をつけて並び替えを行う事ができます。
一見これで完了に見えますが、大きな問題がありました。
このコードではheight、year、Nameが全て昇順で並び替えが実行されてしまうのです。
問題文を見ると、height、Nameはそれぞれ小さい順、アルファベット順で並び替えるので昇順で構わないのですが、yearは大きい順に並び替える必要があるようです。
ふざけてますね。
おそらく、このモデル事務所では若い子の方が将来性があるので重要視されているのでしょう。そういうクライアントもきっといるのだろうと考えて、改めてコードを見直してみます。
次に考えたのは、operatorモジュールは使わずに、sorted関数にラムダ関数を適用して、要素ごとに並び替える作戦です。
これならyearにだけreverse=Trueを適用して降順にする事ができそうです。
people_list = sorted(people_list, key= lambda x:x['height'])
people_list = sorted(people_list, key= lambda x:x['year'], reverse=True)
people_list = sorted(people_list, key= lambda x:x['Name'])
people_list = [
{'Name': 'haruki', 'height': 173, 'year': 2006},
{'Name': 'hiroshi', 'height': 187, 'year': 2000},
{'Name': 'hiroto', 'height': 187, 'year': 2000},
{'Name': 'kanji', 'height': 165, 'year': 1999},
{'Name': 'kento', 'height': 173, 'year': 1998},
{'Name': 'motoyuki', 'height': 171, 'year': 1999},
{'Name': 'satoshi', 'height': 183, 'year': 2000},
{'Name': 'toru', 'height': 183, 'year': 2003},
{'Name': 'toshiki', 'height': 174, 'year': 2001},
{'Name': 'yuki', 'height': 178, 'year': 1996}
]
アパーーーー🫡🤪
はい、崩壊してしまいました。
実行してから気づきましたが、このコードだと
①最初にheightで昇順に並び替える。
②その次にyearで降順に並び替える
③最後にNameで昇順に並び替える。
となっており、結局は名前の順に並び替えらてしまっているのです。
実際は要素ごとに優先順位をつけて並び替えたいので、改めてコードを見直しました。
ついに解決
しかし、この失敗は無駄ではありませんでした。
以上の実行結果をみたところ、
名前順→誕生年順→身長順というふうに優先順位をつけて並び替えられていることに気がつきました。
というのも、
people_list = sorted(people_list, key= lambda x:x['height'])
で並び替える時に身長が同じ人同士では、もともとリストのインデックスが小さい方から順に並び替えられます。
(つまり前後関係はそのまま)
言い換えると、仮に上記のコードを実行する前に誕生年順に並び替えられてさえいれば、
上記の身長順に並び替えるコードを実行した後には
身長順→誕生年順
に並び替える事ができているのです。
さらに、
people = sorted(people, key= lambda x:x['year'], reverse=True)
を実行する前に、すでに名前順(Name)でリストが並んでいれば、
誕生年順→名前順
で並び替えることができます。
以上の考察から、以下のコードで解決です。
people = sorted(people, key= lambda x:x['Name'])
people = sorted(people, key= lambda x:x['year'], reverse=True)
people = sorted(people, key= lambda x:x['height'])
優先順と逆にsorted関数を実行していくと、個別に昇順・降順を選択して並び替える事ができました。
これは他の状況でも使えるテクニックかなと思うので、知識としてインプットしておこうと思います。
最後に
もっと簡単な実装方法があれば是非コメント欄でご教示いただきたいです。
最後に、
コメント・ストック・ライクよろしくお願いします!
補足
コメント欄にて、同時にkeyを指定する方法を教えて頂いたので掲載させていただきます。
people_list = sorted(people_list, key=lambda x:(x['height'], -x['year'], x['Name']))
降順にしたいyearを
-x['year']
とする事で降順に並び替える事ができるようです。
ご教示いただきありがとうございます。