読者です 読者をやめる 読者になる 読者になる

歩いたら休め

If the implementation is easy to explain, it may be a good idea.

【Python】Rubyの配列やハッシュのメソッドをPythonで再現する

Ruby Python itertools

友だちが「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にあるような連結リストを想像する方もいるかもしれませんが、 実際は配列なのでご注意ください

既に素晴らしい記事を書いている方がいらっしゃるのでそちらもご覧ください。 車輪の再発明感は拭えませんが、この記事ではもう少し細かいところまでまとめられればと思います。

yoghurt1131.hatenablog.com

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_consconsってLispのcons)が由来なんしょうか?

each_slice

既に素晴らしい記事を書いている方がいました。

http://blog.livedoor.jp/dankogai/archives/51838970.html

このエントリを見て、PythonRubyの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_bysortメソッドは安定ソートでない(比較結果が同じ要素を元の順序通りに並ばないことがある) そうなので注意が必要です。

また、既にご存じだと思いますが、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)のような冗長な書き方になってしまうから、 という理由もあるように思います。

また、Rubysortメソッドにブロックを渡したときのように、2つずつの要素を取り出して比較したい場合もあるかもしれません。

Python2.7では、sorted関数/.sortメソッドにcmpという引数があり、 組み込み関数に宇宙船演算子(<=>)と同じ挙動のcmp関数が用意されていたのですが、 Python3.5では削除されています。

Python3.5で2.7のcmpのような挙動(Rubysortメソッド+ブロックのような挙動)が欲しい場合、 公式ドキュメントによると、 標準ライブラリの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に関数を渡すこととRubymix_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ではそんなに遅延評価のメリットを感じられませんが、mapfilterでは役立ちます。

inject (reduce), each_with_object, join

Pythonでもreduceという畳み込みの関数が用意されているのですが、 Effective Rubyの『項目19 reduceを使ってコレクションを畳み込む方法を身に付けよう』を読んで その便利さに驚かされたことがあります

Effective Ruby

Effective Ruby

例えば、(以前の記事のそのままの例なのですが)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"

余談ですが、Rubyinjectreduceの別名のエイリアスが用意されていることについては、 Rubyist Magazine - map と collect、reduce と inject ―― 名前の違いに見る発想の違いの内容が面白かったのでぜひ読んでください。

map (collect), select, reject

配列全体に関数やメソッドを適用するmapと、 条件に当てはまる要素だけを残すselect(と真偽値が逆のバージョンのreject)です。

Rubyの配列操作をPythonで書くと?』の記事にある通りです。 ただし、Python3.*ではmapfilterが遅延評価されるように変更されています。 詳しくは以下の記事を読んでください。

postd.cc

mapfilterなどの公開関数と、ジェネレーター内包表記で機能が被っています。 どちらを使ってもいいのですが、私は内包表記のほうをよく使います。 配列の要素の変換とフィルタリングが同時に行えるし、 括弧によって戻り値をリスト、ジェネレーター、集合(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では同じメソッドの別名のエイリアスを用意したり、ifunlessが用意されていたり、 文脈によってきちんと使い分けられれば、コードの意図が明快なプログラミングを行いやすいです。

include?

# Ruby
solami_smile = %w(laala mirei sophy)
puts solami_smile.include? 'sophy'
# => True
puts solami_smile.include? 'dorothy'
# => False

Pythonではinという演算子を利用します。

# 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

Rubyany?メソッドではtrueの値が見つかった時点で(all?false が見つかった時点で)、 それ以上のループを打ち切ります。

Pythonではanyallは関数として用意されています。 真偽値の一覧を返す内包表記と組み合わせて使うことが多いです。 (ここでジェネレーターを使うことで、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にも、スライスのための専用の表記があります。

www.pythonweb.jp

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には簡単に操作できるメソッドは用意されていません。

www.lifewithpython.com

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では、itertoolsgroupby関数で同様の機能が使えます。 ただし、公式ドキュメントにあるとおり、

  • 戻り値がハッシュ(辞書)ではなくジェネレーターであること(一度ループさせると中身が消える)
  • key 関数の値が変わるたびに新しいグループが作られること

に注意する必要があります。上のようなSQLっぽい集約のために使うなら、あらかじめsorted関数でソートしておきましょう。

simanman.hatenablog.com

まとめ

他にもRubyのメソッドはたくさんありますが、Rubyに慣れたプログラマーがつまづきやすいところを中心に紹介したつもりです。 (もっと良い方法があれば教えてください!)

Pythonもイケてるよ!」という記事にしたつもりなのですが、each_consuniqなどは思ったより再現が難しく感じました。 普段使っている便利なメソッドを、自力で再現すると勉強になりますね…。

また、Pythonitertoolsにはまだ便利な機能が眠っている気がしています。 一度調べてみたいです。

このモジュールは イテレータ を構築する部品を実装しています。プログラム言語 APL, Haskell, SML からアイデアを得ていますが、 Python に適した形に修正されています。

やっぱりHaskellを一度ガッツリ触るべきなのかな…。型のしっかりした言語もやりたいし。

Effective Python ―Pythonプログラムを改良する59項目

Effective Python ―Pythonプログラムを改良する59項目