LoginSignup
2
1

More than 3 years have passed since last update.

RubyのEnumerableモジュールのメソッドを、Python3ではどう書くか

Last updated at Posted at 2019-01-03

大変便利なRubyのEnumerableモジュールのメソッドと同様の処理を、Python3ではどのように書くのが簡単か、まとめたいと思います。

map

レシーバーの各要素に関数を適用した結果を配列にして返します。

Rubyでは関数をブロックとして渡します。mapにはcollectという別名もあります。

map.rb
x = [1, 2, 3]
y = x.map { |n| n.next } # => [2, 3, 4] ( x.collect { |n| n.next } も同様)

ブロックの引数に対するメソッドがある場合、メソッド名のシンボルに&をつけてmapの引数に渡すと、ブロックを省略できます。

map_blockfree.rb
x = [1, 2, 3]
y = x.map(&:next) # => [2, 3, 4] ( x.map { |n| n.next } と同様)

Pythonでは内包表記を使うと簡潔に書けます。

map_comprehension.py
x = [1, 2, 3]
y = [n + 1 for n in x] # [2, 3, 4]

Pythonにも高階関数mapが存在しますが、その戻り値はリストではなくイテレーターになります。なので、結果をリストとして利用したい場合は、list関数を用いて変換する必要があります。

map_lambda.py
x = [1, 2, 3]
y = list(map(lambda n: n + 1, x)) # [2, 3, 4]

select

各要素に述語を適用し、真となる要素のみからなる配列を返します。
述語がすべて偽であった場合は、空の配列を返します。

Rubyでは、selectにはfind_allの別名があります。

select.rb
x = [1, 2, 3]
y = x.select { |n| n.odd? } # => [1, 3] ( x.find_all { |n| n.odd? } も同様)

mapのケースと同様に、上の例ではブロックを簡潔に書けます。

select_blockfree.rb
x = [1, 2, 3]
y = x.select(&:odd?) # [1, 3]

Pythonでは、やはり内包表記を使うのが簡単です。

select_comprehension.py
x = [1, 2, 3]
y = [n for n in x if x % 2] # [1, 3]

高階関数filterを用いると以下のようになります。ただし、戻り値はリストではないので、リストとして用いる場合は変換する必要があります。

select_lambda.py
x = [1, 2, 3]
y = list(filter(lambda n: n % 2, x))  # => [1, 3]

reduce

関数の畳み込みです。レシーバーの先頭要素と次要素に関数を適用し、その結果と次の要素に関数を適用することを繰り返します。

Rubyでは、reduceにはinjectの別名があります。

reduce.rb
x = [1, 2, 3]
y = x.reduce(0) { |accum, n| accum + n } # => 6 ( x.inject(0) { |accum, n| accum + n } も同様)

ブロックの第一引数をレシーバー、第二引数を引数にとるメソッドがある場合、メソッド名のシンボルを引数に渡すことでブロックを省略できます。

reduce.blockfree.rb
x = [1, 2, 3]
y = x.reduce(:+) # => 6

Pythonには標準ライブラリのfunctoolsに、高階関数reduce()が存在します。

reduce_lambda.py
import functools
x = [1, 2, 3]
y = functools.reduce(lambda accum, n: accum + n, x, 0) # 6

リストの総和程度であれば、組み込みの関数sumを用いることもできます。

sum.py
x = [1, 2, 3]
y = sum(x) # => 6

all?/any?

各要素に述語を適用し、すべて/いずれか1つ が真であればtrueを、そうでなければfalseを返します。
なお、探索は真偽が確定した時点で打ち切られます。つまり、all?の場合は偽の要素が、any?の場合は真の要素が見つかった時点で結果が返り、以降の要素は評価されません。

any.rb
x = [2, 3, 4]
y = x.any?(&:odd?) # => true

Pythonには組み込み関数allおよびanyがあります。
しかし、Pythonの組み込み関数all、anyは述語を引数に取らないため、元のリストの代わりに真偽値のジェネレーターを渡します。ジェネレーターの代わりに真偽値のリストを作ってしまうと、途中で評価を打ち切れる場合も最後の要素まで評価してしまいます。

any.rb
x = [1, 2, 3]
y = any((n % 2 for n in x)) # true

count

引数なしで呼び出すと、単純にレシーバーの要素数を返します。レシーバーが空のときは、0を返します。

count_noarg.rb
x = [1, 2, 3]
y = x.count # => 3

Pythonでは組み込み関数lenを用います。

len.rb
x = [1, 2, 3]
y = len(x) # 3

引数を与えると、引数と同値な要素の数を返します。

count_witharg.rb
x = [1, 2, 2, 3, 3, 3]
y = x.count(2) # => 2

Pythonにも同様の組み込みメソッドcountがあります。

count_witharg.rb
x = [1, 2, 2, 3, 3, 3]
y = x.count(2) # 2

述語をブロックで与えると、レシーバーの各要素に述語を適用し、真となる要素の数を返します。

count_withblock.rb
x = [1, 2, 2, 3, 3, 3]
y = x.count { |n| n > 2 } # => 3

Pythonの場合は、内包表記によるフィルターとlenを組み合わせます。

count_comprehension.py
x = [1, 2, 2, 3, 3, 3]
y = len([n for n in x if n > 2]) # 3

max/min

レシーバーの最大/最小の要素を返します。

max.rb
x = [1, 2, 3]
y = x.max # => 3

Pythonにも組み込み関数max/minがあります。

max_py
x = [1, 2, 3]
y = max(x) # 3

max_by/min_by

ブロックで渡した評価関数が最大値/最小値をとる要素を返します。

maxby.rb
x = ['foo', 'hello', 'hoge']
y = x.max_by(&:length) # => "hello"

Pythonでは、組み込み関数maxのキーワードkeyに評価関数を指定します。

maxby.rb
x = ['foo', 'hello', 'hoge']
y = max(x, key=len) # 'hello'

sort

レシーバーを昇順にソートした配列を返します。

sort.rb
x = [2, 3, 1]
y = x.sort # => [1, 2, 3]

sort!はレシーバー自身を変更します。

sort_inplace.rb
x = [2, 3, 1]
x.sort! # => [1, 2, 3]
x # => [1, 2, 3]

Pythonにも組み込み関数sortedがあり、引数をソートしたリストを返します。

sorted.py
x = [2, 3, 1]
y = sorted(x) # [1, 2, 3]

リストに対するメソッドsortは、レシーバーを変更します。また、sortはリストに対してのみ定義されていますが、sortedは任意のイテラブルなオブジェクトに対して適用できます。

sort.py
x = [2, 3, 1]
x.sort()
x # => [1, 2, 3]

sort_by

レシーバーの各要素に比較用の関数を適用し、関数の値が小さい順にソートした配列を返します。

sortby.rb
x = ['foo', 'hello', 'hoge']
y = x.sort_by(&:length) # => ["foo", "hoge", "hello"]

sort_by!メソッドはレシーバー自身を変更します。

sortby_inplace.rb
x = ['foo', 'hello', 'hoge']
y = x.sort_by!(&:length) # => ["foo", "hoge", "hello"]
y # => ["foo", "hoge", "hello"]
x # => ["foo", "hoge", "hello"]

Pythonでは、sortedやsortのキーワードkeyに評価用関数を指定します。

sorted_withkey.py
x = ['foo', 'hello', 'hoge']
y = sorted(x, key=len) #  ["foo", "hoge", "hello"]
sort_inplace_withkey.py
x = ['foo', 'hello', 'hoge']
x.sort(key=len) # => None
x # => ["foo", "hoge", "hello"]

reverse

レシーバーを逆順にした配列を返します。

reverse.rb
x = [1, 2, 3]
y = x.reverse # => [3, 2, 1]

reverse!はレシーバー自身を変更します。

reverse_inplace.rb
x = [1, 2, 3]
x.reverse! # => [3, 2, 1]
x # => [3, 2, 1]

Pythonにも組み込み関数reversedがあります。reversedの戻り値はイテレーターなので、リストとして利用する場合はlist関数で変換します。

reversed.py
x = [1, 2, 3]
y = list(reversed(x)) # [3, 2, 1]

リストに対するメソッドreverseは、レシーバー自身を変更します。reversedは任意のイテラブルなオブジェクトに適用できますが、reverseはリストにのみ定義されています。

reverse.py
x = [1, 2, 3]
x.reverse()
x # => [3, 2, 1]

take/drop

先頭から引数で与えた数だけ、取得した/除外した 配列を返します。

take.rb
x = [1, 2, 3, 4]
y = x.take(2) # => [1, 2]
drop.rb
x = [1, 2, 3, 4]
y = x.drop(2) # => [3, 4]

Pythonでは、スライスを用いるのが簡単です。
スライスはインデックスを省略すると、先頭/末尾を補完してくれます。

take.py
x = [1, 2, 3, 4]
y = x[:2] # [1, 2]
drop.py
x = [1, 2, 3, 4]
y = x[2:] # [3, 4]

include?

引数で指定した要素が含まれるか判定します。

include.rb
x = [1, 2, 3]
y = x.include?(2) # => true

Pythonでは、in演算子を用います。

include.py
x = [1, 2, 3]
y = (2 in x) # True

partition

各要素にブロックで与えた述語を適用し、trueの要素とfalseの要素に分けます。

partition.rb
x = [1, 2, 3, 4, 5]
y = x.partition(&:odd?) # => [[1, 3, 5], [2, 4]]

Pythonでは、selectを2回使えばいいです。

partition.py
x = [1, 2, 3, 4, 5]
[[n for n in x if (n % 2)], [n for n in x if not (n % 2)]]

group_by

レシーバーの、ブロックで与えた評価関数による同値類のハッシュを返します。

groupby.rb
x = [1, 2, 3, 4, 5, 6, 7]
y = x.group_by { |n| n % 3 } # => {1 => [1, 4, 7], 2 => [2, 5], 0 => [3, 6]}

これはPythonではループで行うしかないと思います。

groupby.py
x = [1, 2, 3, 4, 5, 6, 7]
y = {}
for n in x:
    p = n % 3
    if p in y:
        y[p].append(n)
    else:
        y[p] = []
y # => {1:[1, 4, 7], 2:[2, 5], 0:[3, 6]}

each_cons

引数で与えた数だけ先頭から取得することを繰り返します。
残りの要素数が引数より少なくなった場合は、それ以上繰り返しません。

eachcons.rb
x = [1, 2, 3, 4, 5]
y = x.each_cons(2) # => [[1, 2], [2, 3], [3, 4], [4, 5]]

Pythonでは、内包表記とスライスを使うのが簡単です。

eachcons.py
x = [1, 2, 3, 4, 5]
y = [x[i:i+2] for i in range(len(x)-1)] # [[1, 2], [2, 3], [3, 4], [4, 5]]
# 1ブロックあたりの要素数をNとして、[x[i:i+N] for i in range(len(x)-(N-1))]

each_slice

引数で与えた数ずつレシーバーを区切ります。
残りの要素数が引数より少なくなった場合も、残りの要素をまとめた配列が付け加えられます。

eachslice.rb
x = [1, 2, 3, 4, 5]
y = x.each_slice(2) # => [[1, 2], [3, 4], [5]]

こちらもPythonでは内包表記とスライスを使うのが簡単です。

eachslice.py
x = [1, 2, 3, 4, 5]
y = [x[i:i+2] for i in range(0, len(x), 2)] # [[1, 2], [3, 4], [5]]
# 1ブロックあたりの要素数をNとして、[x[i:i+N] for i in range(0, len(x), N)]

uniq

レシーバーから重複を除いた配列を返します。

uniq.rb
x = [1, 2, 2, 3, 3, 3]
y = x.uniq # => [1, 2, 3]

Pythonでは、Setに変換するのが簡単です。

uniq.py
x = [1, 2, 2, 3, 3, 3]
y = list(set(x)) # [1, 2, 3]

zip

レシーバー自身と各引数の要素の組からなる配列を返します。

zip.rb
x = [1, 2, 3]
y = x.zip([11, 12, 13]) # => [[1, 11], [2, 12], [3, 13]]

Pythonでは、組み込みのzip()関数で同様のことができます。戻り値の各要素は、各引数の要素のタプルになります。zip()関数の戻り値はイテレーターなので、リストとして用いる場合はlist()関数で変換します。

zip.py
y = list(zip([1, 2, 3], [11, 12, 13])) # => [(1, 11), (2, 12), (3, 13)] 

おわりに

以上です。上に紹介した方法よりも良いやり方があれば是非コメント下さい。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1