最大値をもつものを集める(Ruby)
例えば都道府県名をローマ字化したものから、文字数の最大値と、その文字数をもつすべての県名を得たいとする。そのとき、こんなメソッドを作ってみるといいかも知れない。
module ExEnumerable refine Enumerable do def max_select pool = [] max_num = -Float::INFINITY each do |i| n = yield(i) if n > max_ num max_ num = n pool = [i] elsif n == max_ num pool << i end end [max_ num, pool] end end [Array, Enumerator, Hash].each {|mdl| mdl.include Enumerable} end
Enumerable#max_select
は、ブロックの返り値の最大値(max_num
)を求めて、そのような最大値になるようなものをレシーバーから集め(pool
)、[max_num, pool]
を返す。
これを使って、最初の課題を解いてみる。
require "open-uri" using ExEnumerable url = "https://gist.githubusercontent.com/koseki/38926/raw/671d5279db1e5cb2c137465e22424c6ba27f4524/todouhuken.txt" prefectures = URI.open(url).each_line.map {|l| l.chomp.split.last} prefectures.max_select(&:size) #=>[9, ["fukushima", "yamanashi", "hiroshima", "yamaguchi", "tokushima", "kagoshima"]]
9文字が最大値だとわかる。県名のリストも返ってくる。
Enumerator::Lazy でエラトステネスの篩(Ruby)
Enumerator::Lazy は無限数列を扱うことができるが、それを使って「エラトステネスの篩」をちょっとおもしろく実装できることに気づいた。何はともあれコードである。
lazy_prime.rb
prime_seq = Enumerator.new do |y| sieve = 2.step.lazy loop do a = sieve.first y << a sieve = sieve.reject {|x| (x % a).zero?} end end prime_seq.take(10) #=>[2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
Enumerator::Lazy はsieve = 2.step.lazy
のところで生成している。これの先頭a
は必ず素数なので、素数列として Enumerator で取り出す(y << a
)。そして、sieve
からa
の倍数をreject
ですべて篩い落とし(Lazy だから可能なのである)、そうされたものを新しいsieve
とする。以下、繰り返し、というわけである。
これは子供の頃に紙と鉛筆でやってみた「エラトステネスの篩」のやり方そのものである。小さい方から素数に丸でも打っておいて、その倍数を斜線で消していく…。実装としては、素直といえば素直だ。
しかし、これ、めちゃめちゃに遅いのはすぐわかる。素数ひとつ得るたびに、Enumerator::Lazy オブジェクトを作り直しているのだから、どれほど遅いかというと、例えば最初の 500個の素数を得るのに、この方法だとわたしの環境でなんと 2.8秒ほどもかかる。標準添付ライブラリの prime を使うと 0.0002秒ほどだから、比較にもならない。
でもまあ、ちょっとおもしろかったので書いてみた。
なお、単なる Enumerator ももちろん無限数列を扱うことができるが、map
やselect
、それにここで使ったreject
などの結果が有限数列(Array)になってしまう。なので、Enumerator::Lazy を使ったのである。為念。
MP3ファイルの分割(ffmpeg, Ruby)
ffmpeg と Ruby を使って、MP3ファイルを分割します。
こんな感じ。
cut_mp3.rb
file = DATA.gets.chomp ts = DATA.gets.split.map {|t| t.split(":").map(&:to_i)} Dir.chdir(File.dirname(file)) bname = File.basename(file, ".mp3") ts.map {_1 * 3600 + _2 * 60 + _3}.each_cons(2).with_index(1) do |(s, e), i| `ffmpeg -i "#{file}" -ss #{s} -t #{e - s} "#{bname + ("_%02d" % i)}.mp3"` end __END__ /home/***/Music/TAPEMP3/Bach.mp3 00:00:00 00:18:20 00:36:57 00:56:20 01:14:23 01:38:45 01:58:20
これだと、パス /home/***/Music/TAPEMP3/Bach.mp3
を6ファイルに分割します。最初のファイルは 00:00:00~00:18:20 まで、2番目のファイルは 00:18:20~00:36:57 までで、最後のファイルは 01:38:45~01:58:20 までとなります。
ruby 3.0.0preview1 をちょっと使ってみる
環境は Linux Mint 20 です。
Ractor
まずは Ractor。
https://github.com/ruby/ruby/blob/master/doc/ractor.md
https://github.com/ko1/ruby/blob/ractor/ractor.ja.md
下を見ていじくるとよい。
qiita.com
とりあえずこれ。
Ractor.new do 5.times do puts :hello end end 5.times do puts :world end
結果。こうなってしまう。
world world world world world
これは Ractor の生成にコストがかかり、「hello」が出力される前にインタプリタが終了してしまうため。
なので、こうしてみる。
Ractor.new do 5.times do puts :hello sleep(rand(0.1)) end end 5.times do puts :world sleep(rand(0.1)) end
結果。
world hello hello hello world hello world hello world world
うまくいきました。
Thread との比較
Thread。
N = 10 start = Time.now th = N.times.map { Thread.new do a = 0 1_000_000.times {a += 1} Time.now end }.map(&:value) puts th.map {_1 - start}.max #=>0.576966553
Ractor。
N = 10 start = Time.now rs = [] rs = N.times.map { Ractor.new do a = 0 1_000_000.times {a += 1} Time.now end }.map(&:take) puts rs.map {_1 - start}.max #=>0.365015356
4コア環境で、確かに高速化されている。
終わった順にすべての Ractor を待つ
例えばこんなメソッドを作る。
def Ractor.wait_for(*ractors) ractors.size.times.map do r, obj = Ractor.select(*ractors) ractors.delete(r) obj end end
で、こんな感じ。
N = 10 start = Time.now rs = N.times.map do Ractor.new do a = 0 1_000_000.times {a += 1} Time.now end end result = Ractor.wait_for(*rs) puts result.map {_1 - start}
結果。
0.191312097 0.231078089 0.284552986 0.292159854 0.323803118 0.339945751 0.349074802 0.35051055 0.352556214 0.363963214
カーリルAPIで遊んでみる(Ruby)
「カーリル」は全国の図書館の蔵書検索サイトです。APIが用意されているので、Ruby でちょっと遊んでみました。
まずはサイトから「アプリケーションキー」を取得して下さい。以下の appkey
には、実際に取得したキーが入ります。
図書館の検索
わたしの住んでいる岐阜県各務原市の図書館情報を取得してみます。
コード。
require "net/http" appkey = "MyApplicationKey" uri = URI.parse("https://api.calil.jp/library") q = {appkey: appkey, pref: "岐阜県", city: "各務原市"} uri.query = URI.encode_www_form(q) response = Net::HTTP.get_response(uri) puts response.code puts response.body
Net::HTTP.get_response
でレスポンスを取得します。結果。
200 <?xml version="1.0" encoding="utf-8"?> <Libraries> <Library> <systemid>Gifu_Kakamigahara</systemid> <systemname>岐阜県各務原市</systemname> <libkey>もりの本やさん</libkey> <libid>100889</libid> <short>もりの本やさん</short> <formal>各務原市もりの本やさん</formal> <url_pc>http://ufinity08.jp.fujitsu.com/kakamigahara/</url_pc> <address>岐阜県各務原市鵜沼字石山6529-2</address> <pref>岐阜県</pref> <city>各務原市</city> <post>509-0111</post> <tel>058-370-7175</tel> <geocode>136.950857,35.410975</geocode> <category>SMALL</category> <image/> </Library> <Library> <systemid>Gifu_Kakamigahara</systemid> <systemname>岐阜県各務原市</systemname> <libkey>中央ライフ</libkey> <libid>100890</libid> <short>中央ライフデザインセンター</short> <formal>各務原市中央ライフデザインセンター図書室</formal> <url_pc>http://ufinity08.jp.fujitsu.com/kakamigahara/</url_pc> <address>岐阜県各務原市蘇原中央町2-1-8 中央ライフデザインセンター2階</address> <pref>岐阜県</pref> <city>各務原市</city> <post>504-0813</post> <tel>058-389-1820</tel> <geocode>136.8791775,35.410405</geocode> <category>SMALL</category> <image/> </Library> <Library> <systemid>Gifu_Kakamigahara</systemid> <systemname>岐阜県各務原市</systemname> <libkey>中央図書館</libkey> <libid>100892</libid> <short>中央図書館</short> <formal>各務原市立中央図書館</formal> <url_pc>http://ufinity08.jp.fujitsu.com/kakamigahara/</url_pc> <address>岐阜県各務原市那加門前町3丁目1-3</address> <pref>岐阜県</pref> <city>各務原市</city> <post>504-0911</post> <tel>058-383-1122</tel> <geocode>136.842595,35.400056</geocode> <category>MEDIUM</category> <image/> </Library> <Library> <systemid>Gifu_Kakamigahara</systemid> <systemname>岐阜県各務原市</systemname> <libkey>川島ほんの家</libkey> <libid>100891</libid> <short>川島ほんの家</short> <formal>各務原市川島ほんの家</formal> <url_pc>http://ufinity08.jp.fujitsu.com/kakamigahara/</url_pc> <address>岐阜県各務原市川島松倉町1951-4 川島会館3階</address> <pref>岐阜県</pref> <city>各務原市</city> <post>501-6022</post> <tel>0586-89-5610</tel> <geocode>136.8254624,35.3679736</geocode> <category>SMALL</category> <image/> </Library> <Library> <systemid>Gifu_Kakamigahara</systemid> <systemname>岐阜県各務原市</systemname> <libkey>移動図書館</libkey> <libid>100888</libid> <short>さつき号</short> <formal>各務原市移動図書館「さつき号」</formal> <url_pc>http://ufinity08.jp.fujitsu.com/kakamigahara/</url_pc> <address>岐阜県各務原市那加門前町3丁目1-3</address> <pref>岐阜県</pref> <city>各務原市</city> <post>504-0911</post> <tel>058-383-1122</tel> <geocode>136.8425677,35.4000383</geocode> <category>BM</category> <image/> </Library> <Library> <systemid>Univ_Chubu_Gu</systemid> <systemname>中部学院大学</systemname> <libkey>各務原</libkey> <libid>104938</libid> <short>各務原図書館</short> <formal>中部学院大学附属図書館各務原キャンパス図書館</formal> <url_pc>http://web3.chubu-gu.ac.jp/library/</url_pc> <address>岐阜県各務原市那加甥田町30-1</address> <pref>岐阜県</pref> <city>各務原市</city> <post>504-0837</post> <tel>058-375-3607</tel> <geocode>136.845140,35.404969</geocode> <category>UNIV</category> <image/> </Library> <Library> <systemid>Univ_Tokaigakuin</systemid> <systemname>東海学院大学</systemname> <libkey>本館</libkey> <libid>104710</libid> <short>附属図書館</short> <formal>東海学院大学・東海学院大学短期大学部附属図書館</formal> <url_pc>http://www.tokaigakuin-u.ac.jp/library/</url_pc> <address>岐阜県各務原市那加桐野町5丁目68</address> <pref>岐阜県</pref> <city>各務原市</city> <post>504-8511</post> <tel>058-389-2969</tel> <geocode>136.818267,35.424550</geocode> <category>UNIV</category> <image/> </Library> </Libraries>
ステータスコードは 200 なので、GET は成功しています。レスポンスの中身はデフォルトで xml の形式になっています。
わたしは中央図書館の情報が知りたいので、systemid は「Gifu_Kakamigahara」だということがわかりました。下でこれを使います。
蔵書検索
各務原市中央図書館に、下の本が架蔵されているか調べます。たまたまいまわたしが借りている本です(笑)。
- 作者:安田 峰俊
- 発売日: 2018/05/18
- メディア: 単行本
require "net/http" require "json" appkey = "MyApplicationKey" uri = URI.parse("https://api.calil.jp/check") q = {appkey: appkey, isbn: "9784041067352", systemid: "Gifu_Kakamigahara", callback: :no} uri.query = URI.encode_www_form(q) response = Net::HTTP.get_response(uri) hash = JSON.parse(response.body) puts response.code pp hash
結果。
200 {"session"=>"c5584394d201695694d0e0ef13df6119", "continue"=>0, "books"=> {"9784041067352"=> {"Gifu_Kakamigahara"=> {"status"=>"Cache", "libkey"=>{"中央図書館"=>"貸出中"}, "reserveurl"=> "https://ilisod001.apsel.jp/kakamigahara-library/wopc/pc/OpacServlet?disp=searchResultDetail&id=2728033"}}}}
"continue"
の結果が 0 なので、問い合わせが上手くいったようです。中央図書館に蔵書されていて、確かに貸出中ですね(笑)。
結果が次のような場合になることもあります。
{"session"=>"2c476142999d400a13b2ce1020501f58", "continue"=>1, "books"=> {"9784041067352"=> {"Gifu_Kakamigahara"=>{"status"=>"Running", "reserveurl"=>""}}}}
"continue"
の結果が 1 なので、継続した問い合わせが必要です。
継続した問い合わせを考慮に入れたコード。また、問い合わせる本も図書館も複数(岐阜県図書館も探す)にしてみました。
calil.rb
require "net/http" require "json" appkey = "MyApplicationKey" uri = URI.parse("https://api.calil.jp/check") q = {appkey: appkey, callback: :no} q.merge!({isbn: "9784041067352,9784393142714"}) q.merge!({systemid: "Gifu_Kakamigahara,Gifu_Pref"}) uri.query = URI.encode_www_form(q) response = hash = nil loop do response = Net::HTTP.get_response(uri) hash = JSON.parse(response.body) break if hash["continue"].zero? puts "continue" sleep(3) q = {appkey: appkey, session: hash["session"], callback: :no} uri.query = URI.encode_www_form(q) end puts response.code pp hash
結果。
continue 200 {"session"=>"7d3d755be59c6f7c6364198d9408773f", "continue"=>0, "books"=> {"9784041067352"=> {"Gifu_Pref"=> {"status"=>"Cache", "libkey"=>{"図書館"=>"貸出可"}, "reserveurl"=> "https://www.library.pref.gifu.lg.jp/winj/opac/switch-detail-iccap.do?bibid=1190315471"}, "Gifu_Kakamigahara"=> {"status"=>"OK", "libkey"=>{"中央図書館"=>"貸出中"}, "reserveurl"=> "https://ilisod001.apsel.jp/kakamigahara-library/wopc/pc/OpacServlet?disp=searchResultDetail&id=2728033"}}, "9784393142714"=> {"Gifu_Pref"=> {"status"=>"Cache", "libkey"=>{"図書館"=>"貸出中"}, "reserveurl"=> "https://www.library.pref.gifu.lg.jp/winj/opac/switch-detail-iccap.do?bibid=1100029995"}, "Gifu_Kakamigahara"=> {"status"=>"Cache", "libkey"=>{}, "reserveurl"=>""}}}}