友だちが「Rubyはいろいろなメソッドがあって柔軟だから、 そっちに慣れちゃうとPython書くときちょっと困るんだよね」と言っていました。
たしかに、Rubyは「配列やハッシュに対してこんな機能があればいいな」と感じたとき、 そのデータ型のリファレンスを調べれば大抵見つかります。
しかし、Pythonでもいろいろな便利な関数を使って同等の機能を実現することができます。 ただし、Rubyに比べてオブジェクト指向っぽくないところがあり、 オブジェクトからメソッドを呼び出すのではなく関数やラムダ式を利用する必要あるので、 最初は少し戸惑うかもしれません。
そこで、「Rubyの便利なメソッドを、Pythonのコードで自然に書くならこうだろう」という記事を まとめてみることにしました。 といってもキリがないので、配列とハッシュのメソッドを中心にします。
Rubyは2.2.2、Pythonは3.4.1を利用します。 また便宜上、この記事の中ではPythonの「リスト」と「辞書」をそれぞれRubyの対応する型である 「配列」「ハッシュ」と呼ぶことにします。
$ ruby -v ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-darwin14] $ python -V Python 3.4.3
余談ですが、Pythonは「ハッシュテーブル」を「辞書」と呼ぶなど、 プログラマ以外にも直感的に分かりやすい名前をつけたがるようです。 リストと言われてLispにあるような連結リストを想像する方もいるかもしれませんが、 実際は配列なのでご注意ください。
既に素晴らしい記事を書いている方がいらっしゃるのでそちらもご覧ください。 車輪の再発明感は拭えませんが、この記事ではもう少し細かいところまでまとめられればと思います。
each
プリパラアイドルをターミナルに列挙するプログラムを書きたいとします。
Rubyでもfor
の制御構文は用意されているものの、
実際のプログラミングではeach
メソッドを呼び出すほうが好まれているようです。
オブジェクト指向!って感じですね。
# Ruby idols = %w(laala mirei sophy) idols.each do |idol| puts idol end
ちなみにRubyの元ネタの純粋オブジェクト指向言語のSmalltalkではさらに徹底しており、 条件分岐(if)も真偽値のメソッドとして定義されているという 話を聞いたことがあります。
ハッシュの各要素をループさせることもできます。
# Ruby idol_group_dict = { 'sorami smile' => %w(laala mirei sophy), 'dressing pafe' => %w(shion dorothy leona) } idol_group_dict.each do |group_name, members| members.each do |idol| puts "#{idol} is a member of #{group_name}" end end
Pythonではfor ~ in ~
の制御構文を使ってこう書きます。
# Python idols = ['laala', 'mirei', 'sophy'] for idol in idols: print(idol)
ハッシュの各要素をループさせるには、辞書型の.items()
メソッドを利用します。
ふつうにfor
文に渡すとハッシュのキーしか取り出せないので面食らうかもしれません。
# Python idol_group_dict = { 'sorami smile': ['laala', 'mirei', 'sophy'], 'dressing pafe': ['shion', 'dorothy', 'leona'] } for group_name, members in idol_group_dict.items(): for idol in members: print('{} is a member of {}'.format(idol, group_name))
ちなみにPython3.6からはf'{idol} is member of {group_name}')
という形でも
文字列リテラルができるようになるそうです。
Rubyに比べて冗長だったので嬉しいです。
each_with_index
インデックスも含めてループさせたい場合、Rubyではeach_with_index
を使うと便利です。
# Ruby school_rules = %w(生徒は自分に自信を持たなければならない) school_rules.each_with_index do |rule, i| puts "パプリカ学園校則第#{i+1}条! #{rule}!" end
インデックスを0以外から始めたい場合は、each.with_index
を使います。
(each
メソッドでEnumerator型に変換した後、with_index
メソッドを呼び出しています。)
ちなみにeach
だけでなくmap
等の他のメソッドとも組み合わせられます。
# Ruby school_rules = %w(生徒は自分に自信を持たなければならない) school_rules.each.with_index(1) do |rule, i| puts "パプリカ学園校則第#{i}条! #{rule}!" end
Pythonではenumerate
という関数を利用します。
# Python school_rules = ['生徒は自分に自信を持たなければならない'] for i, rule in enumerate(school_rules): print("パプリカ学園校則第{}条! {}!".format(i+1, rule))
インデックスを1から始めたい場合は、enumerate
の引数に指定してあげるだけです。
# Python school_rules = ['生徒は自分に自信を持たなければならない'] for i, rule in enumerate(school_rules, 1): print("パプリカ学園校則第{}条! {}!".format(i, rule))
each_cons
子どものために「おかあさんといっしょ」のエンディングテーマを出力したいお父さんプログラマーも多いことでしょう。 (今でもこの歌なんでしょうか?)
そんなとき、each_cons
を使うと自然に書けます。
# Ruby song_with_mam = %w(クジラ ラッコ コアラ ライオン 女の子 男の子) song_with_mam.each_cons(2) do |x, y| puts "#{x} になりたい #{y}♪" end # => クジラになりたいラッコ♪ # => ラッコになりたいコアラ♪ # ...
このような場合にはPythonではどう書けば自然なのかパッと思いつきません…。zip
関数を使って
# Python song_with_mam = ['クジラ', 'ラッコ', 'コアラ', 'ライオン', '女の子', '男の子'] for x, y in zip(song_with_mam, song_with_mam[1:]): print('{}になりたい{}♪'.format(x, y))
とか2つループを回して
# Python song_with_mam = ['クジラ', 'ラッコ', 'コアラ', 'ライオン', '女の子', '男の子'] for i, x in enumerate(song_with_mam): for j, y in enumerate(song_with_mam): if j - i == 1: print('{}になりたい{}♪'.format(x, y))
とか、一つ前の値を残して
# Python song_with_mam = ['クジラ', 'ラッコ', 'コアラ', 'ライオン', '女の子', '男の子'] prev = None for x in song_with_mam: if x is not None: print('{}になりたい{}♪'.format(prev, x)) prev = x
とかになるんでしょうか?
と思ったのですが、Pythonのイテレータを扱う標準ライブラリであるitertools
を使うのが自然かもしれません。
Itertools レシピのサンプルコードを使うとこのようになります。
# Python from itertools import tee def pairwise(iterable): a, b = tee(iterable) next(b, None) return zip(a, b) song_with_mam = ['クジラ', 'ラッコ', 'コアラ', 'ライオン', '女の子', '男の子'] for x, y in pairwise(song_with_mam): print('{}になりたい{}♪'.format(x, y))
要素が3つ以上の場合のすぐに拡張もできそうです。ただ、複数個ずつ要素を取り出すだけなのに、大げさなコードになってしまった感はあります。
ところで、メソッド名のeach_cons
のcons
ってLispのcons
)が由来なんしょうか?
each_slice
既に素晴らしい記事を書いている方がいました。
こちらもitertools
を使うのが自然のようですね。
Itertools レシピにあるコードがこちらです。
(上の記事ではPython2系の時代なのでizip_longest
という関数名になっています。)
# Python from itertools import zip_longest def grouper(iterable, n, fillvalue=None): "Collect data into fixed-length chunks or blocks" # grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx" args = [iter(iterable)] * n return zip_longest(*args, fillvalue=fillvalue)
length (size)
配列やハッシュ、そして文字列等の長さを調べるメソッドです。
# Ruby triangle = %(kanon pinon junon) puts "Triangle is a group of #{triangle.length} idols."
Pythonでは、メソッドではなくlen
という関数を使います。Pythonではある程度一般的な操作
(RubyではEnumerable
のメソッドにあるようなもの)は
関数や構文として用意されていることが多いです。
triangle = ['kanon', 'pinon', 'junon'] print('Triangle is a group of {} idols.'.format(len(triangle)))
sort, sort_by, sort!, sort_by!
配列の中身をソートする場合、
Rubyではsort
を呼び出すか、sort_by
のブロックにソート基準を渡すことが多いと思います。
(個人的に、sort
メソッドでブロックを呼び出すとき、宇宙船演算子(<=>
)を定義するか、
戻り値に-1 or 0 or 1を返すブロックを作る必要があって、頭の中がごちゃごちゃしてしまうことが多いです…。)
# Ruby p [3, 0, 1].sort # => [0, 1, 3] # 名前の長さでソートする names = %w(laala fuwari dore shio) p names.sort_by { |n| n.size } # => ["shio", "dore", "laala", "fuwari"] p names.sort_by(&:size) # 簡略化した書き方
余談ですが、sort_by
やsort
メソッドは安定ソートでない(比較結果が同じ要素を元の順序通りに並ばないことがある)
そうなので注意が必要です。
また、既にご存じだと思いますが、sort!
やsort_by!
オブジェクト自身を変化させる破壊的メソッドです。
コピーを作らないため、破壊的メソッドのほうがパフォーマンスが良い場合が多いようです。
names = %w(laala fuwari dore shio) names.sort_by! { |n| n.size } p names # => ["shio", "dore", "laala", "fuwari"]
Pythonでは非破壊操作はsorted
関数を、 破壊的操作は配列(リスト型)の.sort
メソッドを使います。
ちなみに、これらのソートは安定ソートだそうです。
# Python print(sorted([3, 0, 1])) # => [0, 1, 3] # 名前の長さでソートする names = ['laala', 'fuwari', 'dore', 'shio'] print(sorted(names, key=len)) # => ["shio", "dore", "laala", "fuwari"]
破壊的操作の場合のコードです。
# Python names = ['laala', 'fuwari', 'dore', 'shio'] names.sort(key=len) print(names) # => ["shio", "dore", "laala", "fuwari"]
ところで、Rubyのブロックという強力な機能が使えない代わりに、 Pythonでは高階関数で(比較基準となる関数を引数に与えて)機能を実現していることが多いです。
たまにPythonは「配列.size
じゃなくてlen(配列)
なのはオブジェクト指向言語として一貫性がないよね?」
とツッコまれていますが、メソッドとして定義してしまうとnames.sort(key=lambda x: x.size)
のような冗長な書き方になってしまうから、
という理由もあるように思います。
また、Rubyのsort
メソッドにブロックを渡したときのように、2つずつの要素を取り出して比較したい場合もあるかもしれません。
Python2.7では、sorted
関数/.sort
メソッドにcmp
という引数があり、
組み込み関数に宇宙船演算子(<=>
)と同じ挙動のcmp
関数が用意されていたのですが、
Python3.5では削除されています。
Python3.5で2.7のcmp
のような挙動(Rubyのsort
メソッド+ブロックのような挙動)が欲しい場合、
公式ドキュメントによると、
標準ライブラリのfunctools
の中にcmp_to_key
があり、これを使って変換した関数をkey
引数に渡せばいいようです。
旧式の cmp 関数を key 関数に変換するには functools.cmp_to_key() を使用してください。
min, min_by, max, max_by
これもsort
の場合と似ています。
# Ruby # 一番大きな数字を探す tension = [1, 2, 3, 4, 5] p tension.max # => 5 idols = [ {name: 'mirei', gobi: 'puri'}, {name: 'ajimi', gobi: 'davinci'}, {name: 'cosmo', gobi: 'cosmic'} ] # 語尾が一番長いキャラクターを探す p idols.max_by { |x| x[:gobi].size } # => {:name=>"ajimi", :gobi=>"davinci"}
Pythonの場合、max
の引数key
に関数を渡すこととRubyのmix_by
のような挙動になります。
# Python # 一番大きな数字を探す tension = [1, 2, 3, 4, 5] print(max(tension)) idols = [ {'name': 'mirei', 'gobi': 'puri'}, {'name': 'ajimi', 'gobi': 'davinci'}, {'name': 'cosmo', 'gobi': 'cosmic'} ] # 語尾が一番長いキャラクターを探す print(max(idols, key=lambda x: len(x['gobi']))) # => {'name': 'ajimi', 'gobi': 'davinci'}
reverse, reverse!
これもソートの場合とよく似ており、reversed
関数や.reverse
メソッドを利用します。
ただし、reversed
関数の戻り値は配列(list
)ではなくlistreverseiterator
というイテレーターオブジェクトが返ってくるので注意しましょう。
# Python print(reversed([1, 2, 3])) # => <list_reverseiterator object at 0x103c03320> # list型に変換しましょう print(list(reversed([1, 2, 3])))
公式ドキュメント(http://docs.python.jp/3.5/library/functions.html#reversed)には以下のような記述があります。
要素を逆順に取り出すイテレータ (reverse iterator) を返します。 seq は __reverse__() メソッドを持つか、シーケンス型プロトコル (__len__() メソッド、および、 0 以上の整数を引数とする __getitem__() メソッド) をサポートするオブジェクトでなければなりません。
Pythonは、Rubyに比べてカジュアルに遅延評価を行う言語だからかもしれません。
# Python # long_listは超長い配列 for x in reversed(long_list): function(x)
このような場合、reversed
関数が配列を返す実装だった場合、reversed(long_list)
で一度配列全部をループして操作した後に
もう一度for
文のループが回ります。
遅延評価するオブジェクトを返すことで、ループが一度で済むようになっています。
正直、reverse
ではそんなに遅延評価のメリットを感じられませんが、map
やfilter
では役立ちます。
inject (reduce), each_with_object, join
Pythonでもreduce
という畳み込みの関数が用意されているのですが、
Effective Rubyの『項目19 reduceを使ってコレクションを畳み込む方法を身に付けよう』を読んで
その便利さに驚かされたことがあります。
- 作者: Peter J.Jones
- 出版社/メーカー: 翔泳社
- 発売日: 2015/01/19
- メディア: Kindle版
- この商品を含むブログ (4件) を見る
例えば、(以前の記事のそのままの例なのですが)URLの配列(リスト)を、{URL => ページタイトル}というハッシュテーブル(辞書)に変換したいとします。 同時に、ページのURLにgoogleが含まれているものを除外したいとします。
このようなとき、inject
の初期値をハッシュにすることで、ハッシュの中に畳み込んでいくことができます。
# Ruby # ループした結果をハッシュ(デフォルト値では{})のキーに代入していく def create_url_table(urls) urls.inject({}) do |hash, url| hash[url] = get_title(url) unless url =~ %r{google} hash end end
しかし、私は今ではeach_with_object
を利用してます。
# Ruby def create_url_table(urls) urls.each_with_object({}) do |url, hash| hash[url] = get_title(url) unless url =~ %r{google} end end
Pythonでは同様の処理を辞書内包表記を使って書くことができます。
# Python def create_url_table(urls): return {url: get_title(url) for url in urls if 'google' not in url}
もう少し条件が複雑になってくると、ジェネレーターに切り出すようにしています。 『コレクションを畳み込む方法を身に付けよう』で紹介されているような例だと、 内包表記やジェネレーターを使うことが多いです。
# Python def create_url_table(urls): return dict(_url_generator(urls)) def _url_generator(urls): for url in urls: if 'google' not in url: yield (url, get_title(url))
Rubyでは配列の中身の数値の合計値を計算する際にもinject
を使うため、inject
が登場する機会は多いです。
# Ruby [1, 2, 3].inject(:+) # Ruby2.4から.sumも利用できるようになるそうです [1, 2, 3].sum
一方で、Pythonは組み込み関数であるsum
を利用します。
そのせいか、Python3.*になった際にreduce
は組み込み関数でなくなってしまい、
functools
から呼び出す必要があります。
# Python sum([1, 2, 3])
同じように、一つの文字列に結合する際には、Rubyでは配列から文字列結合のメソッド.join
を呼び出しますが、
# Ruby puts %(laala mirei sophy).join(', ') # => "laala, mirei, sophy"
Pythonでは、文字列型の.join
メソッドに配列を渡すようになっています。
これもlen
関数と同じく、オブジェクト指向言語のプログラマーが嫌がることの一つです。
# Python print(', '.join(['laala', 'mirei', 'sophy'])) # => "laala, mirei, sophy"
余談ですが、Rubyにinject
とreduce
の別名のエイリアスが用意されていることについては、
Rubyist Magazine - map と collect、reduce と inject ―― 名前の違いに見る発想の違いの内容が面白かったのでぜひ読んでください。
map (collect), select, reject
配列全体に関数やメソッドを適用するmap
と、
条件に当てはまる要素だけを残すselect
(と真偽値が逆のバージョンのreject
)です。
『Rubyの配列操作をPythonで書くと?』の記事にある通りです。
ただし、Python3.*ではmap
やfilter
が遅延評価されるように変更されています。
詳しくは以下の記事を読んでください。
map
やfilter
などの公開関数と、ジェネレーター内包表記で機能が被っています。
どちらを使ってもいいのですが、私は内包表記のほうをよく使います。
配列の要素の変換とフィルタリングが同時に行えるし、
括弧によって戻り値をリスト、ジェネレーター、集合(set
)を選べるからです。
Rubyには破壊的操作版のmap!
やselect!
もあります。
これは関数の中でメソッドチェーンする際に使っています。
それ以外で使ったことは私はほとんど無いです。
# Ruby # (あんまり例は良くないですが)メソッドチェーンで書く代わりに def count_group_mamber(idols, group_name) idols.filter { |x| x[:group_name] == group_name }. map { |x| x[:name] }. size end # 関数の中では破壊的変更のメソッドで書いています def count_group_mamber(idols, group_name) arr = idols.filter { |x| x[:group_name] == group_name } arr.map! { |x| x[:name] } arr.size end
RubyでもPythonと同様に遅延評価を行いたいなら、.lazy.map
のように書きます。
詳しくは『怠惰で短気で傲慢な君に贈るRubyの遅延評価』を読みましょう。
empty?
Rubyには、配列が空かどうかを判定する.empty?
というメソッドがあります。
これにより明快なコードが書けるようになります。
例えば、名前の配列(names
)を名簿を管理しているDBに問い合わせるような場合、
もしnames
が空ならDBに問い合わせるまでもなく空の配列を返せばいいとします。
# Ruby def search_ids(names) return [] if names.empty? # 名簿DBに問合せる処理 # ... ids end
Pythonでは.empty?
のようなメソッドは無いため、空の配列が偽であることを利用して以下のように書くと思います。
def search_ids(names): if not names: # もしくは len(names) == 0 return [] # 名簿DBに問合せる処理 # ... return ids
Pythonは「あんまり特殊な関数や構文を追加するとわけわかんなくなるから最小限にとどめよう」という 考え方が強いように思います。
一方で、Rubyでは同じメソッドの別名のエイリアスを用意したり、if
とunless
が用意されていたり、
文脈によってきちんと使い分けられれば、コードの意図が明快なプログラミングを行いやすいです。
include?
# Ruby solami_smile = %w(laala mirei sophy) puts solami_smile.include? 'sophy' # => True puts solami_smile.include? 'dorothy' # => False
# Python solami_smile = ['laala', 'mirei', 'sophy'] print('sophy' in solami_smile) # => true print('dorothy' in solami_smile) # => false
また、in
演算子は、ある文字が文字列の中に含まれるかや、ハッシュのキーが存在するか(Rubyの.hash_key?
メソッドの用途)にも使われます。
(Python2.*では辞書型にhas_key
メソッドも存在したのですが、
3系ではin
に統一されました。)
# Python print('la' in 'laala') # => true print('do' in 'laala') # => False idol_group_dict = { 'sorami smile': ['laala', 'mirei', 'sophy'], 'dressing pafe': ['shion', 'dorothy', 'leona'] } print('sorami_smile' in idol_group_dict) # => true
any?, all?
配列の中に〇〇なものがいるかどうか、全部〇〇なのかを調べるとき、any?
やall?
のメソッドは便利です。
例えばプリパラの各チームのメンバーの中に〇〇がいるかどうか確認したいとき、以下のようなコードを書くと思います。 (集合演算を使う方法もあると思いますが)
# Ruby solami_smile = %w(laala mirei sophy) solami_dressing = %w(laala mirei sophy shion dorothy) gaarmageddon = %(aroma mikan gaaruru) boys = %(meganii leona) def cute?(pripara_idol) true end # ソラミスマイルの中にガァルマゲドンのメンバーが1人でもいるか p solami_smile.any? { |x| gaarmageddon.include?(x) } # => false # ソラミドレッシングに男子がいるかどうか p sorami_dressing.any? { |x| boys.include?(x) } # => true # ソラミスマイルにソラミスマイルのメンバーが全員含まれるか p solami_smile.all? { |x| solami_dressing.include?(x) } # => true # ソラミスマイルが全員カワイイかどうか p solami_smile.all? { |x| cute?(x) } # => true
Rubyのany?
メソッドではtrue
の値が見つかった時点で(all?
はfalse
が見つかった時点で)、
それ以上のループを打ち切ります。
Pythonではany
やall
は関数として用意されています。
真偽値の一覧を返す内包表記と組み合わせて使うことが多いです。
(ここでジェネレーターを使うことで、Rubyと同様にany
関数で評価している中でTrue
が見つかった時点でループを打ち切ることができます)
# Python def is_cute(pripara_idol): return True # ソラミスマイルの中にガァルマゲドンのメンバーが1人でもいるか print(all((x in gaarmageddon) for x in solami_smile)) # => False # ソラミスマイルが全員カワイイかどうか print(any(is_cute(x) for x in solami_smile)) # => True
Rubyには他にもone?
やnone?
などのメソッドもあります。
uniq, uniq!
割とPythonを扱う際に鬼門なのが値を一意にするメソッドです。
順番を気にしなくていい & 配列の中の変数がイミュータブルなら、一度集合型(set
)に変換してしまいましょう。
ただし、変数の順番が保持される必要があります。
※ Python3.6からRubyと同じくハッシュの順番が保持されるようになるらしいので、 集合型でも順番が保持されるようになるかもしれません(単なる期待ですが)。
# Python nums = [1, 2, 3, 4, 2, 3, 4] nums_set = set(nums) print(nums_set) # => {1, 2, 3, 4} # 配列に戻す print(list(nums_set)) # => [1, 2, 3, 4]
順番を保持したい場合は、『Python Tips:リストから重複した要素を削除したい』で同様の議論がされています。 また、Itertools レシピの中にも、もう少し難しい場合のサンプルがあります。
# Python def f7(seq): seen = set() seen_add = seen.add return [ x for x in seq if x not in seen and not seen_add(x)] print(f7([1, 2, 3, 4, 2, 3, 4]))
また、sorted
関数でインデックスを基にソートするという手もあります。
# Python nums = [1, 2, 3, 4, 2, 3, 4] nums = sorted(set(nums), key=nums.index) # => [1, 2, 3, 4]
ただし、ミュータブルなオブジェクト(例えば配列やハッシュ)は、set
の要素に含めることができません。
そのため、ユニークにする際にエラーが出てしまいます。
# Python idol_groups = [['laala', 'mirei', 'sophy'], ['shion', 'dorothy', 'leona'], ['laala', 'mirei', 'sophy']] set(idol_groups) # ※以下のようなエラーが出ます # Traceback (most recent call last): # File "<stdin>", line 1, in <module> # TypeError: unhashable type: 'list'
このような場合にどうすればいいのか調べたところ、 『Pythonのリスト内包表記でRubyのuniqメソッドと同じ事をする』 という記事の中にある例がまあ読める例だと感じました。
# Python idol_groups = [['laala', 'mirei', 'sophy'], ['shion', 'dorothy', 'leona'], ['laala', 'mirei', 'sophy']] print([x for i, x in enumerate(idol_groups) if i == idol_groups.index(x)]) # => [['laala', 'mirei', 'sophy'], ['shion', 'dorothy', 'leona']]
コードの中に頻繁に使うなら、
組み込みのlist
型を継承してUniqueList
みたいな名前で専用のクラスを作ってしまったほうがいいのかもしれません。
freeze
配列やハッシュに限りませんが、オブジェクトに破壊的変更が加えられないように制限するメソッドです。
Pythonではイミュータブルな配列としてタプルが、 イミュータブルな集合型としてfrozensetがあります。
先程uniq
のところで述べた通り、ハッシュのキーや集合型にミュータブルなオブジェクトである配列が指定できません。
そのため、そのような場合に配列を指定する必要があるときにタプルを利用します。
「あれ?じゃあfrozendictは無いの?」と思ったら一度提案されてリジェクトされているようです。
ハッシュのキーにハッシュを指定したい場合(ややこしい)は、.items()
メソッドで変換したものをタプルにすると良いと思います。
# Python profile = {'name': 'mirei', 'gobi': 'puri'} tuple(profile.items()) # => (('name', 'mirei'), ('gobi', 'puri'))
slice
配列の一部を取得するメソッドです。Pythonにも、スライスのための専用の表記があります。
transpose
行列(2次元配列)の転置を行う場合、Rubyには専用のメソッドが用意されています。 Pythonでは
# Python matrix = [[1, 2, 3], [4, 5, 6]] print(list(zip(*martix))) # => [(1, 4), (2, 5), (3, 6)]
ただし、転置行列が必要なレベルの数値計算を行う場合、numpy
を利用することが多いと思います。
こちらにはtranspose
のメソッドが用意されています。
flatten
こちらも、Pythonには簡単に操作できるメソッドは用意されていません。
group_by
私が好きなメソッドの一つです。以前書いた記事のまんまですが、 メソッドチェーンでデータベースのテーブル操作みたいなことが簡単にできます。
# Ruby # スターライト学園のアイドルの名簿 name_list = [ {'name' => 'ichigo', 'age' => 16, 'group' => 'soleil'}, {'name' => 'aoi', 'age' => 17, 'group' => 'soleil'}, {'name' => 'ran', 'age' => 17, 'group' => 'soleil'}, {'name' => 'akari', 'age' => 13, 'group' => 'luminas'}, {'name' => 'sumire', 'age' => 14, 'group' => 'luminas'}, {'name' => 'hinaki', 'age' => 14, 'group' => 'luminas'}, ] def col_mean(list, colname) list.reduce(0){|x, y| x + y[colname]} / list.size.to_f end # 各グループの年齢で再集計 p name_list. group_by{|raw| raw['group']}. map{|k, v| {k => col_mean(v, 'age')}} # => [{"soleil"=>16.666666666666668}, {"luminas"=>13.666666666666666}]
Pythonでは、itertools
のgroupby
関数で同様の機能が使えます。
ただし、公式ドキュメントにあるとおり、
- 戻り値がハッシュ(辞書)ではなくジェネレーターであること(一度ループさせると中身が消える)
- key 関数の値が変わるたびに新しいグループが作られること
に注意する必要があります。上のようなSQLっぽい集約のために使うなら、あらかじめsorted
関数でソートしておきましょう。
まとめ
他にもRubyのメソッドはたくさんありますが、Rubyに慣れたプログラマーがつまづきやすいところを中心に紹介したつもりです。 (もっと良い方法があれば教えてください!)
「Pythonもイケてるよ!」という記事にしたつもりなのですが、each_cons
やuniq
などは思ったより再現が難しく感じました。
普段使っている便利なメソッドを、自力で再現すると勉強になりますね…。
また、Pythonのitertools
にはまだ便利な機能が眠っている気がしています。
一度調べてみたいです。
このモジュールは イテレータ を構築する部品を実装しています。プログラム言語 APL, Haskell, SML からアイデアを得ていますが、 Python に適した形に修正されています。
やっぱりHaskellを一度ガッツリ触るべきなのかな…。型のしっかりした言語もやりたいし。
Effective Python ―Pythonプログラムを改良する59項目
- 作者: Brett Slatkin,石本敦夫,黒川利明
- 出版社/メーカー: オライリージャパン
- 発売日: 2016/01/23
- メディア: 大型本
- この商品を含むブログ (4件) を見る