qiita.comいや、Python すばらしいですね。どこまで Ruby でできるか、試してみました。なお、使った Ruby のバージョンは 2.7.0 です。
3値以上の比較
1 == 2 == 3
1 < 2 < 3 < 4
いやー、これは Ruby ではできないですね。なお、Ruby でもこれができるバージョンがあったそうですが、まつもとさんがふつうの(?)比較に戻したそうです。
時間(datetime/date)の比較
Python。
from datetime import date
feb1 = date(2020, 2, 1)
feb2 = date(2020, 2, 2)
feb3 = date(2020, 2, 3)
feb1 < feb2 <= feb2 < feb3
Ruby。require "date"
します。
irb(main):001:0> require "date"
=> true
irb(main):002:0> feb1 = Date.new(2020, 2, 1)
irb(main):003:0> feb2 = Date.new(2020, 2, 2)
irb(main):004:0> feb3 = Date.new(2020, 2, 3)
irb(main):005:0> feb1 < feb2 && feb2 < feb3
=> true
だいたい同じですかね。
時間の最大/最小
Pyhton。
from datetime import date
dates = [
date(2020, 2, 3),
date(2020, 2, 2),
date(2020, 2, 1),
]
min(dates)
max(dates)
Ruby。
irb(main):001:0> require "date"
=> true
irb(main):002:0> dates = 3.downto(1).map {|i| Date.new(2020, 2, i)}
irb(main):003:0> dates.min
=>
irb(main):004:0> dates.max
=>
これもだいたい同じですかね。
時間の計算
Python。
from datetime import datetime
start = datetime(2020, 2, 1, 10)
goal = datetime(2020, 2, 3, 12)
t = goal - start
print(f'あなたの記録は{t.days}日と{t.seconds}秒です')
'あなたの記録は2日と7200秒です'
Ruby。
irb(main):001:0> require "date"
=> true
irb(main):002:0> start = DateTime.new(2020, 2, 1, 10)
irb(main):003:0> goal = DateTime.new(2020, 2, 3, 12)
irb(main):004:0> t = goal - start
irb(main):005:0> "あなたの記録は#{t.to_i}日と#{(t % 1 * 86400).to_i}秒です"
=> "あなたの記録は2日と7200秒です"
これはちょっと面倒ですねえ。もっとうまいやり方はないかな。
時間を連想配列のキーにする
Python。
from datetime import date
counts = {
date(2020, 2, 1): 0,
date(2020, 3, 1): 0,
}
counts[date(2020, 2, 1)] += 1
counts[date(2020, 2, 1)] += 1
counts[date(2020, 3, 1)] += 1
print(counts)
{datetime.date(2020, 2, 1): 2, datetime.date(2020, 3, 1): 1}
Ruby。
irb(main):001:0> require "date"
=> true
irb(main):002:1* counts = {
irb(main):003:1* Date.new(2020, 2, 1) => 0,
irb(main):004:1* Date.new(2020, 3, 1) => 0,
irb(main):005:0> }
irb(main):006:0> counts[Date.new(2020, 2, 1)] += 1
irb(main):007:0> counts[Date.new(2020, 2, 1)] += 1
irb(main):008:0> counts[Date.new(2020, 3, 1)] += 1
irb(main):009:0> counts
=> {
ほぼ同じ。
キーが連想配列に含まれるか
Python。
d = {
'foo': 1,
'bar': 2,
'baz': 3,
}
print('foo' in d)
Ruby。
irb(main):001:0> h = {foo: 1, bar: 2, baz: 3}
irb(main):002:0> h.has_key?(:foo)
=> true
わかりやすいですね。
Python。
d = {
'foo': 1,
'bar': 2,
'baz': 3,
}
print(list(d))
Ruby。
irb(main):001:0> h = {foo: 1, bar: 2, baz: 3}
irb(main):002:0> h.keys
=> [:foo, :bar, :baz]
これもわかりやすいですね。
Python。
d = {
'foo': 1,
'bar': 2,
'baz': 3,
}
print(list(d.values()))
Ruby。
irb(main):001:0> h = {foo: 1, bar: 2, baz: 3}
irb(main):002:0> h.values
=> [1, 2, 3]
これもわかりやすいですね。
連想配列からキーと値のペアを抽出
Python。
d = {
'foo': 1,
'bar': 2,
'baz': 3,
}
for key, value in d.items():
print(key, value)
foo 1
bar 2
baz 3
Ruby。
irb(main):001:0> h = {foo: 1, bar: 2, baz: 3}
irb(main):002:0> puts h.map {_1.join(" ")}
foo 1
bar 2
baz 3
map を使うのは Ruby らしいのでは? もちろんふつうに each あるいは each_pair で取り出すこともできます。
Python。
l = [
['Yamada', 'baseball'],
['Tanaka', 'soccer'],
['Sato', 'tennis'],
]
dict(l)
{'Yamada': 'baseball', 'Tanaka': 'soccer', 'Sato': 'tennis'}
Ruby。
irb(main):001:1* ary = [
irb(main):002:1* [:Yamada, :baseball],
irb(main):003:1* [:Tanaka, :soccer],
irb(main):004:1* [:Sato, :tennis],
irb(main):005:0> ]
irb(main):006:0> ary.to_h
=> {:Yamada=>:baseball, :Tanaka=>:soccer, :Sato=>:tennis}
ほぼ同じですね。
多重配列のループ
Python。
rows = [
['yamada', 20],
['tanala', 18],
['sato', 18],
]
for name, age in rows:
print(f'{name}さんは{age}歳です')
else:
print('紹介終わり')
'yamadaさんは20歳です'
'tanalaさんは18歳です'
'satoさんは18歳です'
'紹介終わり'
Ruby。
irb(main):001:1* rows = [
irb(main):002:1* [:yamada, 20],
irb(main):003:1* [:tanala, 18],
irb(main):004:1* [:sato, 18],
irb(main):005:0> ]
irb(main):006:0* puts rows.map {|name, age| "#{name}さんは#{age}歳です"} + ["紹介終わり"]
yamadaさんは20歳です
tanalaさんは18歳です
satoさんは18歳です
紹介終わり
いいんじゃないでしょうか。
多重配列から必要な要素だけ取り出しつつループ
Python。
l = [
['Yamada', 'Taro', 20, 'baseball'],
['Tanaka', 'Jiro', 18, 'circle'],
]
for last, *others in l:
print(last, others)
print()
for *others, circle in l:
print(circle, others)
print()
for last, first, *__ in l:
print(last, first)
Yamada ['Taro', 20, 'baseball']
Tanaka ['Jiro', 18, 'circle']
baseball ['Yamada', 'Taro', 20]
circle ['Tanaka', 'Jiro', 18]
Yamada Taro
Tanaka Jiro
Ruby。
irb(main):001:1* ary = [
irb(main):002:1* [:Yamada, :Taro, 20, :baseball],
irb(main):003:1* [:Tanaka, :Jiro, 18, :circle],
irb(main):004:0> ]
irb(main):005:0>
irb(main):006:0> puts ary.map {|last, *others| "#{last} #{others.inspect}"}
Yamada [:Taro, 20, :baseball]
Tanaka [:Jiro, 18, :circle]
irb(main):007:0>
irb(main):008:0> puts ary.map {|*others, circle| "#{circle} #{others.inspect}"}
baseball [:Yamada, :Taro, 20]
circle [:Tanaka, :Jiro, 18]
irb(main):009:0>
irb(main):010:0> puts ary.map {|last, first, *_| "#{last} #{first}"}
Yamada Taro
Tanaka Jiro
ほとんど同じだなあ。
では Python の
l = [
['a', 'b', 'c', ['d', 'e', 'f']],
]
for one, *__, (*__, two) in l:
print(one, two)
a f
は Ruby では? これはせっかくなので、Ruby 2.7.0 のパターンマッチを使ってみましょう。
irb(main):001:0> ary = [:a, :b, :c, [:d, :e, :f]]
irb(main):002:1* case ary
irb(main):003:1* in [one, _, _, [_, _, two]]
irb(main):004:0> end
irb(main):005:0> "#{one} #{two}"
=> "a f"
おお、いいですね。
カウンター付きループ
Python。
rows = [
['Yamada', 20],
['Tanaka', 18],
['Sato', 16],
]
for i, (name, age) in enumerate(rows, start=1):
print(f'{i}行目 : 氏名={name}, 年齢={age}')
'1行目 : 氏名=Yamada, 年齢=20'
'2行目 : 氏名=Tanaka, 年齢=18'
'3行目 : 氏名=Sato, 年齢=16'
Ruby。
irb(main):001:1* rows = [
irb(main):002:1* [:Yamada, 20],
irb(main):003:1* [:Tanaka, 18],
irb(main):004:1* [:Sato, 16],
irb(main):005:0> ]
irb(main):006:1* puts rows.map.with_index(1) {|(name, age), i|
irb(main):007:1* "#{i}行目:氏名=#{name}, 年齢=#{age}"
irb(main):008:0> }
1行目:氏名=Yamada, 年齢=20
2行目:氏名=Tanaka, 年齢=18
3行目:氏名=Sato, 年齢=16
Ruby らしいですね。
連想配列のキー無しエラーを防ぐ
Python。
d = {
'Yamada': 20,
'Tanaka': 18
}
d.get('Yamada')
d.get('Sato')
d.get('Sato', '年齢なし')
Ruby。これは Python とは挙動がちがいますね。どちらがよいでしょうか。
irb(main):001:0> h = Hash.new("年齢なし")
irb(main):002:0> h.merge!({Yamada: 20, Tanaka: 18})
=> {:Yamada=>20, :Tanaka=>18}
irb(main):003:0> h[:Yamada]
=> 20
irb(main):004:0> h[:Sato]
=> "年齢なし"
配列/連想配列を展開して関数へ渡す
Python。
def func(a, b, c=None, d=None):
print(a)
print(b)
print(c)
print(d)
l = ['aaa', 'bbb']
d = {'c': 'ccc', 'd': 'ddd'}
func(*l, **d)
aaa
bbb
ccc
ddd
Ruby。キーワード引数は Ruby 2.7 で整理されました。
irb(main):001:1* def func(a, b, c: nil, d: nil)
irb(main):002:1* puts a
irb(main):003:1* puts b
irb(main):004:1* puts c
irb(main):005:1* puts d
irb(main):006:0> end
=> :func
irb(main):007:0> ary = [:aaa, :bbb]
irb(main):008:0> h = {c: :ccc, d: :ddd}
irb(main):009:0> func(*ary, **h)
aaa
bbb
ccc
ddd
ほぼ一緒ですね。
all関数
Python。
l = [
True,
1,
"foo",
]
all(l)
l = [
True,
1,
"",
]
all(l)
Ruby。Enumerable#all? メソッドがあります。
irb(main):001:0> ary = [true, 1, "foo"]
irb(main):002:0> ary.all?
=> true
irb(main):003:0> ary = [true, 1, nil]
irb(main):004:0> ary.all?
=> false
ほぼ同じなのだけれど、Ruby では空文字列は true なので、少し変えました。
any関数
all関数の場合とほぼ同じなので、省略します。Ruby では Enumerable#any? を使います。one? や none? メソッドもあります。
集合演算(Set型)
Ruby では require "set"
をすれば Python とほぼ同じなので省略。
collections / itertools パッケージ
collections.Counter
Python。同一要素の数を数えます。
import collections
l = ['a', 'b', 'c', 'a', 'a', 'c']
c = collections.Counter(l)
print(c.most_common())
[('a', 3), ('c', 2), ('b', 1)]
Ruby。Ruby 2.7 から Enumerable#tally が入りました。
irb(main):001:0> ary = [:a, :b, :c, :a, :a, :c]
irb(main):002:0> ary.tally
=> {:a=>3, :b=>1, :c=>2}
collections.defaultdict
Python。
import json
import collections
groups = collections.defaultdict(list)
groups['baseball'].append('yamada')
groups['tennis'].append('tanaka')
groups['baseball'].append('sato')
print(json.dumps(groups))
{"baseball": ["yamada", "sato"], "tennis": ["tanaka"]}
Ruby。Hash の default_proc を使います。
irb(main):001:0> groups = Hash.new {|h, k| h[k] = []}
irb(main):002:0> groups[:baseball] << :yamada
irb(main):003:0> groups[:tennis] << :tanaka
irb(main):004:0> groups[:baseball] << :sato
irb(main):005:0> groups
=> {:baseball=>[:yamada, :sato], :tennis=>[:tanaka]}
また、Python の
import json
from collections import defaultdict
nested = defaultdict(lambda: defaultdict(int))
nested['a']['a'] += 1
nested['a']['a'] += 1
nested['a']['b'] += 1
nested['b']['c'] += 1
print(json.dumps(nested))
{"a": {"a": 2, "b": 1}, "b": {"c": 1}}
に対しては、Ruby なら
irb(main):001:0> nested = Hash.new {|h, k| h[k] = Hash.new(0)}
irb(main):002:0> nested[:a][:a] += 1
irb(main):003:0> nested[:a][:a] += 1
irb(main):004:0> nested[:a][:b] += 1
irb(main):005:0> nested[:b][:c] += 1
irb(main):006:0> nested
=> {:a=>{:a=>2, :b=>1}, :b=>{:c=>1}}
とすればよいです。
itertools.product
Python。
import itertools
a = ['a1', 'a2']
b = ['b1', 'b2', 'b3']
c = ['c1']
list(itertools.product(a, b, c))
[('a1', 'b1', 'c1'),
('a1', 'b2', 'c1'),
('a1', 'b3', 'c1'),
('a2', 'b1', 'c1'),
('a2', 'b2', 'c1'),
('a2', 'b3', 'c1')]
Ruby には Array#product があります。
irb(main):001:0> a = [:a1, :a2]
irb(main):002:0> b = [:b1, :b2, :b3]
irb(main):003:0> c = [:c1]
irb(main):004:0> a.product(b, c)
=> [[:a1, :b1, :c1], [:a1, :b2, :c1], [:a1, :b3, :c1],
[:a2, :b1, :c1], [:a2, :b2, :c1], [:a2, :b3, :c1]]
itertools.chain.from_iterable
Python。
import itertools
l = [
['a1', 'a2'],
['b1', 'b2', 'b3'],
]
list(itertools.chain.from_iterable(l))
['a1', 'a2', 'b1', 'b2', 'b3']
これは Ruby では Array#flatten でおしまいです。
irb(main):001:1* ary = [
irb(main):002:1* [:a1, :a2],
irb(main):003:1* [:b1, :b2, :b3],
irb(main):004:0> ]
irb(main):005:0> ary.flatten
=> [:a1, :a2, :b1, :b2, :b3]
ioパッケージ
Python。
import io
def writer(f, text):
f.write(text)
def printer(f):
print(f.read())
sio = io.StringIO()
writer(sio, 'foo\n')
writer(sio, 'bar\n')
writer(sio, 'baz\n')
sio.seek(0)
printer(sio)
foo
bar
baz
Ruby では StringIO を使います。
irb(main):001:0> require "stringio"
=> true
irb(main):002:1* def writer(f, text)
irb(main):003:1* f.write(text)
irb(main):004:0> end
=> :writer
irb(main):005:1* def printer(f)
irb(main):006:1* print f.read
irb(main):007:0> end
=> :printer
irb(main):008:0> sio = StringIO.new("tmp", "w+")
irb(main):009:0> writer(sio, "foo\n")
irb(main):010:0> writer(sio, "bar\n")
irb(main):011:0> writer(sio, "baz\n")
irb(main):012:0> sio.rewind
irb(main):013:0> printer(sio)
foo
bar
baz
タプル(tuple)
Ruby には immutable なタプルはありません。では、Python の
from collections import defaultdict
data = [
{'circle': 'baseball', 'name': 'yamada', 'age': 10},
{'circle': 'baseball', 'name': 'sato', 'age': 10},
{'circle': 'baseball', 'name': 'suzuki', 'age': 11},
{'circle': 'tennis', 'name': 'tanaka', 'age': 10},
]
per_circle_age = defaultdict(list)
for v in data:
k = (v['circle'], v['age'])
per_circle_age[k].append(v['name'])
for (circle, age), members in per_circle_age.items():
print(f'{circle}に所属している{age}歳のメンバー:{members}')
"baseballに所属している10歳のメンバー:['yamada', 'sato']"
"baseballに所属している11歳のメンバー:['suzuki']"
"tennisに所属している10歳のメンバー:['tanaka']"
これを Ruby でどうするかというと、ふつうに Array と Hash でいけます。
irb(main):001:1* data = [
irb(main):002:1* {circle: :baseball, name: :yamada, age: 10},
irb(main):003:1* {circle: :baseball, name: :sato, age: 10},
irb(main):004:1* {circle: :baseball, name: :suzuki, age: 11},
irb(main):005:1* {circle: :teniss, name: :tanaka, age: 10},
irb(main):006:0> ]
irb(main):007:0> per_circle_age = Hash.new {|h, k| h[k] = []}
irb(main):008:1* data.each do |h|
irb(main):009:1* k = [h[:circle], h[:age]]
irb(main):010:1* per_circle_age[k] << h[:name]
irb(main):011:0> end
irb(main):012:1* puts per_circle_age.map {|(circle, age), members|
irb(main):013:1* "#{circle}に所属している#{age}歳のメンバー:#{members.inspect}"
irb(main):014:0> }
baseballに所属している10歳のメンバー:[:yamada, :sato]
baseballに所属している11歳のメンバー:[:suzuki]
tenissに所属している10歳のメンバー:[:tanaka]
dataclassesパッケージ
Python。
import dataclasses
@dataclasses.dataclass(frozen=True)
class User:
last_name: str
first_name: str
def full_name(self):
return f'{self.last_name} {self.first_name}'
yamada = User(last_name='Yamada', first_name='Taro')
tanaka = User(last_name='Tanaka', first_name='Jiro')
yamada.full_name()
dataclasses.asdict(yamada)
yamada2 = User(last_name='Yamada', first_name='Taro')
yamada == yamada2
yamada == tanaka
yamada in [yamada2]
yamada.last_name = 'Sato'
d = {yamada: 'foo', tanaka: 'bar'}
{yamada, tanaka} & {yamada2}
Ruby なら、Struct クラスを使うとよいのかな。
irb(main):001:1* User = Struct.new(:last_name, :first_name) do
irb(main):002:2* def full_name
irb(main):003:2* "#{last_name} #{first_name}"
irb(main):004:1* end
irb(main):005:0> end
irb(main):006:0> yamada = User.new(:Yamada, :Taro)
irb(main):007:0> tanaka = User.new(:Tanaka, :Jiro)
irb(main):008:0> yamada.full_name
=> "Yamada Taro"
irb(main):009:0> yamada.to_h
=> {:last_name=>:Yamada, :first_name=>:Taro}
irb(main):010:0> yamada2 = User.new(:Yamada, :Taro)
irb(main):011:0> yamada == yamada2
=> true
irb(main):012:0> yamada == tanaka
=> false
irb(main):013:0> h = {yamada => :foo, tanaka => :bar}
irb(main):014:0> [yamada, tanaka] & [yamada2]
=> [
だいたいいけますね。
どうでしょうか。Ruby もなかなかいけるでしょう?