0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

オフラインリアルタイムどう書く E31 の実装例(Ruby)

Posted at

問題の名前 : ぐるぐる数
問題 : http://nabetani.sakura.ne.jp/hena/orde31rotnum/
実装リンク集 : https://qiita.com/Nabetani/items/d139d5ef70aa23c2d038

次回のイベントは 4/6 最終回にするかも。
see https://yhpg.doorkeeper.jp/events/88379

で。

やや遅い実装

まずは、やや遅い実装。

ruby
# frozen_string_literal: true

require "json"

def next_vals(b,n)
  [n, (n+1)%b]
end

def build_number(b, head, size, states)
  (size-1).times.with_object([head]) do |ix,o|
    candidates = next_vals(b, o.last).sort
    o.push candidates[:low==states[ix] ? 0 : 1]
  end
end

def undigits(b,d)
  d.reverse_each.inject(0) do |acc,n|
    acc*b+n
  end
end

def build_states(b, digits)
  r=digits.each_cons(2).with_object([]){ |(p,q),o|
    candidates = next_vals( b, p )
    o << %i(low high above)[candidates.count{ |e| e<q }]
    break o unless candidates.include?(q)
  }
  r + [:low]*(digits.size-1-r.size)
end

def guru(b,digits)
  states = build_states(b, digits)
  above = states.index(:above)
  return build_number(b, digits.first, digits.size, states) unless above
  # 繰り上がり処理がある
  n = undigits(b,digits[0,above+1].reverse)
  d = (n+1).digits(b).reverse
  head = guru(b,d)
  head + [next_vals( b, head.last).min] * (digits.size-above-1)
end

def sup(b,x)
  undigits(b, guru(b, x.digits(b).reverse).reverse)
end

def solve_impl( b, x, y )
  x = sup(b,x)
  count = 0
  while x<=y
    count+=1
    x = sup(b,x+1)
  end
  count
end

def solve( src )
  sb, *sxy = src.split(",")
  b = sb.to_i
  xy = sxy.map{ |e| e.to_i(b) }
  if b==2
    (xy[1] - xy[0]+1).to_s
  else
    solve_impl( b, *xy ).to_s
  end
end

if __FILE__==$PROGRAM_NAME
  json = File.open( ARGV[0], &:read)
  data = JSON.parse( json, symbolize_names:true )
  data[:test_data].map{ |number:, src:, expected:|
    actual = solve( src )
    ok = expected==actual
    p [ (ok ? "ok" : "**NG**"), number, src, expected, actual ]
    ok
  }.tap{ |r| puts( r.all? ? "everything is okay" : "something wrong" ) }
end

ruby 1.rb data.json のように実行する。

「次に大きいぐるぐる数」という計算ができればよいというアイディアだけど、MikuriyaHiroshi さんのアイディアの方が平易なコードになっていいかもね。

ただ。
b が 2 のときはすべての数がぐるぐる数なので、この計算をするとえらく時間がかかる。
そこで b が 2 のときは単に引き算するというショートカットをしている。

手元のマシンで3秒弱。

速い実装

つづいてちゃんとした実装。

ruby
# frozen_string_literal: true

require "json"

def state(b, x)
  x.each_cons(2).map{ |prev,cur|
    lambda{ |lo,hi|
      return :below if cur<lo
      return :lo if cur==lo
      return :mid if cur<hi
      return :hi if cur==hi
      return :above
    }[*[ prev, (prev+1) % b ].sort]
  }
end

def count_state( s )
  return 1 if s.empty?
  case s[0]
  when :below then 0
  when :lo then count_state(s.drop(1))
  when :mid then 2**(s.size-1)
  when :hi then count_state(s.drop(1)) + 2**(s.size-1)
  when :above then 2**s.size
  end
end

# x 以下の ぐるぐる数の個数
def guru_count( b, x )
  # x.size 未満の桁数
  s0 = (1...x.size).sum{ |keta| (b-1)*(2**(keta-1)) }
  # x.size 桁で、先頭が 1..(x.first-1)
  s1 = (x.first-1)*(2**(x.size-1))
  # 先頭が x.first
  s2 = count_state( state(b, x) )
  [s0, s1, s2].sum
end

def solve( src )
  sb,sx,sy = src.split(",")
  b = sb.to_i
  x = (sx.to_i(b) - 1).digits(b).reverse
  y = sy.to_i(b).digits(b).reverse
  ycount = guru_count(b,y)
  xcount = guru_count(b,x)
  ( ycount - xcount ).to_s
end

if __FILE__==$PROGRAM_NAME
  json = File.open( ARGV[0], &:read)
  data = JSON.parse( json, symbolize_names:true )
  data[:test_data].map{ |number:, src:, expected:|
    actual = solve( src )
    ok = expected==actual
    p [ (ok ? "ok" : "**NG**"), number, src, expected, actual ]
    ok
  }.tap{ |r| puts( r.all? ? "everything is okay" : "something wrong" ) }
end

まずはこの手の問題の定石。
「0<=x<=y で、x<=i<=y である i について C(i)が真になる i の数は?」みたいな問題は、しばしば「0<=x で、0<=i<x である i について C(i)が真になる i の数」を求める関数を書くと楽になる。

で。
問題はその『「0<=x で、0<=i<x である i について C(i)が真になる i の数」を求める関数』の実装。

例えば。
455734 ( 10進数、6桁 )
よりも小さいぐるぐる数の数を数えたい。そうすると、これは

a) 1桁〜5桁のぐるぐる数 の数
b) 1〜3で始まる6桁のぐるぐる数 の数
c) 4で始まる6桁のぐるぐるで455734より小さいものの数

の三種類の合計になる。a と b は簡単に求まる。
c は

c1) 44 で始まる 6桁のぐるぐる数 の数
c2) 45 で始まる 455734 より小さい 6桁のぐるぐる数 の数

の2つに分けられる。
c1 は簡単に求まる。
c2 は「5で始まる 5桁のぐるぐる数」の数と同じになるので、再帰呼出しで解決できる

というわけ。
これを実行すると ruby自体の起動時間に隠れるぐらいの時間で計算が終わる。

まとめ

会場の方の反応を見ると、やや難しかった模様。
わりときれいな、それでいてオリジナリティのある問題だと思ったんだけど、どうだろう。

次回 4月6日(土曜)は、最終回になるかもしれない(ならないかもしれない)です。
ご興味がお有りなら是非。

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?