qiita.comいや、Python すばらしいですね。どこまで Ruby でできるか、試してみました。なお、使った Ruby のバージョンは 2.7.0 です。
3値以上の比較
1 == 2 == 3 # -> False 1 < 2 < 3 < 4 # -> True
いやー、これは Ruby ではできないですね。なお、Ruby でもこれができるバージョンがあったそうですが、まつもとさんがふつうの(?)比較に戻したそうです。
時間(datetime/date)の比較
from datetime import date feb1 = date(2020, 2, 1) feb2 = date(2020, 2, 2) feb3 = date(2020, 2, 3) feb1 < feb2 <= feb2 < feb3 # -> True
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) # -> datetime.date(2020, 2, 1) max(dates) # -> datetime.date(2020, 2, 3)
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 => #<Date: 2020-02-01 ((2458881j,0s,0n),+0s,2299161j)> irb(main):004:0> dates.max => #<Date: 2020-02-03 ((2458883j,0s,0n),+0s,2299161j)>
これもだいたい同じですかね。
時間の計算
# Input 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}秒です') # Output 'あなたの記録は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秒です"
これはちょっと面倒ですねえ。もっとうまいやり方はないかな。
時間を連想配列のキーにする
# Input 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) # Output {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 => {#<Date: 2020-02-01 ((2458881j,0s,0n),+0s,2299161j)>=>2, #<Date: 2020-03-01 ((2458910j,0s,0n),+0s,2299161j)>=>1}
ほぼ同じ。
キーが連想配列に含まれるか
d = { 'foo': 1, 'bar': 2, 'baz': 3, } print('foo' in d) # -> True
Ruby。
irb(main):001:0> h = {foo: 1, bar: 2, baz: 3} irb(main):002:0> h.has_key?(:foo) => true
わかりやすいですね。
連想配列からキーを抽出
d = { 'foo': 1, 'bar': 2, 'baz': 3, } print(list(d)) # -> ['foo', 'bar', 'baz']
Ruby。
irb(main):001:0> h = {foo: 1, bar: 2, baz: 3} irb(main):002:0> h.keys => [:foo, :bar, :baz]
これもわかりやすいですね。
連想配列から値を抽出
d = { 'foo': 1, 'bar': 2, 'baz': 3, } print(list(d.values())) # -> [1, 2, 3]
Ruby。
irb(main):001:0> h = {foo: 1, bar: 2, baz: 3} irb(main):002:0> h.values => [1, 2, 3]
これもわかりやすいですね。
連想配列からキーと値のペアを抽出
d = { 'foo': 1, 'bar': 2, 'baz': 3, } for key, value in d.items(): print(key, value) # Output 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 で取り出すこともできます。
2重配列を連想配列へ変換
# Input l = [ ['Yamada', 'baseball'], ['Tanaka', 'soccer'], ['Sato', 'tennis'], ] dict(l) # Output {'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}
ほぼ同じですね。
多重配列のループ
# Input rows = [ ['yamada', 20], ['tanala', 18], ['sato', 18], ] for name, age in rows: print(f'{name}さんは{age}歳です') else: print('紹介終わり') # Output '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歳です 紹介終わり
いいんじゃないでしょうか。
多重配列から必要な要素だけ取り出しつつループ
# Input 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() # 最初の2要素を取り出す # (他の要素が要らない場合はダブルアンダースコアを指定するのがPython流) for last, first, *__ in l: print(last, first) # Output 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> #最初の2要素を取り出す irb(main):010:0> puts ary.map {|last, first, *_| "#{last} #{first}"} Yamada Taro Tanaka Jiro
ほとんど同じだなあ。
では Python の
# Input l = [ ['a', 'b', 'c', ['d', 'e', 'f']], ] for one, *__, (*__, two) in l: print(one, two) # Output 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"
おお、いいですね。
カウンター付きループ
# Input rows = [ ['Yamada', 20], ['Tanaka', 18], ['Sato', 16], ] for i, (name, age) in enumerate(rows, start=1): print(f'{i}行目 : 氏名={name}, 年齢={age}') # Output '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 らしいですね。
連想配列のキー無しエラーを防ぐ
d = { 'Yamada': 20, 'Tanaka': 18 } d.get('Yamada') # -> 20 d.get('Sato') # -> None 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] => "年齢なし"
配列/連想配列を展開して関数へ渡す
# Input 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) # Output 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関数
l = [ True, 1, "foo", ] all(l) # -> True l = [ True, 1, "", ] all(l) # -> False
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? メソッドもあります。
collections / itertools パッケージ
collections.Counter
Python。同一要素の数を数えます。
# Input import collections l = ['a', 'b', 'c', 'a', 'a', 'c'] c = collections.Counter(l) print(c.most_common()) # Output [('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
# Input import json import collections # defaultdict()の引数には関数(callable)ならなんでも渡せる groups = collections.defaultdict(list) # 普通の連想配列だと "baseballというキーは存在しない" といったエラーが発生する groups['baseball'].append('yamada') groups['tennis'].append('tanaka') groups['baseball'].append('sato') print(json.dumps(groups)) # Output {"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 の
# Input 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)) # Output {"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
# Input import itertools a = ['a1', 'a2'] b = ['b1', 'b2', 'b3'] c = ['c1'] list(itertools.product(a, b, c)) # Output [('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
# Input import itertools l = [ ['a1', 'a2'], ['b1', 'b2', 'b3'], ] list(itertools.chain.from_iterable(l)) # Output ['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パッケージ
# Input 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) # Output 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 の
# Input 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}') # Output "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パッケージ
import dataclasses # frozen=True とすると不変(immutable)なオブジェクトとして扱える @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() # -> Yamada Taro # 簡単に連想配列へ変換出来ます dataclasses.asdict(yamada) # -> {'last_name': 'Yamada', 'first_name': 'Taro'} # 比較が可能です yamada2 = User(last_name='Yamada', first_name='Taro') yamada == yamada2 # -> True yamada == tanaka # -> False yamada in [yamada2] # -> True # "frozen=True" とした場合は値の再代入は出来ません yamada.last_name = 'Sato' # -> FrozenInstanceError: cannot assign to field 'last_name' # immutableなので連想配列のキーとして使えます d = {yamada: 'foo', tanaka: 'bar'} # 集合演算(Set型)も可能です {yamada, tanaka} & {yamada2} # -> {User(last_name='Yamada', first_name='Taro')}
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] => [#<struct User last_name=:Yamada, first_name=:Taro>]
だいたいいけますね。
どうでしょうか。Ruby もなかなかいけるでしょう?