オフラインリアルタイムどう書くE07 の問題の ruby による実装例


問題：http://nabetani.sakura.ne.jp/hena/orde07_7seg/
実装リンク集：http://qiita.com/Nabetani/items/6e7a6fadbfaa4ae20e89

で。

この問題は、

* 情報が不十分な 7seg から、その 7seg で表示可能な数字(あるいは空白)のリストを得る
* 得られた情報から、最大値と最小値を得る

という二段階になっている。

前半はビット演算で簡単に求まる。

集合で書くと、

* ついているとわかっているランプ ⊂ 本当についているランプ
* ついているとわかっているランプ ∩ 本当に消えているランプ = ∅

なので、ビット演算で書くと

* ついているとわかっているランプ & 本当についているランプ == ついているとわかっているランプ
* ついているとわかっているランプ & 本当に消えているランプ == 0

となる。

後半はいろいろな戦略がある。
私が最初に選んだ方法は

* 全部試して合致するものから最大最小を得る

という馬鹿げた実装。こんな感じ。

```ruby
require "benchmark"

PATS=[
  [0x0, :blank],
  [0x3f, 0],  [0x06, 1],  [0x5b, 2],  [0x4f, 3],  [0x66, 4],
  [0x6d, 5],  [0x7d, 6],  [0x27, 7],  [0x7f, 8],  [0x6f, 9]
]

def okay( x, ketas )
  s=("%*d" % [ketas.size, x ])
  s.size.times.all?{ |ix|
    c=s[ix]
    k=ketas[ix]
    if c==" "
      k.include?(:blank)
    else
      k.include?(c.to_i)
    end
  }
end

def solve(src)
  ons, offs = src.split(",").map{ |x| x.split(":").map{|seg| seg.to_i(16) } }
  ketas=ons.size.times.map do |ix|
    PATS.each.select{ |bits,digit|
      ( ons[ix] & bits ) == ons[ix] && (offs[ix] & bits )==0
    }.map(&:last)
  end

  mm=(10**ketas.size).times.select{ |x|
    okay(x,ketas)
  }.minmax
  mm.compact.empty? ? "-" : mm.join(",")
end

$stdout.sync=true

DATA.map do |line|
  /(?<num>\d+).*"(?<src>.+)".*"(?<expected>.+)"/=~line
  actual = nil
  tick = Benchmark.realtime{ actual = solve( src ) }
  okay = actual==expected
  puts( "%s %s %s->%s ( e:%s ) %f" % [ ( okay ? "ok" : "**NG**" ), num, src, actual, expected, tick ] )
  okay
end.all?.tap{|x| puts( x ? "everything is ok" : "something wrong" ) }

__END__
/*0*/ test( "06:4b:46:64:6d,79:20:10:10:02", "12345,13996" );    
/*1*/ test( "41:00,3e:01", "-" );    
/*2*/ test( "00:00,79:79", "1,11" );    
/*3*/ test( "02:4b:46:64,20:20:10:10", "1234,3399" );    
```

3桁なら、0から999まで全部試す。
とても遅いけど、正しいと確信が持てるという意味では安心感がある。

とはいえ、これでは全部テストを通すのに数分を要するので困る。困るので、もっと速いのを書いた。

前半は先程のと同じ。
後半は、

* 最初の桁が空白かどうかで分岐して、再帰呼び出しで求める。

というもの。こんな感じ：

```ruby
PATS=[
  [0, :blank],
  [0x3f, 0],  [0x06, 1],  [0x5b, 2],  [0x4f, 3],  [0x66, 4],
  [0x6d, 5],  [0x7d, 6],  [0x27, 7],  [0x7f, 8],  [0x6f, 9]
]

def find_minmax(ketas, ord, head=true)
  h, *r = ketas
  return ( h - [:blank] ).send(ord) if ketas.size==1
  if head && h.include?( :blank )
    return [
        find_minmax( r, ord, true ),
        find_minmax( [h-[:blank]]+r, ord, true )
      ].compact.send(ord)
  end
  h-=[:blank]
  h-=[0] if head
  return nil if h.size==0
  m=find_minmax( r, ord, false )
  return m ? h.send(ord)*10**(ketas.size-1) + m : nil
end

def solve(src)
  ons, offs = src.split(",").map{ |x| x.split(":").map{|seg| seg.to_i(16) } }
  ketas=ons.zip( offs ).map{ |on,off|
    PATS.select{ |pat|
      (pat[0] & on ) == on && ( pat[0] & off )== 0
    }.map( &:last )
  }
  return "-" if ketas.any?{ |x| x==[] }
  max = find_minmax( ketas, :max )
  return "-" unless max
  [  find_minmax( ketas, :min ), max ].join(",")
end

DATA.map do |line|
  /(?<num>\d+).*"(?<src>.+)".*"(?<expected>.+)"/=~line
  actual = solve( src )
  okay = actual==expected
  puts( "%s %s %s->%s ( e:%s )" % [ ( okay ? "ok" : "**NG**" ), num, src, actual, expected ] )
  okay
end.all?.tap{|x| puts( x ? "everything is ok" : "something wrong" ) }

__END__
/*44*/ test( "18:04:26:20:04:24:1a,02:21:50:48:02:08:00", "6177540,6177678" );    
/*45*/ test( "00:08:34:00:00:64:06,18:24:02:00:61:08:61", "260141,7269141" );    
/*46*/ test( "00:02:0a:04:4a:00:20,18:21:24:02:04:60:19", "125214,7126214" );
```

最大値を求める計算と最小値を求める計算を同じメソッドにするために、send(:min) のようなことをしている。
head は先頭の桁かどうか。先頭だと 0 が使えないとか、空白が選べるとか、いろいろ違いがあるからね。

問題としては、やや難しかった模様。