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)

20200722222030
「カーリル」は全国の図書館の蔵書検索サイトです。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」だということがわかりました。下でこれを使います。
 

蔵書検索

各務原市中央図書館に、下の本が架蔵されているか調べます。たまたまいまわたしが借りている本です(笑)。

八九六四 「天安門事件」は再び起きるか

八九六四 「天安門事件」は再び起きるか

systemid と isbnコードを使ってリクエストします。

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"=>""}}}}

自然数の約数の個数(Ruby)

自然数の約数の個数は、素因数分解をして求める。Ruby なら、prime ライブラリを require して、Inetger#prime_divisionメソッドを使う。

require "prime"

n = 360

puts n.prime_division.inject(1) {|acc, (a, b)| acc * (b + 1)}    #=>24

Ruby2D で穴掘り迷路


見てのとおり、穴掘り迷路です。Gem 'ruby2d' を使いました。Ruby2D についてはこちら

コード。
dig_maze.rb

require "ruby2d"

L = 20    #迷路の大きさ
W = L * 2 + 3

Block_w = 10
set width: W * Block_w, height: W * Block_w, fps_cap: 10

blocks = W.times.map {|y|
  W.times.map {|x|
    Square.new x: x * Block_w, y: y * Block_w,
               size: Block_w, color: "green"
  }
}

field = Array.new(W) {Array.new(W, 1)}
field[0] = field[-1] = Array.new(W, -1)
(1..W - 2).each {|y| field[y][0] = field[y][-1] = -1}

field.define_singleton_method(:show) do
  each_index do |y|
    self[y].each_index do |x|
      self[y][x].zero? ? blocks[y][x].remove : blocks[y][x].add
    end
  end
end

start = [2, 2]
stack = [start]
show_stack = [start]

dig = ->(now) {
  movable = []
  [[1, 0], [0, -1], [-1, 0], [0, 1]].each do |dx, dy|
    x = now[0] + dx * 2
    y = now[1] + dy * 2
    movable << [x, y] if field[y][x] == 1
  end
  if movable.empty?
    return if stack.empty?
    jump = stack.delete_at(rand(stack.size))
    dig.(jump)
  else
    nxt = movable.sample
    show_stack << [(now[0] + nxt[0]) / 2, (now[1] + nxt[1]) / 2]
    show_stack << nxt
    stack << nxt
  end
}


update do
  now = show_stack.shift
  next unless now
  field[now[1]][now[0]] = 0
  field.show
  dig.(now) if show_stack.empty?
end

show

Gem 'Ruby 2D' で遊ぶ(2)

過去記事のプログラムを少し改変したものです。
 
コード。
ruby2d_sample2a.rb

require 'ruby2d'
require 'matrix'
include Math

Width = 500
C = 15    #円の数
R = 20    #円の半径

L = 70    #三角形の外接円の半径

set width: Width, height: Width

circles = C.times.map {
  Circle.new x: rand(R..Width - R), y: rand(R..Width - R),
     radius: R, color: Color.new([rand, rand, rand, 0.8]), z: 0
}
cvs = circles.map {[rand(-3.0..3.0), rand(-3.0..3.0)]}    #円の移動ベクトル
circles_move = circles.zip(cvs)


rot = ->(θ) {
   θ1 = θ / 180.0 * PI
   Matrix[[cos(θ1), sin(θ1)], [-sin(θ1), cos(θ1)]]
}

points = ->(θ) {
  o = Vector[Width / 2.0, Width / 2.0]
  v0 = Vector[0, -L]
  p1 = o + rot.(θ +   0) * v0
  p2 = o + rot.(θ + 120) * v0
  p3 = o + rot.(θ + 240) * v0
  [p1, p2, p3]
}

triangle = Triangle.new z: 10, opacity: 0.8

triangle.define_singleton_method(:rotate) do |θ|
  p1, p2, p3 = points.(θ)
  self.x1 = p1[0]
  self.y1 = p1[1]
  self.x2 = p2[0]
  self.y2 = p2[1]
  self.x3 = p3[0]
  self.y3 = p3[1]
end

triangle_color = [rand, rand, rand]
triangle_color_step =
   (Vector[rand(-1.0..1.0), rand(-1.0..1.0), rand(-1.0..1.0)]
     .normalize / 100).to_a

triangle_color.define_singleton_method(:update) do
  triangle.color = self + [0.8]
  replace(zip(triangle_color_step).map {|a, b| a + b})
  triangle_color.each_index do |i|
    if triangle_color[i] < 0 || triangle_color[i] > 1
      triangle_color_step[i] = -triangle_color_step[i]
    end
  end
end


θ = 0

update do
  #円の移動
  circles_move.each do |c, vec|
    c.x += vec[0]
    c.y += vec[1]
    c.x = Width + R if c.x < -R
    c.y = Width + R if c.y < -R
    c.x = -R if c.x > Width + R 
    c.y = -R if c.y > Width + R 
  end
  
  #三角形の回転
  triangle.rotate(θ)
  triangle_color.update if (θ % 3).zero?
  θ = (θ + 1) % 360
end

show

inject が Enumerator を返さない(Ruby)

例えば true/false の配列をビット列に変換したいとして、こうしたかった。

cond = [true, false, false, true, false, true]

cond.inject(0).with_index {|(acc, c), i| c ? acc | 1 << i : acc}
#=>TypeError (0 is not a symbol nor a string)

injectはブロックを取らない場合、Enumerator を返さないのですね。


仕方がないのでenum_forを使う。

cond.enum_for(:inject, 0).with_index {|(acc, c), i| c ? acc | 1 << i : acc}
#=>0b101001

これでinjectも Enumerator を返します。


しかし、enum_forってどうなっているのだろうね。どうも、ブロックを取るメソッドはすべて使えるらしい。変な例。

class String
  def upcase🍓
    yield(upcase)
  end
end

enum = "aaa".enum_for(:upcase🍓)
enum.map {|e| e + "0"}    #=>["AAA0"]

謎である。