<file_sep>#### 1. Array#transpose 는 배열을 입체적으로 다루는 데 도움이 된다.
- [spiral array 문제](http://codingdojang.com/scode/266)  
다차원 배열을 회전시킬 수 있다.
```ruby
cube = ->x,y { (0...y).map {|e| [*0...x].product [e] } }
peel = ->cub { cub[0]? cub.shift + peel[cub.transpose.reverse] : [] }
sprl = ->x,y { spr=peel[cube[x,y]]; cube[x,y].map {|_|_.map {|e|"%3d" % spr.index(e)}*''} }

expect(sprl[6,6]).to eq ["  0  1  2  3  4  5",
                         " 19 20 21 22 23  6",
                         " 18 31 32 33 24  7",
                         " 17 30 35 34 25  8",
                         " 16 29 28 27 26  9",
                         " 15 14 13 12 11 10"]
```
- [상자 넓이 구하기 문제](http://codingdojang.com/scode/506)  
다차원 배열의 행과 열을 하나의 배열로 구조화 할 수 있다.
```ruby
gap = ->a { a.flat_map {|e| e.each_cons(2).map {|a,b|(b-a).abs} } }
area = ->a { gap[(a+a.transpose).map{|_|[0,*_,0]}].sum + (a.size**2)*2 }

expect( area[ [[1,4,3,4],
               [2,3,4,1],
               [3,4,2,1],
               [9,3,2,1]]] ).to eq 120
```
- [Poker Hands 문제](http://codingdojang.com/scode/423)  
숫자배열과 무늬배열로 쉽게 구조화 시킬 수 있다.
```ruby
to_n = "23456789TJQKA".chars.zip(2..14).to_h
hand = ->cards do
  ranks, suits = cards.map(&:chars).transpose
  pairs, nums = ranks.map(&to_n).group_by {|_|_}.
                      map {|k,g| [g.size,k] }.sort.reverse.transpose
  kind = %w(11111 2111 221 311 32 41).zip([0,1,2,3,6,7]).to_h[pairs.join]
  strt = 4 if nums.each_cons(2).all? {|i,j| i == j.next }
  flsh = 5 if suits.uniq.one?; stfl = 8 if strt && flsh
  [[kind, strt, flsh, stfl].compact.max, nums]
end

play_poker = ->file,games=File.readlines(file).map(&:split) do
  prt_result = ->idx { puts %w(Tie. Black\ wins. White\ wins.)[idx] }
  games.map {|cards| hand[cards[0,5]] <=> hand[cards[5,5]] }.each &prt_result
end

test_data = ["2H 3D 5S 9C KD 2C 3H 4S 8C AH\n",
             "2H 4S 4C 2D 4H 2S 8S AS QS 3S\n",
             "2H 3D 5S 9C KD 2C 3H 4S 8C KH\n",
             "2H 3D 5S 9C KD 2D 3H 5C 9S KH\n"]
allow(File).to receive(:readlines).and_return(test_data)

results = "White wins.\nBlack wins.\nBlack wins.\nTie.\n"
expect{ play_poker["game_data.txt"] }.to output(results).to_stdout
```
<file_sep>describe "Coding Dojang - http://codingdojang.com" do
  it "#51 - Steps" do
    step = ->a,b,n=b-a { s=(n**0.5).to_i; 2*s+(n**0.5==s ? -1 : n>s*s+s ? 1:0) }
    steps = ->n=gets.to_i { puts (1..n).map {gets.chop.split.map &:to_i}.map &step }

    expect(step[45,48]).to eq 3
    expect(step[45,49]).to eq 3
    expect(step[45,50]).to eq 4
    expect(step[0,2**31]).to eq 92681

    #=> stdin/stdout test
    $stdin = StringIO.new("3\n45 48\n45 49\n45 50\n")
    expect { steps[] }.to output("3\n3\n4\n").to_stdout
  end

  it "#52 - Sliding Window. ver.simple" do
    def s_window(n, k, str)
      a = str.split(' ').map(&:to_i)
      x = (0..n-k).map {|i| a[i,k].minmax }
      [x.map(&:first).join(' '), x.map(&:last).join(' ')]
    end

    expect(s_window(8, 3, "1 3 -1 -3 5 3 6 7").first).to eq "-1 -3 -3 -3 3 3"
    expect(s_window(8, 3, "1 3 -1 -3 5 3 6 7").last).to eq "3 3 5 5 6 7"
  end

  it "#52 - Sliding Window" do
    # sort, find, index(obj),delete(obj)느림. sort_by, bsearch_index로 대체
    # 마찬가지로 first, last보다 [0][-1]이 빠르며, closure보다 &:가 빠름.
    def s_window(n, k, str)
      arr,fr = str.split(' ').map.with_index{|v,i|[i,v.to_i]}, []
      minmax = (0..n-k).map do |i|
        if i == 0
          fr = arr[i,k].sort_by(&:last)
        else
          fr.insert((fr.bsearch_index{|_|_[1]>=arr[i+k-1][1]} || k), arr[i+k-1])
          fr.delete_at((fr.bsearch_index{|_|_[0]>arr[i-1][0]} || k)-1)
        end
        [fr[0][1], fr[-1][1]]
      end
      [minmax.map(&:first).join(' '), minmax.map(&:last).join(' ')]
    end

    expect( s_window(8, 3, "1 3 -1 -3 5 3 6 7")[0]).to eq "-1 -3 -3 -3 3 3"
    expect( s_window(8, 3, "1 3 -1 -3 5 3 6 7")[1]).to eq "3 3 5 5 6 7"

    big_arr = (0..10**3).map {|i|rand(9)}.join(' ')
    # check_runtime = Benchmark.realtime { s_window(10**6, 30000, big_arr) }
    check_runtime = Benchmark.realtime { s_window(10**3, 300, big_arr) }
    expect( check_runtime ).to be_between(0.0, 15000)
  end

  it "#52 - Sliding Window. O(n)" do
    l_min = ->s { s.reduce([]) {|a,e| a << [a[-1]||e, e].min } }
    r_min = ->s { s.reverse_each.reduce([]) {|a,e| a.unshift([a[0]||e, e].min) } }
    l_max = ->s { s.reduce([]) {|a,e| a << [a[-1]||e, e].max } }
    r_max = ->s { s.reverse_each.reduce([]) {|a,e| a.unshift([a[0]||e, e].max) } }

    s_window = lambda do
      n, k, *arr = 2.times.flat_map { gets.split.map(&:to_i) } # for stdin case
      sl, sr, bl, br, mins, maxs = [[],[],[],[],[],[]]
      arr.each_slice(k) {|s| sl+=l_min[s]; sr+=r_min[s]; bl+=l_max[s]; br+=r_max[s] }
      (0..n-k).each {|i| mins << [sr[i],sl[i+k-1]].min; maxs << [br[i],bl[i+k-1]].max }
      puts mins*' ', maxs*' '
    end

    $stdin = StringIO.new("8 3\n1 3 -1 -3 5 3 6 7\n")
    expect{ s_window[] }.to output("-1 -3 -3 -3 3 3\n" + "3 3 5 5 6 7\n").to_stdout

    # performance
    # arr = (0...1_000_000).map {|i| rand(1_000_000) }
    # Benchmark.bm(25) do |x|
    #   x.report("case 3. n=10**6, k=5*10**5") { s_window[10*6, 5*10*5, arr] }
    # end

    # 축약버전
    # 중복을 없애기 위한 .send(fn)동적 호출, concat대신 += (복사발생)을 사용해서 축약코드는 느림.
    l = ->s,fn { s.reduce([]) {|a,e| a << [a[-1]||e, e].send(fn) } }
    r = ->s,fn { s.reverse_each.reduce([]) {|a,e| a.unshift([a[0]||e, e].send(fn)) } }
    s_window = ->n,k,arr do
      sl, sr, bl, br, mins, maxs = [[],[],[],[],[],[]]
      arr.each_slice(k) {|s| sl+=l[s,:min]; sr+=r[s,:min]; bl+=l[s,:max]; br+=r[s,:max] }
      (0..n-k).each {|i| mins << [sr[i],sl[i+k-1]].min; maxs << [br[i],bl[i+k-1]].max }
      puts mins*' ', maxs*' '
    end
  end

  it "#53 - Intervals" do
    pairs = proc { (1..gets.to_i).map { gets.split(/ /).map(&:to_i) }.sort }
    ni_pw = proc { pairs[].chunk_while{|(_,a),(b,_)|a>=b}.map {|e| e.flatten.minmax*' '} }

    allow(pairs).to receive(:[]) { [[1,4], [5,6], [6,9], [8,10], [10,10]] }
    expect( ni_pw.() ).to eq ["1 4", "5 10"]
    $stdin = StringIO.new("5\n1 4\n5 6\n6 9\n8 10\n10 10\n") # test data
    expect(ni_pw[]).to eq ["1 4", "5 10"]
  end

  it "#54 - 487-3279" do
    matches = "ABCDEFGHIJKLMNOPRSTUVWXY".gsub(/(...)/).zip("23456789".chars)
    p_num = ->str { str.delete("-").insert(3,"-").tap {|e| matches.each{|f,n| e.tr!(f,n)} } }
    dups = ->texts { texts.map(&p_num).group_by{|e|e}.reject {|_,v| v.size<2 } }
    check = ->texts { sum=dups[texts]; sum.size>0? sum.map {|k,v| [k,v.size]*' ' } : "No dups" }

    dups_set = %w[4873279 ITS-EASY 888-4567 3-10-10-10 888-GLOP TUT-GLOP
                  967-11-11 310-GINO F101010 888-1200 -4-8-7-3-2-7-9- 487-3279]
    no_dups_set = %w[123-4567 487-3279]
    expect(check[dups_set]).to eq ["487-3279 4", "888-4567 3", "310-1010 2"]
    expect(check[no_dups_set]).to eq "No dups"
  end

  it "#55 - The Knights Of The Round Table" do
    r = ->a,b,c,s=(a+b+c)/2 { ((s-a)*(s-b)*(s-c)/s)**0.5 }
    r_t = proc {puts "The radius of the round table is: %.3f" % r[*gets.split.map(&:to_i)] }

    expect(r[12,12,8]).to eq 2.8284271247461903
    #=> for stdin/out test
    $stdin = StringIO.new("12 12 8\n")
    expect { r_t[] }.to output("The radius of the round table is: 2.828\n").to_stdout
  end

  it "#56 - Insertion Sort" do
    swap = ->a,s { a[s],a[s-1]=a[s-1],a[s]; a }
    trav = ->a,s { s==0? a : (a[s]<a[s-1]? trav[swap[a,s],s-1] : a) }
    sort = ->a,s=1 { a.size>s ? sort[trav[a,s],s+1] : a }

    expect(sort[[5,2,4,6,1,3]]).to eq [1,2,3,4,5,6]
    # depdent function
    expect(swap[[5,2,4,6,1,3],1]).to eq [2,5,4,6,1,3]
    expect(trav[[5,2,4,6,1,3],1]).to eq [2,5,4,6,1,3]
    expect(trav[[1,2,4,5,6,3],5]).to eq [1,2,3,4,5,6]
  end

  it "#57 - Sort Large File" do
    require 'bitset'

    sort_by_bit = ->rf,wf,size,bs=Bitset.new(size) do
      IO.foreach(rf) {|l| bs[l.to_i] = true }
      File.open(wf,"w") {|f| (0...size).each {|i| f.puts(i) if bs[i] } }
    end
    #=> usage : sort_by_bit["unordered.txt", "sorted.txt", size]

    unordered_nums = [*0...100].shuffle # shuffle randomly
    File.open("r.txt","w") {|f| unordered_nums.each {|n| f.puts(n) } }
    sort_by_bit["r.txt", "w.txt",100]
    generated_file_nums = File.read("w.txt").split.map(&:to_i)

    # shuffled arr.sort == generated file's arr
    expect( unordered_nums.sort ).to eq generated_file_nums
=begin
    crystal
    require "bit_array"

    def sort_by_bit(rf, wf, size)
      ba = BitArray.new(size)
      File.each_line(rf) {|l| ba[l.to_i] = true }
      File.open(wf, "w") {|f| 0.upto(size-1) {|i| f.puts(i) if ba[i]} }
    end

    sort_by_bit("unordered.txt", "ordered.txt", 5)

    unordered_nums = (0...100).to_a.shuffle # shuffle randomly
    File.open("r.txt","w") {|f| unordered_nums.each {|n| f.puts(n) } }
    sort_by_bit("r.txt", "w.txt", 100)
    generated_file_nums = File.read("w.txt").split.map(&.to_i)

    # shuffled arr.sort == generated file's arr
    unordered_nums.sort.should eq generated_file_nums
=end
  end

  it "#58 - One Edit Apart" do
    case_diff = ->a,b { b.chars.combination(b.size-1).include? a.chars }
    case_same = ->a,b { (0...a.size).count {|i| a[i]!=b[i] } < 2 }
    one_apart = ->a,b { a.size==b.size ? case_same[a,b] : case_diff[*[a,b].minmax_by(&:size)] }

    expect( one_apart["cat","dog"] ).to eq false
    expect( one_apart["cat","cats"] ).to eq true
    expect( one_apart["cat","cut"] ).to eq true
    expect( one_apart["cat","cast"] ).to eq true
    expect( one_apart["cat","at"] ).to eq true
    expect( one_apart["cat","acts"] ).to eq false
  end

  it "#59 - Largest Subset" do
    add = ->seq,n { h,t = seq[n-1]||n,seq[n+1]||n; seq[h],seq[t],size = t,h,t-h;
                    (seq[:max],seq[:size]=(h..t),size) if size>seq[:size]; seq }
    largest = ->nums { nums.reduce({max:0..0, size:0}) {|seq,n| add[seq,n] if !seq[n]}[:max].to_a }

    expect( largest.([1,3,4,5]) ).to eq [*3..5]
    expect( largest.([1,5,4,3,6]) ).to eq [*3..6]
    expect( largest.([1,6,10,4,7,9,5]) ).to eq [*4..7]
    expect( largest.([1,6,10,4,7,9,8,5,11,3,12]) ).to eq [*3..12]
  end

  it "#60 - Josephus Problem" do
    safe_pos = ->n,k,ring=[*1..n] do
      ring.rotate!(k-1).shift until ring.size==1; ring[0]
    end
    #n,k=gets.chomp.split.map &:to_i;
    expect( safe_pos.(10, 3) ).to eq 4
    expect( safe_pos.(100, 3) ).to eq 91
  end
end
<file_sep>#### 7. 간단한 알고리즘들.
- [Eight Queens](http://codingdojang.com/scode/392)  
재귀 구현으로 잘 알려진 문제.
```ruby
require 'benchmark'

valid = ->square,x,y do
  square.reject {|cx,cy| cx==x || cy==y || cx+cy==x+y || cx-cy==x-y }
end

# 모두가 배타적인 위치에 놓여있을 때 숫자 1로 환산.
cnt = ->n,squares,row=1 do
  squares.select {|x,y| x == row}.
    reduce(0) do |sum,square|
      row < n ? sum + cnt[n, valid[squares, *square], row+1] : 1
    end
end
queens = ->size,m=[*1..size] { cnt[size, board=m.product(m)] }

expect(queens[2]).to eq 0
expect(queens[4]).to eq 2
expect(queens[6]).to eq 4
expect(queens[7]).to eq 40
expect(queens[8]).to eq 92
expect(queens[10]).to eq 724
expect(Benchmark.realtime { queens[8] }).to be_between(0.001, 0.01)
```

- [이진트리 레이아웃](http://codingdojang.com/scode/534)  
이진 트리 생성과 in-order 순회.
```ruby
add = ->par,node do
  par ? node.y = par.y + 1 : (return node)
  chd = node.val < par.val ? par.l : par.r
  chd ? add[chd, node] : (node.val < par.val ? par.l = node : par.r = node)
  par
end

layout = ->btree_str=gets.chop,cnt=0 do
  Node = Struct.new(:val, :x, :y, :l, :r)
  nodes = btree_str.chars.map {|c| Node.new(c, 0, 1)}
  btree = nodes.reduce(&add)

  trav = ->node do
    trav[node.l] if node.l
    node.x = cnt+=1
    puts node.values[0..2].join(" ")
    trav[node.r] if node.r
  end
  trav[btree]
end

in_ordered = "a 1 4\n" + "c 2 3\n"  + "e 3 6\n"  + "g 4 5\n" +
             "h 5 4\n" + "k 6 2\n"  + "m 7 3\n"  + "n 8 1\n" +
             "p 9 3\n" + "q 10 5\n" + "s 11 4\n" + "u 12 2\n"
expect{ layout["nkcmahgeupsq"] }.to output(in_ordered).to_stdout
```

- [소수의 개수 구해보기](http://codingdojang.com/scode/503)  
[Sieve of Eratosthenes](https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes).
```ruby
primes =->n,s=[1]*n do
  (2..n).select {|e| (e*2..n).step(e){|i|s[i-1]=0} if s[e-1] > 0 }.size
end
expect( primes[1000] ).to eq 168
expect( primes[10000000] ).to eq 664579
```

- [그림판](http://codingdojang.com/scode/483)  
[BFS: Breadth-first search](https://en.wikipedia.org/wiki/Breadth-first_search).
```ruby
require 'matrix'

paint_image = proc do
  xy, start = (1..2).map { gets.split.map(&:to_i) }
  image = Matrix[*(1..xy[0]).map { gets.chars[0, xy[1]] }]
  q, find_color, paint = [start[0,2]], image[*start[0,2]].to_s, start[2].to_s

  cell = ->x,y { x>=0 && y>=0 && image[x,y] == find_color }
  next_of = ->x,y { [[1,0],[-1,0],[0,-1],[0,1]].map {|r,c| [x+r,y+c] }.select &cell }
  nexts = ->x,y { image.send(:[]=, x, y, paint); next_of[x,y] }
  q += nexts[*q.shift] until !q[0]

  puts image.to_a.map(&:join)
end

$stdin = StringIO.new("10 10\n" +
                  "5 5 3\n" +
                  "0000000000\n" +
                  "0000001000\n" +
                  "0000110100\n" +
                  "0011000010\n" +
                  "0100000010\n" +
                  "0100000010\n" +
                  "0100000100\n" +
                  "0010001000\n" +
                  "0001011000\n" +
                  "0000100000\n")
expect { paint_image[] }.to output("0000000000\n" +
                                   "0000001000\n" +
                                   "0000113100\n" +
                                   "0011333310\n" +
                                   "0133333310\n" +
                                   "0133333310\n" +
                                   "0133333100\n" +
                                   "0013331000\n" +
                                   "0001311000\n" +
                                   "0000100000\n").to_stdout
end
```

- [Euclid Problem](http://codingdojang.com/scode/437)  
[Extended Euclidean](https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm), [Bézout's identity](https://en.wikipedia.org/wiki/B%C3%A9zout%27s_identity), [Modular inverse](https://rosettacode.org/wiki/Modular_inverse).
```ruby
# a,b가 주어졌을 때 ax + by = d(최대공약수)를 만족하는 x,y,d를 구하기
find_xy_gcd = ->a,b do
  x,s,y,t = 1,0,0,1
  (a,q,b = b,*a.divmod(b); x,s,y,t = s,x-q*s,t,y-q*t) until b.zero?
  [x,y,a]
end  

expect(find_xy_gcd[4,6]).to eq [-1,1,2]
expect(find_xy_gcd[17,17]).to eq [0,1,17]
expect(find_xy_gcd[1071,1029]).to eq [-24,25,21]
expect(find_xy_gcd[78696,19332]).to eq [212,-863,36]
```
<file_sep>#### 4. Enumerator#lazy 는 대용량, 무한대를 다루는데 도움이 된다.
- [피보나치 수열 구하기](http://codingdojang.com/scode/461)  
무한 수열에서 조건을 만족하는 수 모두를 구한다.
```ruby
fib = ->x { x < 2 ? x : fib[x-2] + fib[x-1] }
fib_seq = ->n { (0..1/0.0).lazy.map(&fib).take_while {|f| f < n}.force }

expect( fib_seq[10] ).to eq [0, 1, 1, 2, 3, 5, 8]
expect( fib_seq[100000].size ).to eq 26
expect( fib_seq[100000].last ).to eq 75025
```

- [N'th Palindrome](http://codingdojang.com/scode/401)  
무한 수열에서 조건을 만족하는 n번째 수를 획득한다.
```ruby
pal = ->nth { (0..1/0.0).lazy.
                select {|n| n == n.to_s.reverse.to_i}.take(nth).force.last }

expect( [1, 4, 30, 100].map(&pal) ).to eq [0, 3, 202, 909]
```

- [아마존 면접문제](http://codingdojang.com/scode/418)  
대용량 파일 전체를 메모리에 올리지 않고 1라인씩 읽어가며 처리한다. 가볍고 빠르다.
```ruby
require 'rspec'
require 'rspec/mocks/standalone'

extend RSpec::Matchers # https://github.com/pry/pry/issues/1277

def cnt_officers_at(time, file)
  exist = ->chk_in,chk_out { (time >= chk_in && time <= chk_out) ? 1 : 0 }
  File.open(file,'r').each_line.lazy
    .map {|person| exist[*person.strip.split] }.force.reduce :+
end

# mock file
data = "09:12:23 11:14:35\n10:34:01 13:23:40\n10:34:31 11:20:10\n"
log_file = allow(File).to receive(:open).and_return(data)

expect( cnt_officers_at("09:13:20", log_file) ).to eq 1
expect( cnt_officers_at("11:05:20", log_file) ).to eq 3
expect( cnt_officers_at("09:13:20", log_file) ).to eq 1
expect( cnt_officers_at("11:05:20", log_file) ).to eq 3
```
<file_sep>describe "Coding Dojang - http://codingdojang.com" do
  it "#11 - Primary Arithmetic" do
    # 두 수를 더했을 때 올림수 세기.
    # 팁 : split(' ') split으로도 가능. 관례 : 보통 공백으로 구분
    justify = ->nums { nums.map {|s| s.rjust(nums.map(&:size).max) } }
    carry = ->nums { nums.map {|n| n.chars.map(&:to_i) }.transpose.reverse.
                     reduce([0]) {|c,(a,b)| c[-1]+a+b > 9? c<<1 : c }.count(1) }
    stdin = proc { (gets('0 0').split(/\n/)-["","0 0"]).map(&:split) }
    prt = ->n { puts "%s carry operation%s." % [n>0?n:"No", n>1?"s":""] }
    prt_c = proc {|strs| (strs||stdin[]).map(&justify).map(&carry).each(&prt) }

    expect( justify.(["123","45"]) ).to eq ["123"," 45"]
    expect( justify.(["12","345"]) ).to eq [" 12","345"]
    expect( carry.([" 99","123"]) ).to eq 2
    expect( carry.(["555","555"]) ).to eq 3

    stdin_sample = [%w(123 456), %w(555 555), %w(123 594)]
    result = "No carry operation.\n3 carry operations.\n1 carry operation.\n"
    expect { prt_c.(stdin_sample) }.to output(result).to_stdout
  end

  it "#12 - Convert a number to money format" do
    int = ->num { num.reverse.scan(/.{1,3}/).join(',').reverse }
    money_format = ->num { n,q = num.to_s.split('.'); int[n] + (q ? "."+q:"") }

    expect( money_format.(1000) ).to eq "1,000"
    expect( money_format.(20000000) ).to eq "20,000,000"
    expect( money_format.(-3245.24) ).to eq "-3,245.24"
  end

  it "#13 - Counting Code Lines" do
    comment = /\s*\/(\*.+?\*\/|\/[^\n]*$)/m
    cnt = ->code { code.gsub(comment, "").split("\n").map(&:empty?).count(false) }

    code1 =  "// This file contains 3 lines of code\n" +
             "public interface Dave {\n" +
             "    /**\n" +
             "     * count the number of lines in a file\n" +
             "     */\n" +
             "    int countLines(File inFile); // not the real signature!\n" +
             "}\n"

    code2 =  "/*****\n" +
             " * This is a test program with 5 lines of code\n" +
             " *  \/* no nesting allowed!\n" +
             "\n" +
             "//*****//***/// Slightly pathological comment ending...\n" +
             "public class Hello {\n" +
             "    public static final void main(String [] args) { // gotta love Java\n" +
             "        // Say hello\n" +
             "        System./*wait*/out./*for*/println/*it*/(\"Hello/*\");\n" +
             "    }\n" +
             "}\n"

    expect( cnt[code1] ).to eq 3
    expect( cnt[code2] ).to eq 5
  end

  it "#14 - Rotation 3 ways" do
    # rotate(+n), rotate(0), rotate(-n)
    rotate = ->s,list=s.split { list.rotate(-list.shift.to_i)*' ' }

    cases = { stay: "0 똘기 떵이 호치 새초미",
              foward: ["1 10 20 30 40 50", "4 가 나 다 라 마 바 사"],
              reverse: "-2 A B C D E F G" }
    expect( rotate.(cases[:stay]) ).to eq "똘기 떵이 호치 새초미"
    expect( rotate.(cases[:foward][0]) ).to eq "50 10 20 30 40"
    expect( rotate.(cases[:foward][1]) ).to eq "라 마 바 사 가 나 다"
    expect( rotate.(cases[:reverse]) ).to eq "C D E F G A B"
  end

  it "#15 - Nth Palindrome" do
    # simple way
    # simple = ->nth { (0..1.0./0.0).lazy.
    #                  select{|n|n==n.to_s.reverse.to_i}.take(nth).force.last }
    # expect([1,4,30,100].map(&simple)).to eq [0,3,202,909]

    # fast way : 자릿수별 거울수 갯수를 산술적으로 계산
    idx = ->n { (2..n).reduce([10]) {|a,d| a[-1]<n ? [d, n-a[-1]-1, a[-1]+9*10**(d/2-(d%2^1))] : (break a)} }
    gen = proc {|d,pos| h = 10**(d/2-(d%2^1))+pos; [h, (h/10**(d%2)).to_s.reverse] }
    pal = ->n { n>10 ? gen[*idx[n]].join.to_i : n-1 }

    nths, pals = [1,4,30,100,30000,1000000], [0,3,202,909,200000002,90000000009]
    expect( nths.map(&pal) ).to eq pals
    expect( Benchmark.realtime { pal[10**1000]} ).to be_between(0.001, 0.1)
  end

  it "#16 - 미로통과 검사" do
    require 'matrix'
    maze_checker = ->maze_str=gets("\n\n") do
      maze = Matrix[*maze_str.split("\n").map(&:chars)]
      start, dest = %w(< >).map {|ch| maze.index(ch) }; q = [start]

      road = ->x,y { x>=0 && y>=0 && " ><".chars.include?(maze[x,y]) }
      next_of = ->x,y { [[1,0],[-1,0],[0,-1],[0,1]].map {|a,b| [x+a,y+b] }.select &road }
      nexts = ->x,y { maze.send(:[]=, x, y, "#"); next_of[x,y] }

      (x,y = q.shift; maze[x,y]==">"? (return true) : q += nexts[x,y]) until !q[0]
      false
    end

    maze1 = "<     >\n"
    maze2 = "########\n#<     #\n#  ##  #\n#  ##  #\n#     >#\n########\n"
    maze3 = "#######\n#<    #\n##### #\n#     #\n# #####\n" +
            "# #   #\n# # # #\n#   #>#\n#######\n"
    maze4 = "<   #   >\n\n"
    maze5 = "########\n#<     #\n#     ##\n#    #>#\n########\n"
    maze6 = "#< #  #\n#  #  #\n#  # >#\n"

    expect( [maze1, maze2, maze3].map &maze_checker ).to eq [true, true, true]
    expect( [maze4, maze5, maze6].map &maze_checker ).to eq [false, false, false]
  end

  it "#17 - Quine" do
    quine = proc {
      quine = "\nputs \"quine = \" + quine.inspect + quine"
      puts "quine = " + quine.inspect + quine
    }

    expected = %q(quine = "\nputs \"quine = \" + quine.inspect + quine"
puts "quine = " + quine.inspect + quine)+"\n"
    expect { quine.() }.to output(expected).to_stdout

    # 1. eval way
    #eval quine="print 'eval quine=';p quine"

    # 2. inspect way
    #quine = "\nputs \"quine = \" + quine.inspect + quine"
    #puts "quine = " + quine.inspect + quine

    # 3. cheat ways
    #$><<IO.read($0)
    #puts IO.read($0)
    #puts open($0).gets
    #$stdout << IO.read($0)
  end

  it "#18 - Find Files" do
    ls = ->ext,str { Dir.glob("**/*.#{ext}").
                        select {|f| File.read(f) =~ /#{str}/ } }
    expect( ls.("txt", "LIFE IS TOO SHORT") ).to eq []
    expect( ls.("rb", "rspec") ).to include("spec/spec_helper.rb")
  end

  it "#19 - Convert tab to spaces" do
    convert = ->code { code.gsub("\t", "    ") }
    cleanse = ->src { File.open(src,'r+') {|f| f.write(conv[File.read(src)]) } }
    #=> cleanse["source file"]
    expect( convert.("\t") ).to eq "    "
  end

  it "#20 - Paging" do
    # m : 총 건수, n : 한 페이지에 보여줄 게시물 수, 출력 : 총 페이지 수
    pages = ->m,n { ( m.to_f / n ).ceil }
    data = [[0,1],[1,1],[2,1],[1,10],[10,10],[11,10]]
    expect( data.map(&pages) ).to eq [0,1,2,1,1,2]
  end
end
<file_sep>#### 9. irb와 rspec
irb 또는 pry에서 rspec을 사용해서 아이디어를 즉시 테스트해 볼 수 있다.  
- irb에서 RSpec 사용하기
```ruby
require 'rspec'
include RSpec::Matchers

expect(1).to eq 1
```

- STDIN 테스트
```ruby
test_stdin = "12345\n"
$stdin = StringIO.new(test_stdin)

def str_to_i
  gets.to_i
end  
expect(str_to_i).to eq 12345
```

- STDOUT 테스트
```ruby
def echo(str)
  puts str
end

expect { echo("abcde") }.to output("abcde\n").to_stdout
```

- Mock a file
```ruby
gem install rspec-mocks # install
```
```ruby
require 'rspec/mocks/standalone'

extend RSpec::Matchers

def read(file_name) # lazy way
  File.read(file_name,'r')
end

log_file = allow(File).to receive(:read).and_return("file contents\n")
expect( read(log_file) ).to eq "file contents\n"
```

- Benchmark
```ruby
require 'benchmark'

expect( Benchmark.realtime { puts "do_something" } ).to be_between(0.0, 0.1)
```

- RSpec-Benchmark
```ruby
gem install 'rspec-benchmark' # install
```
```ruby
require 'rspec-benchmark'

include RSpec::Benchmark::Matchers

expect { print "do " }.to perform_under(10).ms    # 실행평균 10ms 이하
# 1초에 최소 99번 실행할 수 있어야 한다. ips = iteration per sec
expect { sleep 0.01; print "do " }.to perform_at_least(99).ips
```
<file_sep>class RDate
  attr_reader :year, :month, :day
  attr_reader :year_to_days, :month_to_days

  def initialize(date)
    ymd = ->str { [[0,4],[4,2],[6,2]].map {|s,e| str[s,e].to_i } }
    @year, @month, @day = ymd[date]
    raise "서기 0년은 존재하지 않습니다" if @year == 0
    @month_to_days = sum_days_with_month
    @year_to_days = sum_days_with_year
  end

  def leaf_year?
    @year%4==0 && @year%100!=0 || @year%400==0
  end

  def sum_date
    @year_to_days + @month_to_days + @day
  end

  def self.subdate(from, to)
    from_date, to_date = RDate.new(from), RDate.new(to)
    (to_date.sum_date - from_date.sum_date).abs
  end

  private
    def sum_days_with_month
      day_per_month = [0,31,28 + (leaf_year?? 1:0),31,30,31,30,31,31,30,31,30,31]
      days = day_per_month[0,@month].reduce(0, :+)
    end

    def sum_days_with_year
      year = @year - 1
      days = year == 0 ? 0 : 365*year
      leaf_days = year/4 - year/100 + year/400
      days + leaf_days
    end
end
<file_sep>describe "Coding Dojang - http://codingdojang.com" do
  it "#101 - Word ladder" do
    start, dest = %w(hit cog); dict = %w(hot dot dog lot log)
    nexts = ->word,dict { dict.select {|w| (word.chars-w.chars).size==1 } }
    laddr = ->dict,word,path { path[-1] == dest ? {path*' ' => path.size} :
                               nexts[word,dict].map {|e| laddr[dict-[e],e,path+[e]] } }
    w_laddr = ->s,d,dict { v = laddr[dict<<d,s,[s]].flatten.reduce(&:merge)
                           min = v.values.min; [min, v.select{|_,e|e==min}.keys] }
    expect( w_laddr[start, dest, dict] ).to eq [5, ["hit hot dot dog cog",
                                                    "hit hot lot log cog"]]
  end

  it "#102 - Largest number" do
    m_perm = ->nums { nums.permutation.to_a.map(&:join).max.to_i }
    m_sort = ->nums { nums.sort{|x,y|[y,x]*''<=>[x,y]*''}.join.to_i }

    nums = [3,30,34,5,9]
    expect(m_perm[nums]).to eq 9534330 # permutation way
    expect(m_sort[nums]).to eq 9534330 # sorting way
  end

  it "#104 - Count primes" do
    # loop way
    # primes =->n{ (2..n).select{|e|(2..Math.sqrt(e)).none?{|f|(e%f)==0}}.size }
    # Sieve of Eratosthenes
    primes =->n,s=[1]*n do
      (2..n).select {|e| (e*2..n).step(e){|i|s[i-1]=0} if s[e-1]>0}.size
    end
    expect( primes[1000] ).to eq 168
    expect( primes[10000000] ).to eq 664579

    # crystal & ruby
    def count_primes(max)
      # Sieve of Eratosthenes
      sieve = Array.new(max + 1, true)
      sieve[0] = sieve[1] = false
      2.step(max**0.5) {|i| (i*i).step(max,i) {|j| sieve[j]=false } if sieve[i] }
      sieve.count {|e|e}
    end
  end

  it "#105 - Count number character" do
    cnter = ->n { ([*1..n]*'').chars.reduce(Hash.new(0)) {|a,c| a[c]+=1; a} }
    # cnter = ->n { ('1'..n.to_s).each_with_object(Hash.new(0)) {|s,h| s.each_char{|c| h[c]+=1 } } }
    expect(cnter[5]).to eq({"1"=>1, "2"=>1, "3"=>1, "4"=>1, "5"=>1})
    expect(cnter[1000]).to eq( {"1"=>301, "2"=>300, "3"=>300, "4"=>300, "5"=>300,
                                "6"=>300, "7"=>300, "8"=>300, "9"=>300, "0"=>192} )
  end

  it "#106 - Sum of numbers" do
    msum = ->l,h do
      (l..h).reduce(0) {|sum,n| n.to_s.chars.map(&:to_i).reduce(:*)+sum }
    end
    expect( msum.(1, 10) ).to eq 45
    expect( msum.(10, 15) ).to eq 15
    expect( msum.(10, 1000) ).to eq 93150
  end

  it "#107 - Boxes area" do
    gap = ->a { a.flat_map {|e| e.each_cons(2).map {|c|c.reduce(:-).abs} } }
    area = ->a,n=a.size { gap[(a+a.transpose).map{|_|[0,*_,0]}].reduce(:+) + n*n*2 }
    expect( area[ [[1,2],
                   [2,4]] ]).to eq 32
    expect( area[ [[1,4,3,4],
                   [2,3,4,1],
                   [3,4,2,1],
                   [9,3,2,1]]] ).to eq 120
  end

  it "#108 - Montecarlo method" do
    m_pi = ->n do
      (1..n).reduce {|_,e|_+(rand(0..1.0)**2+rand(0..1.0)**2>1? 0:1) }*4.0/n
    end
    expect( m_pi[10000] ).to be_within(0.1).of(3.1415)
    expect( m_pi[20000] ).to be_within(0.1).of(3.1415)
    expect( m_pi[30000] ).to be_within(0.1).of(3.1415)
  end

  it "#109 - Scale group" do
    # { 대표수 => 비교 가능한 수 }로 표현.
    # { 1=>[1, 2, 3, 4], 6=>[6, 5, 4] }. 2는 1234와 비교가능, 4는 1234, 654와 비교가능
    def make_group
      scales = proc { (1..gets.to_i).map { gets.split.map(&:to_i) } }
      connect = ->set,pair do
        left, right = pair
        pushed = set.select {|key,vals| set[key] << right if vals.index(left) }
        set[left] = set[right]? [left]+set.delete(right) : pair if pushed.size.zero?
        set
      end
      [gets.to_i, scales[].reduce({}, &connect)]
    end

    def unknowns
      known = ->i,group { group.reduce([]) {|a,(_,mem)| mem.index(i)? a|mem : a } }
      n, group = make_group
      (1..n).map {|i| n - known[i, group].size }
    end

    case_all_chained = ["5", "4", "3 4", "2 3", "1 2", "4 5"].join("\n")
    case_part_merged = ["5", "4", "2 3", "1 3", "5 4", "2 4"].join("\n")
    case_part_chained = ["6", "5", "1 2", "2 3", "3 4", "5 4", "6 5"].join("\n")

    $stdin = StringIO.new(case_all_chained) #=> for stdin test
    expect( unknowns() ).to eq [0, 0, 0, 0, 0] #=>{1=>[1,2,3,4,5]}

    $stdin = StringIO.new(case_part_merged)
    expect( unknowns() ).to eq [3, 2, 1, 1, 3] #=> {2=>[2,3,4] 1=>[1,3], 5=>[5,4]}

    $stdin = StringIO.new(case_part_chained)
    expect( unknowns() ).to eq [2, 2, 2, 0, 3, 3] #=> {1=>[1,2,3,4], 6=>[6,5,4]}
=begin
    # shorthands
    adjs = proc { (1..gets.to_i).map { gets.split.map(&:to_i) } }
    connect = ->set,pair {
      l,r = pair
      add_grp = set.select {|tag,g| set[tag] << r if g.member?(l) }
      (set[l] = (set[r]? [l]+set.delete(r) : pair)) if add_grp.empty?; set }
    tagging = proc { [gets.to_i, adjs[].reduce({}, &connect)] }
    knowns = ->i,set { set.reduce([]) {|a,(_,g)| g.member?(i)? a|g : a } }
    unknowns = proc { n,set = tagging[]; (1..n).map {|i| n - knowns[i,set].size } }

    case_all_chained = ["5", "4", "3 4", "2 3", "1 2", "4 5"].join("\n")
    case_part_merged = ["5", "4", "2 3", "1 3", "5 4", "2 4"].join("\n")
    case_part_chained = ["6", "5", "1 2", "2 3", "3 4", "5 4", "6 5"].join("\n")

    $stdin = StringIO.new(case_all_chained) #=> for stdin test
    expect( unknowns.() ).to eq [0, 0, 0, 0, 0] #=>{1=>[1,2,3,4,5]}
    $stdin = StringIO.new(case_part_merged)
    expect( unknowns.() ).to eq [3, 2, 1, 1, 3] #=> {2=>[2,3,4] 1=>[1,3], 5=>[5,4]}
    $stdin = StringIO.new(case_part_chained)
    expect( unknowns.() ).to eq [2, 2, 2, 0, 3, 3] #=> {1=>[1,2,3,4], 6=>[6,5,4]}
=end
  end

  it "#110 - winning probability" do
    odds = ->a,b { Rational((b**0.5).to_i-(a**0.5).to_i, b-a) }
    expect(odds[1, 4]).to eq "1/3".to_r
    expect(odds[1,16]).to eq "1/5".to_r
    expect(odds[1,2**60]).to eq "1/1073741825".to_r
  end
end
<file_sep>#### 3. 다양한 형태의 Stack. (구문평가식, reduce, 문자열)
- [LISP계산기 문제. 풀이1](http://codingdojang.com/scode/533)  
구문평가식은 스택을 사용한다. 이를 이용해 스택을 구성할 수 있다.  ex) 1+(2+(3+4))
```ruby
calc = proc {|op,*args| args.reduce(op) || 0 }
lisp_eval = ->exp { eval exp.gsub(/[(\s)]/, '('=>"calc[:", ' '=>',', ')'=>']') }

# tests : arity 1, 3, 4, nested s-exp, complex s-exp
cases = ["(+)"] +
        ["(- 10 3)", "(* 2 3)"] +  
        ["(- 10 3 5)", "(* 2 3 4)"] +
        ["(* (+ 2 3) (- 5 3))", "(/ (+ 9 1) (+ 2 3))"] +
        ["(* 1 (- 2 3) 4 (+ 2 -1) 3)" ]
expect( cases.map(&lisp_eval) ).to eq [0, 7, 6, 2, 24, 10, 2, -12]
```

- [LISP계산기 문제. 풀이2](http://codingdojang.com/scode/533)  
reduce로 토큰을 쌓은 뒤 조건에 도달하면 top을 계산하는 방식으로 스택을 구현했다.
```ruby
calc = proc {|op,*args| args.reduce(op) || 0 }
eval_top = ->s { t=[]; t.unshift(s.pop) until t[0].is_a? Symbol; s << calc[*t] }
lisp_eval = ->str do
  tokens = str.gsub(/[()]/, "("=>"",")"=>" )").split.
               map {|e| e == ")" ? e : e =~ /[[:digit:]]/ ? e.to_i : e.to_sym }
  tokens.reduce([]) {|stack,e| e == ")" ? eval_top[stack] : stack << e }.pop
end

# tests : arity 1, 3, 4, nested s-exp, complex s-exp
cases = ["(+)"] +
        ["(- 10 3)", "(* 2 3)"] +  
        ["(- 10 3 5)", "(* 2 3 4)"] +
        ["(* (+ 2 3) (- 5 3))", "(/ (+ 9 1) (+ 2 3))"] +
        ["(* 1 (- 2 3) 4 (+ 2 -1) 3)" ]
expect( cases.map(&lisp_eval) ).to eq [0, 7, 6, 2, 24, 10, 2, -12]
```

- [Simple Balanced Parentheses 문제](http://codingdojang.com/scode/457)  
문자열을 스택으로 사용했다.
```ruby
is_balanced = ->str { str.scan(/[()]/).reduce("v") {|a,e| e=='('? a+e : a.chop} == "v" }

# test data
test_str = "(5+6)∗(7+8)/(4+3)"
balanced_strs = %w[ (()()()()) (((()))) (()((())())) ]
not_balanced_strs = %w[ ((((((()) ())) (()()(() (()))( ())(() ]

expect(is_balanced[test_str]).to eq true
expect(balanced_strs.map &is_balanced).to eq [true]*3
expect(not_balanced_strs.map &is_balanced).to eq [false]*5
```
<file_sep>require 'byebug'

describe "Coding Dojang - http://codingdojang.com" do
  it "#31 - Persons in the office - Amazon" do
    # simple way
    def cnt_login(time, file) # simple way
      exist = ->log { (log.first<=time && log.last >=time) ? 1:0 }
      File.open(file,'r').each_line
                         .map {|line| exist[line.strip.split] }.reduce :+
    end

    # lazy way
    def cnt_login_lazy(time, file) # lazy way
      exist = ->log { (log.first <= time && log.last >= time) ? 1:0 }
      lazy_cnt_map = File.open(file,'r').each_line.lazy
                                        .map {|line| exist[line.strip.split] }
      lazy_cnt_map.force.reduce :+
    end

    # mocking file
    data = "09:12:23 11:14:35\n10:34:01 13:23:40\n10:34:31 11:20:10\n"
    log_file = allow(File).to receive(:open).and_return(data)

    expect( cnt_login("09:13:20", log_file) ).to eq 1
    expect( cnt_login("11:05:20", log_file) ).to eq 3
    expect( cnt_login_lazy("09:13:20", log_file) ).to eq 1
    expect( cnt_login_lazy("11:05:20", log_file) ).to eq 3
  end

  it "#32 - Korea currency" do
    num = "영일이삼사오육칠팔구".chars.zip([*0..9]).to_h
    digit = "십백천만억조경".chars.zip([*1..4,8,12,16].map {|e|10**e}).to_h

    chunk = ->str { str.chars.map {|n| num[n]||digit[n]}.chunk_while {|i,j|i<j} }
    pairs = ->str { chunk[str].map {|n,*d| d.size>0? [n,[*d].reduce(:*)] : [1,n]} }
    revised = ->str { pairs[str].tap {|e| e[-1][-1]=10**7 if e[-2,2]&.map(&:last)==[10**8,10**3] }}

    valid = ->str { chunk[str].none? {|e| e[0]>=10**8} }
    kr_cur = ->str { valid[str]? revised[str].reduce(0) {|sum,(n,d)| d>sum ? (sum+n)*d : sum+(n*d)} : false}

    case1 = %w(영 일 칠 이천오 구천 일십만 십만)
    case2 = %w(일억오천 일억오천만 억오천만 사천구십칠조이천만삼백십육)
    expect( case1.map(&kr_cur) ).to eq [0, 1, 7, 2005, 9000, 10**5, 10**5]
    expect( case2.map(&kr_cur) ).to eq [15*10**7, 15*10**7, false,
                                                          4097000020000316]
  end

  it "#33 - Friend or Enemy. Path Compression" do
    root = ->n,nodes { n = nodes[n][:root] until nodes[n][:root]==n; n }
    set_node = ->n,r,rel,nodes { nodes[n][:root]=r; nodes[n][:rel]=rel; nodes }

    chk_cons = ->n, rel_info, nodes, incons do
      a, b, rel = rel_info[n]
      a_rel, b_rel = nodes[a][:rel], nodes[b][:rel]

      if root[a,nodes] == root[b,nodes]
        incons << n if (a_rel||0)^(b_rel||0) != rel
      else
        nodes = case [a_rel.nil?, b_rel.nil?]
        when [true, true] then set_node[b, a, rel, nodes]
        when [false, true] then set_node[b, root[a,nodes], a_rel^rel, nodes]
        when [true, false] then set_node[a, root[b,nodes], b_rel^rel, nodes]
        else set_node[root[b,nodes], root[a,nodes], a_rel^b_rel^rel, nodes]
        end
      end

      [nodes, incons]
    end

    find_mosoon = ->arrs do
      rels = ->s { s.map(&:split).map {|a,b,rel| [a.to_i, b.to_i, (rel=="f"? 0:1)] } }
      init = ->n { (1..n).reduce({}) {|a,e| a[e]={root:e}; a } }

      rel_info = rels[arrs]; size,line = rel_info[0];
      result = (1..line).reduce([init[size],[]]) {|a,nth| chk_cons[nth, rel_info, *a] }
      result[1].size > 0 ? result[1] : "THE SPY DID NOT BETRAY"
    end

    rpt1 = ["3 3", "1 2 f", "2 3 e", "1 3 f"]
    rpt2 = ["5 7", "1 2 f", "3 4 e", "4 5 f", "2 4 e", "1 3 e", "1 4 e", "5 3 e"]
    rpt3 = ["5 7", "1 2 f", "3 4 e", "4 5 f", "2 4 e", "1 3 f", "1 4 e", "5 3 e"]
    rpt4 = ["5 7", "2 1 f", "3 4 e", "4 5 f", "2 4 e", "1 3 f", "1 4 e", "5 3 e"]

    expect( find_mosoon[rpt1] ).to eq [3] #모순
    expect( find_mosoon[rpt2] ).to eq [5] #모순, 다중 병합.
    expect( find_mosoon[rpt3] ).to eq "THE SPY DID NOT BETRAY" #정상, 다중 병합.
    expect( find_mosoon[rpt4] ).to eq "THE SPY DID NOT BETRAY" #정상, 노드순서 뒤집힌 경우.
  end

  it "#34 - Mine map" do
    adjs = ->x,y { [*x-1..x+1].product([*y-1..y+1]) }
    cnt = ->adjs,mines { adjs.map {|x,y| mines.flatten(1).to_h[[x,y]]}.count('*') }
    stdin = ->io do
      (0...io.gets.split[1].to_i).map {|r| io.gets.chop.chars
                                           .map.with_index {|v,c|[[r,c],v]} }
    end
    mark = ->io,mines=stdin[io] do
      mines.map {|col| col.map {|xy,v| v=='*'? '*' : cnt[adjs[*xy],mines]}*'' }
    end
    #stdin = proc { (0...gets.split[1].to_i).map {|r| gets.chop.chars.map.with_index {|v,c|[[r,c],v]} } }
    #mark = ->mines=stdin[] { mines.map {|col| col.map {|xy,v| v=='*'? '*' : cnt[adjs[*xy],mines]}*'' } }
    #expect(mark[$stdin]).to eq %w(*100 2210 1*10 1110)
    $stdin = StringIO.new("4 4\n*...\n....\n.*..\n....\n")
    expect( mark[$stdin] ).to eq %w(*100 2210 1*10 1110)
  end

  it "#35 - Australian Voting" do
    def elect(names,votes)
      counter = votes.reduce(Hash.new(0)) {|cnt,prefer| cnt[prefer.first] +=1; cnt }
      min_votes, max_votes = counter.values.minmax
      if max_votes >= votes.size / 2
        names[counter.key(max_votes)-1]
      else
        last_places = counter.select {|_,v| v == min_votes}.keys
        votes.each {|v| v.delete_if {|prefer| last_places.include? prefer} }
        elect(names, votes)
      end
    end

    names = ["<NAME>", "<NAME>", "<NAME>"]
    votes = [[1, 2, 3], [2, 1, 3], [2, 3, 1], [1, 2, 3], [3, 1, 2]]
    expect( elect(names,votes) ).to eq "<NAME>"

    # 출력코드
    # n, inputs = gets.to_i, STDIN.read.split(/[\r\n]/)
    # names, votes = inputs.shift(n), inputs.map {|vote| vote.split.map &:to_i }
    # p elect(names, votes)  #=> "<NAME>
  end

  it "#36 - Poker Hands" do
    to_n = "23456789TJQKA".chars.zip(2..14).to_h
    hand = ->cards do
      ranks, suits = cards.map(&:chars).transpose
      pairs, nums = ranks.map(&to_n).group_by {|_|_}.
                          map {|k,g| [g.size,k] }.sort.reverse.transpose
      kind = %w(11111 2111 221 311 32 41).zip([0,1,2,3,6,7]).to_h[pairs.join]
      strt = 4 if nums.each_cons(2).all? {|i,j| i == j.next }
      flsh = 5 if suits.uniq.one?; stfl = 8 if strt && flsh
      [[kind, strt, flsh, stfl].compact.max, nums]
    end

    play_poker = ->file,games=File.readlines(file).map(&:split) do
      prt_result = ->idx { puts %w(Tie. Black\ wins. White\ wins.)[idx] }
      games.map {|cards| hand[cards[0,5]] <=> hand[cards[5,5]] }.each &prt_result
    end

    test_data = ["2H 3D 5S 9C KD 2C 3H 4S 8C AH\n",
             "2H 4S 4C 2D 4H 2S 8S AS QS 3S\n",
             "2H 3D 5S 9C KD 2C 3H 4S 8C KH\n",
             "2H 3D 5S 9C KD 2D 3H 5C 9S KH\n"]
    allow(File).to receive(:readlines).and_return(test_data)

    results = "White wins.\nBlack wins.\nBlack wins.\nTie.\n"
    expect{ play_poker["game_data.txt"] }.to output(results).to_stdout
  end

  it "#37 - Jolly Jumpers " do
    jolly = ->n,seq { seq.each_cons(2).map{|a,b|(a-b).abs}.reduce(:+)==(n-1)*2 }
    seqs = proc { (readline('0').split(/\n/)-["","0"]).map {|line| line.split.map(&:to_i) } }
    chk = proc { seqs[].map {|e| n,*seq=e; jolly[n,seq]? "Jolly":"Not Jolly" } }

    expect( jolly.(4, [1,4,2,3]) ).to eq true
    expect( jolly.(5, [1,4,2,-1,6]) ).to eq false

    # rspec에서는 동작하지만 guard-rspec 에서는 아래와 같은 에러가 발생한다.
    # Errno::ENOENT: No such file or directory @ rb_sysopen - -f
    # byebug
    $stdin = StringIO.new("4 1 4 2 3\n5 1 4 2 -1 6\n0")
    expect { puts chk.() }.to output("Jolly\nNot Jolly\n").to_stdout
  end

  it "#38 - Random Walk" do
    require 'set'

    X, Y = 100, 100; matrix = [*1..Y].product([*1..X]).to_set
    Tile = Struct.new :x, :y, :stay, :visit
    tiles = matrix.map {|e| [e, Tile.new(e[0],e[1],0,0)] }.to_h
    at = ->x,y { tiles[[x,y]] || false }

    crawl = ->bug { n=at[bug[0]+rand(-1..1),bug[1]+rand(-1..1)]; n ? [n.x,n.y] : bug }
    trail = ->prev,now { prev==now ? at[*prev].stay+=1 : at[*now].visit+=1 }
    move = ->bug { crawl[bug].tap {|e| trail[bug,e]} }

    bug=[rand(X)+1,rand(Y)+1]; (bug=move[bug]; matrix.delete(bug)) until matrix.empty?
    # puts "total movements : #{tiles.values.map(&:visit).reduce :+}"
    # tiles.each_slice(X).each {|cols| cols.each { |e|
    #             print "[%3d (%3d)] " % [e[1].stay, e[1].visit]}; print "\n" }
  end

  it "#39 - Max rope length - labyrinth" do
    # BFS. Max length
    max_rope_length = ->xy=gets.split.map(&:to_i) do
      labyrinth = Matrix[*(1..xy[1]).map { gets.chars[0, xy[0]] }]
      q, max_len = [labyrinth.index(".") << -1], 0

      road = ->x,y,_ { x>=0 && y>=0 && labyrinth[x,y] == "." }
      adjs = ->x,y,len { [[1,0],[-1,0],[0,-1],[0,1]].map {|r,c| [x+r,y+c,len] }.select &road }
      nexts = ->x,y,len { labyrinth.send(:[]=, x, y, "#")
                          max_len = [max_len, len+1].max; adjs[x,y,len+1] }
      q += nexts[*q.shift] until !q[0]
      "Maximum rope length is #{max_len}."
    end
    max_ropes = proc { puts (1..gets.to_i).map { max_rope_length[] } }

    test_labyrinths = "2\n"   +
                  "3 3\n" +
                  "###\n" +
                  "#.#\n" +
                  "###\n" +
                  "7 6\n" +
                  "#######\n" +
                  "#.#.###\n" +
                  "#.#.###\n" +
                  "#.#.#.#\n" +
                  "#.....#\n" +
                  "#######\n"
    expected_stdout = "Maximum rope length is 0.\n" +
                      "Maximum rope length is 8.\n"

    $stdin = StringIO.new(test_labyrinths)
    expect { max_ropes.call }.to output( expected_stdout ).to_stdout
  end

  it "#40 - Directory Diff " do
    require 'fileutils'

    ls = ->d { Dir.glob(d+"**/*").reduce([]) {|a,e| File.file?(e)? a<<e.sub(d,""):a }}
    trx = ->d1,d2 { lst,lsr = ls[d1],ls[d2]
                    %w(T R X).zip([lst-lsr, lsr-lst,
                    (lsr&lst).select {|f| !FileUtils.cmp(d1+f, d2+f) }]).to_h }
    #=> test에 /bin이 없고, real에 /spec이 없으며, same파일은 내용만 다를 때.
    # p trx["test/", "real/"]
    # {"T"=>["spec/redis_spec.rb", "spec/spec_helper.rb"],
    # "R"=>["bin/bundler", "bin/htmldiff", "bin/ldiff", "bin/respec", "bin/rspec"],
    # "X"=>["same"] }
 end
end
<file_sep>require 'rdate'

describe "RDate - 그레고리력 기준" do
  it ".new - 길이 8, Date문자열로 연월일 숫자 획득" do
    r_date = RDate.new("20160705")
    expect( r_date.year ).to eq 2016
    expect( r_date.month ).to eq 7
    expect( r_date.day ).to eq 5
  end

  it ".month_to_days - 해당년 1월 1일 부터 해당년 전월까지 경과일" do
    expect( RDate.new("20160105").month_to_days ).to eq 0
    expect( RDate.new("20160205").month_to_days ).to eq 31
    expect( RDate.new("20160305").month_to_days ).to eq 31+29
    expect( RDate.new("20160405").month_to_days ).to eq 31+29+31
  end

  it ".month_to_days - 윤달이 고려된 경과일 계산" do
    r_date = RDate.new("00040301")
    expect( r_date.leaf_year? ).to eq true
    expect( r_date.month_to_days).to eq 31+29
  end

  it ".year_to_days - 서기 1년 1월 1일로 부터 해당년 전년까지 경과일" do
    expect( RDate.new("00010101").year_to_days ).to eq 0
    expect( RDate.new("00020101").year_to_days ).to eq 365
    expect( RDate.new("00030101").year_to_days ).to eq 365*2
  end

  it ".year_to_days - 윤년은 366일로 계산" do
    # 4년이면 윤년
    expect( RDate.new("00040101").year_to_days ).to eq 1095
    expect( RDate.new("00050101").year_to_days ).to eq 1095 + 366
    # 100년이면 평년
    expect( RDate.new("01000001").year_to_days ).to eq 36159
    expect( RDate.new("01010001").year_to_days ).to eq 36159 + 365
    # 400년이면 윤년
    expect( RDate.new("04000001").year_to_days ).to eq 145731
    expect( RDate.new("04010001").year_to_days ).to eq 145731 + 366
  end

  it ".sum_date - 서기 1년 1월 1일로 부터 경과일" do
    expect( RDate.new("00010101").sum_date ).to eq 1
    expect( RDate.new("00010301").sum_date ).to eq 31+28+1
  end

  it ".subdate - 서기 0년 입력시 에러처리" do
    not_exist_zero = "서기 0년은 존재하지 않습니다"
    expect{ RDate.subdate("00000101", "20160706" ) }.
            to raise_error.with_message not_exist_zero
    expect{ RDate.subdate("20160706", "00000101" ) }.
            to raise_error.with_message not_exist_zero
  end

  it ".subdate - Date간 차이 일자 계산" do
    from, to = RDate.new("00010101"), RDate.new("00010201")
    expect( to.sum_date - from.sum_date ).to eq 31
    expect( RDate.subdate("00010101", "00010201") ).to eq 31
    expect( RDate.subdate("20160201", "20160301") ).to eq 29
    # params reverse-case
    expect( RDate.subdate("20160301", "20160201") ).to eq 29
    # dojang test datas
    expect( RDate.subdate("20070515", "20070501") ).to eq 14
    expect( RDate.subdate("20070501", "20070515") ).to eq 14
    expect( RDate.subdate("20070301", "20070515") ).to eq 75
  end
end
<file_sep>describe "Coding Dojang - http://codingdojang.com" do
  it "#21 - Convert unit" do
    # 1 inch  =  2.54 cm, 1 cm    =    10 mm, 1 inch  =    72 pt
    # 1 inch  =    96 px, 1 pt    =    20 dxa, 1 dxa   =   635 emu
    # %g - float formatting 1.0 -> 1 , 1.375 -> 1.375
    units = %w(inch cm mm pt px dxa emu)
    tr_rate_inch = [1, 2.54, 25.4, 72, 96, 20*72, 635*20*72]
    unit = units.zip(tr_rate_inch).to_h
    conv = ->from,to { num,u = from.split;
                       ["%g" % ((unit[to].to_f / unit[u]) * num.to_i) , to]*' ' }

    expect( conv["10 cm", "cm"] ).to eq "10 cm"
    expect( conv["10 inch", "mm"] ).to eq "254 mm"
    expect( conv["1024 px", "pt"] ).to eq "768 pt"
    expect( conv["768 px", "inch"] ).to eq "8 inch"
    expect( conv["9144000 emu", "inch"] ).to eq "10 inch"
    expect( conv["12000 dxa", "px"] ).to eq "800 px"
  end

  it "#22 - Minimum Pair" do
    min_pair = ->sorted_list { sorted_list.each_cons(2).min_by{|a,b|b-a} }
    expect( min_pair.([1,3,4,8,13,17,20]) ).to eq [3,4]
  end

  it "#23 - 3n + 1" do
    # n이 홀수면 3*n+1, 짝수면 n/2. n이 1이 될 때 까지 수의 길이(갯수)를 구한다.
    # n의 범위를 입력 받아서 3n+1연산을 했을 때 가장 길이가 긴 n의 길이를 구한다.
    seq = ->num { num == 1 ? num : seq.(num.odd?? 3*num+1 : num/2) + 1 }
    max_len = ->lines=$< { lines.map {|line| limits = line.split
                           [limits, eval(limits*'..').map(&seq).max]*' ' } }
    expect( seq.(22) ).to eq 16
    #=> for stdin/stdout
    lines = ["1 10", "100 200", "201 210", "900 1000"]
    max_seq_lens = ["1 10 20", "100 200 125", "201 210 89", "900 1000 174"]
    expect( max_len.(lines) ).to eq max_seq_lens
  end

  it "#24 - Manipulate names" do
    str = "이유덕,이재영,권종표,이재영,박민호,강상희,이재영,김지완,최승혁,이성연," +
          "박영서,박민호,전경헌,송정환,김재성,이유덕,전경헌"
    names = str.split(/,/)
    # 순서대로 1,2,3,4번 각각의 출력.
    # puts names.select {|n| n[0]=~/[김|이]/ }.group_by {|c|c[0]}.
    #                                        map {|f,v| "#{f}: #{v.size}" }
    # puts names.count {|n| n=="이재영" }
    # puts names.uniq.join
    # puts names.uniq.sort
  end

  it "#25 - Pots of gold game" do
    # 2명이 번갈아가며 가장 앞, 또는 가장 뒤 항아리를 선택할 수 있을 때
    # A가 이길 수 있는 최선의 전략을 찾아보기

    # A, B가 최선의 선택을 하는 경우를 가정했을 때 이기는 경우를 발견하는 전략.
    # A - B - A순으로 큰값을 선택해야 한다.
    # 따라서 sum = A선택 + B가선택하고 남은 값. 이들 중 가장 큰 값을 재귀로 취하면 된다.
    sum = ->p { [*p][0]? [ p[0] + [sum[p[1..-2]], sum[p[2..-1]]].min,
                          p[-1] + [sum[p[1..-2]], sum[p[0..-3]]].min ].max : 0 }
    expect( sum.([9,8,7,5,4,6,3,1,2]) ).to eq 25
    expect( sum.([3,6,2,8,1,9,4,5,7]) ).to eq 17 #=> B가 이길수 밖에 없는 상황.
  end

  it "#26 - Smallest Range with Catesian Product" do
    # min_range = ->l { l.shift.product(*l).map(&:minmax).sort_by{|a,b|b-a}[0] }
    min_range = ->lists { lists.shift.product(*lists).
                            map(&:minmax).min_by {|a,b| b-a } }
    lists = [[4,10,15,24,26], [0,9,12,20], [5,18,22,30]]
    expect( min_range.(lists) ).to eq [20,24]
  end

  it "#27 - K Palindrome" do
    def pal(s, k)
      return "NO" if k < 0
      return "YES" if s.size < 2
      s[0]==s[-1]? pal(s[1..-2],k) : pal(s[1..-1],k-1) || pal(s[0..-2],k-1)
    end

    expect( pal("ab", 1) ).to eq "YES"
    expect( pal("abc", 1) ).to eq "NO"
    expect( pal("abxa", 1) ).to eq "YES"
    expect( pal("abdxa", 1) ).to eq "NO"
    expect( pal("asfsdfasdfsdfsadfasfadfasdfadfadfadfdf", 15) ).to eq "NO"
    expect( pal("asfsdfasdfsdfsadfasfadfasdfadfadfadfdf", 30) ).to eq "YES"
    # one-liner
    # pal = ->s,k { k<0? "NO" : s[1]? s[0]==s[-1]? pal[s[1..-2],k] :
    #                            pal[s[1..-1],k-1] || pal[s[0..-2],k-1] : "YES" }
  end

  it "#28 - Special sort" do
    s_sort = ->list { list.group_by {|e| e < 0 }.map(&:last).flatten }
    expect( s_sort.([-1,1,3,-2,2]) ).to eq [-1,-2, 1, 3, 2]
  end

  it "#29 - Array Swapping in-place - Amazon" do
    # ver.simple
    transform = ->a { a.shift(a.size/2).zip(a).flatten }
    expect(transform[%w(a b c 1 2 3)]).to eq %w(a 1 b 2 c 3)
    # ver.swap
    swap = ->arr { arr.each_slice(2).map {|a,b|a,b=b,a}.flatten }
    transform = ->a,n=a.size/2 { (1...n).each{|i|a[n-i,i*2]=swap[a[n-i,i*2]]}; a}
    # ver.nested-loop to catesian
    swap = ->a { n=a.size/2; [*1...n].product([*0...2]).each {|i,j| idx=n-i+2*j
                                        a[idx],a[idx+1] = a[idx+1],a[idx] }; a }
    # ver.in-place
    transform = ->a,n=a.size/2 { (1...n).each {|i|
                    (0...i).each{|j| x=n-i+2*j; a[x],a[x+1]=a[x+1],a[x]}}; a }
    expect( transform.(%w(a b c 1 2 3)) ).to eq %w(a 1 b 2 c 3)
  end

  it "#30 - Alphabet with numbers - Facebook" do
    ch = ('1'..'26').zip('a'..'z').to_h
    head = ->n { [1,2].map {|i| [n[i..-1]||"", ch[n[0,i]]] }.uniq.select {|_,c|c} }
    gens = ->num,s="" { num[0] ? head[num].flat_map {|n,c| gens[n, s+c] } : s }

    expect( gens['1123'] ).to eq ["aabc", "aaw", "alc", "kbc", "kw"]
    expect( gens['12321'] ).to eq ["abcba", "abcu", "awba", "awu", "lcba", "lcu"]
    expect( gens['12345'] ).to eq ["abcde", "awde", "lcde"]
    expect( gens['129981'] ).to eq ["abiiha", "liiha"]
  end
end
<file_sep>#### 6. Matrix는 반복되는 거대 연산과 2차원 배열 탐색에 유용하다.
- [피보나치 수열 구하기](http://redutan.github.io/2016/03/31/anti-oop-if)  
피보나치 수 연산에 [행렬 지수함수](https://ko.wikipedia.org/wiki/%ED%96%89%EB%A0%AC_%EC%A7%80%EC%88%98_%ED%95%A8%EC%88%98) 연산자 Matrix#** 를 사용해서 빠르고 간단하게 답을 구할 수 있다.
```ruby
require 'matrix'
require 'benchmark'

# base_matrix [1, 1]  fibo nth = fibo ** nth.  [F nth+1, F nth]
#             [1, 0]                           [F nth, F nth-1]. F nth-1이 답.
def fibo_rest(nth, divisor=4294967291)
  base_matrix = Matrix[[1,1],[1,0]]
  m_exp = ->m,n { n==1 ? m : (m_exp[m, n>>1]**2).map{|_|_%divisor} * m**(n&1) }
  m_exp[base_matrix, nth][1,1]
end

expect( fibo_rest(1) ).to eq 0
expect( fibo_rest(5) ).to eq 3
expect( fibo_rest(10** 10) ).to eq 2839099801
expect( fibo_rest(10**100) ).to eq 2537702751
expect( Benchmark.realtime { fibo_rest(10**100) } ).to be_between(0.0, 1.0)
```

- [미로 통과 검사](http://codingdojang.com/scode/402)  
BFS 알고리즘으로 2차원 배열의 상하좌우로 이동할 수 있는 길을 찾은 뒤, 이동할 수 있는 지점을 큐에 채워넣는 방식을 반복해서 종착지를 찾으면 true, 그렇지 않으면 false가 된다. 2차원 배열을 직접 다루는 것 보다 매트릭스가 직관적이다. 단, matrix에 값을 할당하는 일은 조금 불편하다.
```ruby
require 'matrix'

maze_checker = ->maze_str=gets("\n\n") do
  maze = Matrix[*maze_str.split("\n").map(&:chars)]
  start, dest = %w(< >).map {|ch| maze.index(ch) }
  q = [start]

  road = ->x,y { x>=0 && y>=0 && " ><".chars.include?(maze[x,y]) }
  next_of = ->x,y { [[1,0],[-1,0],[0,-1],[0,1]].map {|a,b| [x+a,y+b] }.select &road }
  nexts = ->x,y { maze.send(:[]=, x, y, "#"); next_of[x,y] }

  (x,y = q.shift; maze[x,y]==">"? (return true) : q += nexts[x,y]) until !q[0]
  false
end

maze1 = "<      >\n"
maze2 = "########\n" +
        "#<     #\n" +
        "#  ##  #\n" +
        "#  ##  #\n" +
        "#     >#\n" +
        "########\n"
maze3 = "#######\n" +
        "#<    #\n" +
        "##### #\n" +
        "#     #\n" +
        "# #####\n" +
        "# #   #\n" +
        "# # # #\n" +
        "#   #>#\n" +
        "#######\n"
maze4 = "<  #  >\n\n"
maze5 = "########\n" +
        "#<     #\n" +
        "#     ##\n" +
        "#    #>#\n" +
        "########\n"
maze6 = "#< #  #\n" +
        "#  #  #\n" +
        "#  # >#\n"

expect( [maze1, maze2, maze3].map &maze_checker ).to eq [true, true, true]
expect( [maze4, maze5, maze6].map &maze_checker ).to eq [false, false, false]
```
<file_sep>source  'http://rubygems.org/'

group :development, :test do
  gem 'rspec', '3.4.0'
  gem 'rspec-benchmark', '0.1.0'
  gem 'byebug', '9.0.5'
  gem 'guard-rspec', '4.7.1'
  gem 'bitset', '0.1.0'
end
<file_sep>#### 8. 약기, Syntatic Sugar, 관행
약기와 syntatic sugar는 응축된 표현이 가능케 하고, 관행은 반복과 boiler-plate 코드를 줄일 수 있게 해준다.

- Range  
range클래스는 주어진 값의 #succ로 다음값을 획득하게 되어있다. String#succ는 오른쪽 끝문자의 숫자/영문 여부를 확인한 뒤 숫자면 숫자범위에서만 값 증가, 영문이면 영문내에서 값 증가, 혼합형이면 ascii로 증가시킨다. 값을 증가시키는 중에 최대값을 넘어서는 carry가 발생하면 올림자리 값을 증가시킨다.
```ruby
# 관행
("1a".."zz").to_a #=> ["1a", "1b",.., "9z"]
("11a".."zz").to_a.size #=> 234. 9(1~9까지) * 26(a~z까지)
("11a".."zzz").to_a #=> ["11a", "11b",.., "99z"]

# loop를 Stream.forEach()로 바꾸지 말아야 할 3가지 이유](http://bit.ly/28UZ60o)
# : 본문의 엑셀 컬럼명 생성을 구현해보자. ex) A, B, ..., Z, AA, AB, ..., ZZ, AAA
# : 명시적으로 아래와 같이 구현할 수 있다.
xls_cols = ->n do
  (1..n).flat_map {|e| [*"A".."Z"].repeated_permutation(e).map(&:join) }
end  
xls_cols[3]

# 관행
# : 매번 개인이 range에서 요구하는 동작을 구현할 필요 없이 range에 관행을 반영한다.
# : 그와 같은 이유로 위의 코드가 필요없이 range만으로 간단히 구현할 수 있다.
("A".."ZZZ").to_a # 약기로 [*"A".."ZZZ"]
```

- String#split  
문자열.split("구분자") 처럼 구분자를 명기하면 구분자에 의해서 문자열 여러개로 분리된다. 일반적으로 '공백'과 '줄 바꿈'에 의해서 문자열을 분할 하는 경우가 많다. 따라서 구분자 없이 기입하는 경우 이 둘에 의해서 문자열이 분리된다.
```ruby
# 관행
"abc def".split   #=> ["abc", "def"]
"1\n2\n3\n".split #=> ["1", "2", "3"]
"1 2\n3\n".split  #=> ["1", "2", "3"]

# 구분자 명기
"1234567".split("4") #=> ["123", "567"]
```

- String#to_i  
문자열 안에 공백과 줄바꿈 문자가 포함되어 있으면 관행에 의해서 자동으로 탈락된다.
```ruby
# 관행
"123".to_i      #=> 123
"123 ".to_i     #=> 123
"123 \n".to_i   #=> 123
"1 23 \n".to_i  #=> 1

# 명시적 삭제
"123 ".chop.to_i    #=> 123
"123 \n".chomp.to_i #=> 123
```

- Proc, Lambda, &  
루비의 list comprehension은 좌에서 우로 흐른다.(몇몇 언어는 우에서 좌로 흐른다.) 이와 같은 특성으로 루비의 리스트 조작식은 평문처럼 읽히는 장점이 있다. 복잡한 표현식은 함수(Proc, Lambda)로 분리한 뒤, &를 사용하여 함수를 조립, 자연스럽게 읽히는 표현을 만들 수 있다.
```ruby
# 객체 메소드 약기 : Symbol#to_proc. 객체의 메소드는 &심볼로 표현할 수 있다.
[1, 2, 3].map {|e| e.to_s } #=> [1, 2, 3].map(&:to_s)

# proc, lambda 약기 : &함수명 으로 표현할 수 있다.
plus_one = ->n { n + 1 }
[1, 2, 3].map {|e| plus_one.call(e) } #=> [1, 2, 3].map(&plus_one)


# 응용 : 수정 전
DICT = %w(.- -... -.-. -.. . ..-. --. .... .. .--- -.- .-.. -- -. --- .--.
         --.- .-. ... - ..- ...- .-- -..- -.-- --..).zip('a'..'z').to_h
translate = ->morse do
  morse.split('  ').map {|w| w.split.map {|e| DICT[e] }.join }.join(" ")
end  

test_morse_str = ".... .  ... .-.. . . .--. ...  . .- .-. .-.. -.--"
expect( translate[test_morse_str] ).to eq "he sleeps early"

# 응용 : 수정 후
# 왼쪽에서 우측으로 한글처럼 읽어도 무리가 없다.
# 약기 단어의 모스부호 변환을 word란 함수로 분리, DICT해쉬를 &DICT로 표현
DICT = %w(.- -... -.-. -.. . ..-. --. .... .. .--- -.- .-.. -- -. --- .--.
         --.- .-. ... - ..- ...- .-- -..- -.-- --..).zip('a'..'z').to_h
word = ->morse_word { morse_word.split.map(&DICT).join }
translate = ->morse_str { morse_str.split('  ').map(&word).join(" ") }

test_morse_str = ".... .  ... .-.. . . .--. ...  . .- .-. .-.. -.--"
expect( translate[test_morse_str] ).to eq "he sleeps early"
```

- %w  
문자열 배열은 따옴표가 반복되므로 %w로 짧게 표현할 수 있다.
```ruby
alphabet = %w(a b c d e) #=> ["a", "b", "c", "d", "e"]
```

- `*`  
splat 연산자는 여러 개의 요소들을 표현하는데 도움이 된다.
```ruby
head, *tail = [1, 2, 3, 4] #=> head = 1, tail = [2, 3, 4]
*init, last = [1, 2, 3, 4] #=> init = [1, 2, 3], last = 4

param = [1, 2]
do_something = ->a,b { [a, b] }
do_something.call(*param)

arr = %w(a b c d)
Hash[*arr] #=> Hash["a", "b", "c", "d"] = {"a"=>"b", "c"=>"d"}
```

- Proc.call  
.call, .(), [] 모두 함수 호출을 의미한다. 함수 호출을 간결하게 표현할 수 있다.
```ruby
plus = ->a,b { a + b}

plus[1, 2]
plus.(1, 2)
plus.call(1, 2)
```
<file_sep>#### 5. Array#dig, Hash#dig 는 n차원의 배열, 해쉬를 단순하게 다루는데 도움이 된다.
- [if를 피하고 싶어서](http://redutan.github.io/2016/03/31/anti-oop-if)  
다양한 할인정책(쿠폰 - 금액할인, 사이트 - 할인율)과 할인 계산식을  
상수, 함수, 파라미터 arity와 관계없이 해쉬와 한 줄의 코드로 응축할 수 있다.
```ruby
DICT = { "discnt" =>
          { "coup" => { "fcafe" => 1000, "pack" => [700, 800, 900] },
            "rate" => { "normal" => 0.0, "naver" => 0.1, "danawa" => 0.15 },
            "form" => { "normal" => "rate_form", "naver" => "rate_form",
                        "danawa" => "rate_form", "fcafe" => "coup_form",
                        "amazon" => "->amt { (amt ** 0.5).round }"} } }
# load rule
rule = ->path { eval( DICT.dig(*path.split("/")).to_s ) }

# local objects to load - 사용자가 정의한 함수들도 로딩 가능
rate_form = ->code,amt,rate=rule["discnt/rate/"+code] { rate * amt }
coup_form = ->code,amt,coup=rule["discnt/coup/"+code] { amt < coup ? amt : coup }

# usage : code in application
rule["discnt/form/naver"].call("naver", 1000) #=> 1_00 (네이버 할인:amt*0.1)
rule["discnt/form/amazon"].call(1000) #=> 32 (아마존 할인:amt**0.5.round )

# test - constant cases
expect( rule["discnt/rate/normal"] ).to eq 0.0
expect( rule["discnt/coup/pack"] ).to eq [700, 800, 900]
# test - function cases
expect( rule["discnt/form/normal"].call("normal",1000) ).to eq 0.0
expect( rule["discnt/form/naver"].call("naver",1000) ).to eq 100.0
expect( rule["discnt/form/fcafe"].call("fcafe",1000) ).to eq 1000.0
expect( rule["discnt/form/amazon"].call(1000) ).to eq 32
```
<file_sep># [coding dojang](http://codingdojang.com/)

algorithm & idiom practices.

#### 작성

* 루비 습득과 언어의 자유도 확인, Fp느낌을 알고자 작성.
* 위 목적에 의해 short-hand, proc, lambda, high-order function 위주로 작성
* 테스트는 [RSpec](http://rspec.info/)을 사용.
* [링크](http://codingdojang.com/profile/answer/3058)에서 풀이내역을 확인할 수 있다.

#### 항목

* [quiz 01 ~ 10](https://github.com/cavinkwon/ruby_coding_dojang/blob/master/spec/quiz_01_10_spec.rb) : 8-queens 복잡한 재귀, Tug of War에 Dynamic programming, Spiral Array로 2차원 배열의 회전, Four Box로 배열통합을 사용하였다.
* [quiz 11 ~ 20](https://github.com/cavinkwon/ruby_coding_dojang/blob/master/spec/quiz_11_20_spec.rb) : Nth-palindrome에 memoization, 미로통과 검사에 BFS 알고리즘을 사용하였다.
* [quiz 21 ~ 30](https://github.com/cavinkwon/ruby_coding_dojang/blob/master/spec/quiz_21_30_spec.rb) : K-palindrome에 memoization, 미로통과 검사 문제에 BFS 알고리즘을 사용하였다. Amazon의 Alphabet with numbers 문제가 흥미롭다.
* [quiz 31 ~ 40](https://github.com/cavinkwon/ruby_coding_dojang/blob/master/spec/quiz_31_40_spec.rb) : Friend or Enemy에 서로소 집합 자료구조, labyrinth에 BSF를 사용하였다. jolly numbers와 Australian Voting 문제가 흥미롭다.
* [quiz 41 ~ 50](https://github.com/cavinkwon/ruby_coding_dojang/blob/master/spec/quiz_41_50_spec.rb) : Optimum polynomial은 역행렬을 사용한 방정식풀이 문제,  Euclid Problem에 extended euclid, bezout, modular inverce를 차용하였다. Ugly Number 문제가 흥미롭다.
* [quiz 51 ~ 60](https://github.com/cavinkwon/ruby_coding_dojang/blob/master/spec/quiz_51_60_spec.rb) : Sliding window, One Edit Apart 문제가 흥미롭다.
* [quiz 61 ~ 70](https://github.com/cavinkwon/ruby_coding_dojang/blob/master/spec/quiz_61_70_spec.rb) : XOR descryption, Simple Balanced Parentheses에 문자열을 활용한 스택, fibonacci에 매트릭스를 활용한 pow연산, lazy 연산을 사용하였다.
* [quiz 71 ~ 80](https://github.com/cavinkwon/ruby_coding_dojang/blob/master/spec/quiz_71_80_spec.rb) : Conway's Life Game에 결합, 분배법칙. rotation연산이 사용되었다. tic-tac-toe game과 morse translation 문제가 흥미롭다.
* [quiz 81 ~ 90](https://github.com/cavinkwon/ruby_coding_dojang/blob/master/spec/quiz_81_90_spec.rb) : Print image에 BFS 알고리즘을 사용하였다.
* [quiz 91 ~ 100](https://github.com/cavinkwon/ruby_coding_dojang/blob/master/spec/quiz_91_100_spec.rb) : OLHC에 입출력 구조화가 사용되었다.
* [quiz 101 ~ 110](https://github.com/cavinkwon/ruby_coding_dojang/blob/master/spec/quiz_101_110_spec.rb) : Count primes에 sieve of erastosthenes 알고리즘을 사용하였고 Boxes area에 매트릭스 구조화를 사용하였다. Word Ladder, Scale group문제가 매우 흥미롭다.
* [quiz 111 ~ 120](https://github.com/cavinkwon/ruby_coding_dojang/blob/master/spec/quiz_111_120_spec.rb) : Panda's survival, Safe passage 문제가 흥미롭다.
* [quiz 121 ~ 130](https://github.com/cavinkwon/ruby_coding_dojang/blob/master/spec/quiz_121_130_spec.rb) : Conditional combination이 사용되었다.

#### TO-DO

* 테스트 STDIN  [StringIO](http://ruby-doc.org/stdlib-2.3.1/libdoc/stringio/rdoc/StringIO.html) 을 명시적으로 표현
* [Enumerable](http://ruby-doc.org/core-2.3.1/Enumerable.html), [Lazy](http://ruby-doc.org/core-2.3.1/Enumerator/Lazy.html), [Concurrent](https://github.com/ruby-concurrency/concurrent-ruby), code-golf tip을 cheet sheet로 묶기.


## 참고자료
* [Extended Euclidean algorithm](https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm)
* [Bézout's identity](https://en.wikipedia.org/wiki/B%C3%A9zout%27s_identity)
* [Modular inverse](https://rosettacode.org/wiki/Modular_inverse)
* [BFS: Breadth-first search](https://en.wikipedia.org/wiki/Breadth-first_search)
* [Dynamic Programming](https://ko.wikipedia.org/wiki/%EB%8F%99%EC%A0%81_%EA%B3%84%ED%9A%8D%EB%B2%95)
* [Memoization](https://ko.wikipedia.org/wiki/%EB%A9%94%EB%AA%A8%EC%9D%B4%EC%A0%9C%EC%9D%B4%EC%85%98)
<file_sep>describe "Coding Dojang - http://codingdojang.com" do
  it "#111 - Panda's survival" do
    def max_trail(bamboos, n=[*0...bamboos.size])
      trees = n.product(n).map {|x,y| [y,x,bamboos[x][y]] }
      nexts = ->x,y,v do
        trees.select {|ex,ey,ev| (x-ex).abs+(y-ey).abs <2 && ev>v }
      end
      trail = ->xy,cnt=1,t=nexts[*xy] { t[0]? t.map {|e| trail[e,cnt+1] } : cnt }
      trees.map(&trail).flatten.max
    end

    bamboos_1 = [ [0, 0],
                  [0, 0] ]
    expect(max_trail(bamboos_1)).to eq 1

    bamboos_2 = [ [ 1, 6, 7],
                  [ 2, 5, 8],
                  [ 3, 4, 9] ]
    expect(max_trail(bamboos_2)).to eq 9

    bamboos_3 = [ [14, 9,12,10],
                  [ 1,11, 5, 4],
                  [ 7,15, 2,13],
                  [ 6, 3,16, 8] ]
    expect(max_trail(bamboos_3)).to eq 4
  end

  it "#112 - Safe Passage" do
    # 빠른 둘로 가장 느린 둘을 이동시키는 시간을 한 사이클로 두면 수식으로 일반화 가능(홀/짝 두가지).
    # a,b를 가장 빠른 둘, rest를 나머지라 할 때,
    # 전체이동시간 = (a x 이동횟수) + (b x 이동횟수) + (느린 멤버 둘씩 이동하는 시간의 총합)
    sum = ->a,b,rest,size { a*(size+1)/2 + b*(size+1 - size%2) +
                            rest.reverse.each_slice(2).map(&:first).reduce(:+) }
    safe_time = ->times { a,b,*rest = times.split.map(&:to_i).drop(1).sort
                          rest[0] ? sum[a,b,rest,rest.size] : b || a }

    expect( safe_time["2 15 5"] ).to eq 15
    expect( safe_time["4 1 2 7 10"] ).to eq 17
    expect( safe_time["5 12 1 3 8 6"] ).to eq 29
  end

  it "#114 - Pingpong" do
    # 제약사항 : For Loop 또는 Array를 쓰지 말 것
    #           Assignment를 쓰지 말 것, 즉, 변수 할당을 하지 말 것.
    #           String을 쓰지 말 것

    # 1. string, list comprehension - 쉽게 풀기
    sevens = ->nth { (0..nth).select {|n| n%7==0 || n.to_s.include?("7") || n==nth} }
    pingpong = ->x { sevens.(x).each_cons(2).map {|a,b| b-a }.map.with_index.
                               reduce(0) {|sum,(gap,i)| sum + gap*(i.odd?? -1:1)} }
    # test
    expect( [8, 22, 68, 100].map &pingpong ).to eq [6, 0, 2, 2]
    expect( Benchmark.realtime { pingpong.(10000) } ).to be_between(0.001, 0.1)

    # 2. list comprehension - string 쓰지 않기
    is_7 = ->n { n.abs<10 ? (n.abs==7) : is_7.(n/10) || is_7.(n%10) }
    sevens = ->nth { (0..nth).select {|n| is_7.(n) || n%7==0 || n==nth } }
    pingpong = ->x { sevens.(x).each_cons(2).map {|a,b| b-a }.
                            map.with_index {|gap,i| gap * (i.odd?? -1:1) }.reduce:+ }
    # test
    expect( [7, 17, -7].all? &is_7 ).to be_truthy # 7s
    expect( [8,  0, -2].all? &is_7 ).to be_falsy  # not 7s
    expect( sevens.(20) ).to eq [0, 7, 14, 17, 20]
    expect( [8, 22, 68, 100].map(&pingpong) ).to eq [6, 0, 2, 2]
    expect( Benchmark.realtime { pingpong.(10000) } ).to be_between(0.001, 0.2)

    # 3. recursion
    is_7 = ->n { n.abs<10 ? n.abs==7 : is_7[n/10] || is_7[n%10] || n%7==0 }
    pingpong = proc do |nth, idx, val, inc|
      idx.nil? ? pingpong.(nth, 1, 1, 1) : idx == nth ? val :
                 pingpong.(nth, idx+1, val+inc, is_7.(idx+1) ? -inc : inc )
    end
    # test
    expect( [8, 22, 68, 100].map(&pingpong) ).to eq [6, 0, 2, 2]
  end

  it "#115 - Reverse Spiral Array" do
    cube = ->x,y { (1..y).map {|row| [*1..x].product [row]} }
    peel = ->cub { cub[0]? cub.pop + peel[cub.reverse.transpose] : [] }
    rev_sp = proc { x,y = gets.split.map(&:to_i); r_sp = peel[cube[x,y]].reverse
                    puts cube[x,y].map {|r| r.map {|e| "%3d" % r_sp.index(e)}*''} }

    $stdin = StringIO.new("5 6") #=> for stdin test
    expect{ rev_sp.() }.to output(" 16 17 18 19 20\n" +
                                  " 15  4  5  6 21\n" +
                                  " 14  3  0  7 22\n" +
                                  " 13  2  1  8 23\n" +
                                  " 12 11 10  9 24\n" +
                                  " 29 28 27 26 25\n" ).to_stdout

  end

  it "#116 - Look and say sequence" do
    # import Data.List
    # let ant = iterate (concatMap(\g-> [length g, head g]) . group)[1]
    # ant !! 999 !! 999
    ant = ->seq=[1] { seq.chunk{|_|_}.flat_map {|h,g|[g.size,h]} }
    iterate = ->n { n==1? [1].lazy : ant[iterate[n-1]] }
    ant_seq = ->nth,n { iterate[100].take(100).force.last }

    expect( ant_seq[100,100] ).to eq 1
  end

  it "#117 - 120th Prisoner" do
    free = ->n,f=1,cnt=0 { n>=f ? free[n,f+1,cnt+(n%f>0?0:1)] : cnt.odd? }
    cnt_frees = ->n { (1..n).count(&free) }

    expect( [1,4,9,16,25,36,49,64,81,100].all?(&free) ).to eq true
    expect( [2,3,8,18,120].all?(&free) ).to eq false
    expect( cnt_frees[120] ).to eq 10
  end

  it "#118 - Longest common partial string" do
    def longest_common_str(io=$stdin)
      a,b = (1..2).map {io.gets.chop}.sort_by(&:size)
      cmn = ->s { (s...a.size).reduce("") {|_,e| b.include?(a[s..e])? a[s..e] : break } }
      lcs = (0...a.size).reduce("") {|gcs,s| [gcs, cmn[s]||""].max_by(&:size) }
      puts lcs.size, lcs
    end

    test_stdin = StringIO.new("photography\nautograph\n") # for stdin
    expect{ longest_common_str(test_stdin) }.to output("7\ntograph\n").to_stdout
  end

  it "#120 - Dash Insert" do
    def dash_insert(str=gets.chop)
      head, *tail = str.chars.map(&:to_i).chunk_while {|a,b| a%2+b%2 == 1}.to_a
      puts (head + tail.map {|e| ["*-"[e[0]%2] ,e] }).join
    end

    $stdin = StringIO.new("4546793\n")
    expect{ dash_insert() }.to output("454*67-9-3\n").to_stdout
  end
end
<file_sep>describe "Coding Dojang - http://codingdojang.com" do
  # perfomance test, regular expression, matrix rotation
  it "#1 - Sum of self number" do
    # self_number란 generator가 없는 수를 말한다.
    # 1~n까지의 self number란 n까지의 generator를 배재한 결과를 말한다.
    generator = ->n { n + n.to_s.chars.map(&:to_i).reduce(:+) }
    sum_selfnum = ->n { ( [*1..n] - (1..n).map(&generator) ).reduce:+ }
    # one-liner
    # ([*1..5000] - (1..5000).map {|n| n.digits.sum + n }).sum
    #([*1..5000]-(1..5000).map {|n| n+n.to_s.chars.map(&:to_i).reduce(:+)}).reduce:+

    expect( generator.(1) ).to eq 2
    expect( generator.(91) ).to eq 101
    expect( (1..5).map(&generator) ).to eq [2, 4, 6, 8, 10]
    expect( (1..5).map(&generator) ).not_to include(1, 3, 5, 7, 9)
    # sum of self number
    expect( sum_selfnum.(5) ).to eq 9
    expect( sum_selfnum.(5000) ).to eq 1227365
  end

  it "#2 - Spiral array" do
    # cube를 외곽부터 90도씩 돌려가며 깎아내서 수열을 만든 뒤
    # cube 인덱스로 깎아낸 수열의 값을 획득한다
    cube = ->x,y { (0...y).map {|e| [*0...x].product [e] } }
    peel = ->cub { cub[0]? cub.shift + peel[cub.transpose.reverse] : [] }
    spiral = ->x,y { spr = peel[cube[x,y]]
                     cube[x,y].map {|_|_.map {|e| "%3d" % spr.index(e)}*'' } }

    expect( spiral[4,3] ).to eq ["  0  1  2  3",
                                 "  9 10 11  4",
                                 "  8  7  6  5"]
    expect( spiral[6,6] ).to eq ["  0  1  2  3  4  5",
                                 " 19 20 21 22 23  6",
                                 " 18 31 32 33 24  7",
                                 " 17 30 35 34 25  8",
                                 " 16 29 28 27 26  9",
                                 " 15 14 13 12 11 10"]
  end

  it "#3 - LCD Display" do
    DGTS = %w(DSDDSDDDDD CRRRCLLRCC SSDDDDDSDD CRLRRRCRCR DSDDSDDSDD)
    dic = ->n { s,d,l = " -|".chars
                [ s*(n+2), s+d*n+s, l+s*(n+1), l+s*n+l, s*(n+1)+l ] }
    to_code = ->size,num do
      num.chars.map {|n| DGTS.map {|d| d[n.to_i] } }.
                     transpose.map.with_index {|a,i| [a] * (i.odd?? size:1) }
    end
    lcd = ->s,num { s = s.to_i; dh = "SDLCR".chars.zip(dic[s]).to_h
                    to_code[s,num].flatten(1).map {|line| line.map(&dh)*'' } }
    print_lcd = proc { puts $<.map(&:split).map(&lcd) }

    expect( to_code.(1, "0") ).to eq [[["D"]],
                                      [["C"]],
                                      [["S"]],
                                      [["C"]],
                                      [["D"]]]
    expect( to_code.(2, "0") ).to eq [[["D"]],
                                      [["C"], ["C"]],
                                      [["S"]],
                                      [["C"], ["C"]],
                                      [["D"]]]
    expect( to_code.(2, "40")).to eq [[["S", "D"]],
                                       [["C", "C"], ["C", "C"]],
                                       [["D", "S"]],
                                       [["R", "C"], ["R", "C"]],
                                       [["S", "D"]]]
    expect( lcd.("2", "40") ).to eq ["     -- ", # S D
                                     "|  ||  |", # C C
                                     "|  ||  |", # C C
                                     " --     ", # D S
                                     "   ||  |", # R C
                                     "   ||  |", # R C
                                     "     -- "] # S D
  end

  it "#4 - Slurpy. regular expression" do
    slump = /([DE]F+)+G/
    slimp = /(?<slimp>AH|AB\g<slimp>C|A#{slump}C)/
    slurpy = /#{slimp}#{slump}/

    is_slurpy = proc { gets.chop =~ /^#{slurpy}$/ ? "YES" : "NO" }
    check = proc { (1..(gets.to_i)).map(&is_slurpy) }
    slurpy_out = proc { puts "SLURPY OUTPUT", check.(), "END OF OUTPUT" }

    slumps     = %w(DFG EFG DFFFFFG DFDFDFDFG DFEFFFFFG)
    not_slumps = %w(DFEFF EFAHG DEFG DG EFFFFDG)
    slimps     = %w(AH ABAHC ABABAHCC ADFGC ADFFFFGC ABAEFGCC ADFDFGC)
    not_slimps = %w(ABC ABAH DFGC ABABAHC SLIMP ADGC)
    slurpys    = %w(AHDFG ADFGCDFFFFFG ABAEFGCCDFEFFFFFG)
    not_slurpys= %w(AHDFGA DFGAH ABABCC)

    #=> regex test
    expect( slumps.all? {|s| s=~/^#{slump}$/ } ).to eq true
    expect( not_slumps.all? {|s| s=~/^#{slump}$/ } ).to eq false
    expect( slimps.all? {|s| s=~/^#{slimp}$/} ).to eq true
    expect( not_slimps.all? {|s| s=~/^#{slimp}$/ } ).to eq false
    expect( slurpys.all? {|s| s=~/^#{slurpy}$/ } ).to eq true
    expect( not_slurpys.all? {|s| s=~/^#{slurpy}$/ } ).to eq false
  end

  it "#5 - Multiples of 3 and 5" do
    # 3 또는 5로 나뉘어 지는 것의 합.
    # n%3==0||n%5==0 을 스마트하게 n%3 * n%5 < 1
    sum_multiples = ->max { (1...max).select {|n| n%3 * n%5 < 1 }.reduce:+ }
    expect( sum_multiples.(10) ).to eq 3+5+6+9
    expect( sum_multiples.(1000) ).to eq 233168
  end

  it "#6 - Eight Queens Problem" do
    # 8x8 체스판. 8개의 퀸이 행, 열, 대각선으로 겹치지 않게 놓일 수 있는 가짓수
    # 체스판의 1칸을 sqaure라 하자.
    # row 1~8까지 row조건에 맞는 것을 걸러내서. row가 max에 도달하면 cnt 1증가
    invalid = ->sx,sy,x,y { sx==x || sy==y || sx+sy==x+y || sx-sy==x-y }
    valids = ->squares,x,y { squares.reject {|sx,sy| invalid.(sx,sy,x,y) } }

    cnt_exs = lambda do |max,squares,row=1|
      squares.select {|sx,sy| sx == row }.reduce(0) do |sum,square|
        row == max ? 1 : sum + cnt_exs.(max, valids[squares,*square], row+1)
      end
    end
    queens = ->size,axis=[*1..size] { cnt_exs[size, axis.product(axis)] }

    expect( queens.(2) ).to eq 0
    expect( queens.(4) ).to eq 2
    expect( queens.(6) ).to eq 4
    expect( queens.(7) ).to eq 40
    expect( queens.(8) ).to eq 92
    # expect( queens.(10) ).to eq 724 #=> 6~8초

    # for performance
    expect( Benchmark.realtime { queens[8] } ).to be_between(0.0, 0.5)
    expect { queens[8] }.to perform_under(50).ms
    expect { queens[8] }.to perform_under(50).ms.and_sample(10)
    # expect { queens[4] }.to perform_at_least(100).ips
  end

  it "#7 - Count Eight" do
    # 1부터 10000까지 8이란 숫자는 총 몇번 등장하는가?
    counter = ->num,s { ([*1..num]*'').count s }
    # counter_2 = ->num,s { ([*1..num]*'').reduce(0) {|sum,e| sum+e.count(s) } }
    expect( counter.(10, '1') ).to eq 2
    expect( counter.(10000, '8') ).to eq 4000
  end

  it "#8 - SubDate" do
    # 그레고리력 기준
    require 'rdate'

    from, to = RDate.new("00010101"), RDate.new("00010201")
    expect( to.sum_date - from.sum_date ).to eq 31
    expect( RDate.subdate("00010101", "00010201") ).to eq 31
    expect( RDate.subdate("20160201", "20160301") ).to eq 29
    # params reverse-case
    expect( RDate.subdate("20160301", "20160201") ).to eq 29
    # dojang test datas
    expect( RDate.subdate("20070515", "20070501") ).to eq 14
    expect( RDate.subdate("20070501", "20070515") ).to eq 14
    expect( RDate.subdate("20070301", "20070515") ).to eq 75
  end

  it "#9 - Tug of War" do
    # Dynamic programming에 Set을 활용. 퍼포먼스 문제
    # Set을 값으로 갖는 배열을 n개 만들고 더해진 값을 Set에 저장.
    require 'set'

    def generate_sums_with(sums, weight, size=sums.size-2)
      (size).downto(0).each do |cnt|
        sums[cnt].each {|s| sums[cnt+1].add(s+weight) }
      end
    end

    def balanced_tug
      weights = (1..gets.to_i).map { gets.to_i }
      sums = (0..weights.size).map { Set.new }.tap {|e| e[0].add(0) }
      weights.each {|weight| generate_sums_with(sums, weight) }

      sum = weights.reduce(:+)
      balanced_one = sums[weights.size/2].min_by {|e| (sum-e*2).abs }
      [balanced_one, sum-balanced_one].minmax.join(" ")
    end

    # stdin test data
    test_stdin  = %w( 3 100 90 200
                      6 45 55 70 60 50 75
                      4 92 56 47 82
                      5 2 3 4 7 8
                      4 50 50 100 200).join("\n")
    $stdin = StringIO.new(test_stdin)
    expect( balanced_tug ).to eq "190 200"
    expect( balanced_tug ).to eq "175 180"
    expect( balanced_tug ).to eq "138 139"
    expect( balanced_tug ).to eq "12 12"
    expect( balanced_tug ).to eq "150 250"

    # for perfomance
    # require 'benchmark'
    # case_100 = ([100] + 100.times.map { Random.rand(1..450) }).join("\n")
    # $stdin = StringIO.new(case_100)
    # expect( Benchmark.realtime { balanced_tug } ).to be_between(0.0, 5.0)
  end

  it "#10 - Four Boxes " do
    # 직사각형 4개의 면적 구하기, 사각형 좌표를 product로 풀어낸 뒤 uniq좌표 갯수 구하는 방식
    tiles = ->x1,y1,x2,y2 { [*x1...x2].product([*y1...y2]) }
    area = ->rects { rects.reduce([]) {|sum,rect| sum + tiles[*rect]}.uniq.size }
    # one-liner
    # rects.map {|e|[*e[0]...e[2]].product([*e[1]...e[3]])}.flatten(1).uniq.size

    rects = [[1,2,4,4],[2,3,5,7],[3,1,6,5],[7,3,8,6]]
    # special case : x1=x2 or y1=y2
    expect(tiles[1,4,1,5]).to eq []
    expect(tiles[1,2,4,2]).to eq []
    # common case
    expect(tiles[*rects[0]]).to eq [[1,2],[1,3],[2,2],[2,3],[3,2],[3,3]]
    expect(area[rects]).to eq 26
  end
end
<file_sep>class MazeChecker
  attr_reader :maze, :start, :dest

  def set_maze(maze_str)
    @maze = maze_str.split("\n").map(&:chars)
    @maze.each_with_index do |row, idx|
      @start  = [idx, row.index("<")] if row.index("<")
      @dest   = [idx, row.index(">")] if row.index(">")
    end
  end

  def is_road?(x, y)
    (x >= 0 && y >=0) && [" ", ">", "<"].include?(@maze.dig(x,y))
  end

  def next_of(x, y)
    [[1,0],[-1,0],[0,-1],[0,1]].map {|ax,ay| [x+ax, y+ay] }
                               .select {|ex,ey| is_road?(ex, ey) }
  end

  def move_next(x, y)
    @maze[x][y] = "#"
    next_of(x, y)
  end

  def traverse
    queue = [@start]
    until queue.empty?
      cursor = queue.shift
      return true if @maze[cursor.first][cursor.last] == ">"
      queue += move_next(*cursor)
    end
    false
  end
end
<file_sep>describe "Coding Dojang - http://codingdojang.com" do
  it "#91 - Reverse Cards" do
    stdin = proc{ [].tap{|_|10.times{_<<gets.chomp.split.map(&:to_i)}} }
    flip = ->a,b,cards{ cards.insert(a-1,*cards.slice!(a-1..b-1).reverse) }
    flipped_str = ->revs=stdin.(){ revs.reduce([*1..20]){|a,e|flip[*e,a]}*' ' }

    expect( flip.(5, 10, [*1..20]) ).to eq [1,2,3,4,10,9,8,7,6,5,*(11..20)]
    expect( flipped_str.([[1,1],[2,2]]) ).to eq [*1..20]*' '
    expect( flipped_str.((1..10).map {|e|[e,e]}) ).to eq [*1..20]*' '
  end

  it "#92 - 90 degree times" do
    rtime = ->n { h=(6*n-3)/11.0; m=(h-h.to_i)*60; "%2d. %02d:%02d" % [n,h,m] }
    times = ->n=0,r=[] { r << rtime[n+=1] until rtime[n+1][4,2]> '23'; r }
    prt_rt = ->r=times[] { puts r, "Total: #{r.size}번!"}

    expect( rtime.(1 ) ).to eq " 1. 00:16"
    expect( rtime.(44) ).to eq "44. 23:43"
    #=> stdout test
    rt_outs = " 1. 00:16\n 2. 00:49\n 3. 01:21\n 4. 01:54\n 5. 02:27\n 6. 03:00\n"+
              " 7. 03:32\n 8. 04:05\n 9. 04:38\n10. 05:10\n11. 05:43\n12. 06:16\n"+
              "13. 06:49\n14. 07:21\n15. 07:54\n16. 08:27\n17. 09:00\n18. 09:32\n"+
              "19. 10:05\n20. 10:38\n21. 11:10\n22. 11:43\n23. 12:16\n24. 12:49\n"+
              "25. 13:21\n26. 13:54\n27. 14:27\n28. 15:00\n29. 15:32\n30. 16:05\n"+
              "31. 16:38\n32. 17:10\n33. 17:43\n34. 18:16\n35. 18:49\n36. 19:21\n"+
              "37. 19:54\n38. 20:27\n39. 21:00\n40. 21:32\n41. 22:05\n42. 22:38\n"+
              "43. 23:10\n44. 23:43\nTotal: 44번!\n"
    expect { prt_rt.() }.to output(rt_outs).to_stdout
  end

  it "#93 - Duplicate numbers " do
    check_dup = ->nums{ nums.map(&:chars).map {|n| n==n.uniq&&n.size==10} }
    nums = %w(0123456789 01234 01234567890 6789012345 012322456789)
    expect( check_dup.(nums) ).to eq [true, false, false, true, false]
  end

  it "#94 - Max Perfomance" do
    max_pf_with_options = ->part_pows, p_pows=part_pows.sort.reverse do
      base_pow, base_price, p_price = 150, 10, 3
      pf = ->size { (base_pow + p_pows[0,size].reduce(:+)) / (base_price + p_price*size) }
      (1..p_pows.size).reduce([base_pow/base_price]) {|pfs,size| pfs << pf[size]}.max
    end
    expect( max_pf_with_options[ [30, 70, 15, 40, 65] ] ).to eq 17
  end

  it "#95 - H-index & G-index" do
    paper_eval_by_citations = ->str, cs=str.split.map(&:to_i) do
      puts "hidx : #{ cs.select {|h| cs.count {|e|e >= h} == h }.max }"
      puts "gidx : #{ (0..cs.size).select {|g| cs.max(g).reduce(0,:+) >= g**2 }.max }"
    end

    expect { paper_eval_by_citations["0 15 4 0 7 10 0"] }.
                    to output( "hidx : 4\ngidx : 6\n" ).to_stdout
  end

  it "#96 - Printing OXs" do
    ox = ->n=read.to_i { (1..n).map {|i| 'O'*(n-i)+'X'*i } }
    expect( ox.(6) ).to eq ["OOOOOX",
                            "OOOOXX",
                            "OOOXXX",
                            "OOXXXX",
                            "OXXXXX",
                            "XXXXXX"]
  end

  it "#97 - Compare version" do
    ver = ->str { str.split('.').map(&:to_i) }
    compare = ->a,b { [a, "=><"[ver.(a)<=>ver.(b)], b]*' ' }

    expect( compare['0.0.2', '0.0.1'] ).to eq "0.0.2 > 0.0.1"
    expect( compare['1.0.10', '1.0.3'] ).to eq "1.0.10 > 1.0.3"
    expect( compare['1.2.0', '1.1.99'] ).to eq "1.2.0 > 1.1.99"
    expect( compare['1.1', '1.0.1'] ).to eq "1.1 > 1.0.1"
    expect( compare['1.1.0','1.1.0'] ).to eq "1.1.0 = 1.1.0"
    expect( compare['1.1.99','1.2.0'] ).to eq "1.1.99 < 1.2.0"
  end

  it "#98 - Happy Number" do
    _next = ->n { n.to_s.chars.reduce(0){|a,e| a+e.to_i**2} }
    is_happy = ->n,seqs=[] { s=_next[n]; s==1 ? "a Happy" :
                      (seqs.include?(s)? "an UnHappy" : is_happy[s,seqs<<s]) }
    prt_happy = proc { gets.to_i.times {|nth| n=gets.to_i;
                       puts "Case ##{nth}: #{n} is #{is_happy[n]} Number" } }

    expect( _next[15] ).to eq 26
    expect( [4,7,13].map(&is_happy) ).to eq ["an UnHappy", "a Happy", "a Happy"]
    $stdin = StringIO.new("3\n4\n7\n13\n")
    expect { prt_happy.() }.to output("Case #0: 4 is an UnHappy Number\n"+
                                      "Case #1: 7 is a Happy Number\n"+
                                      "Case #2: 13 is a Happy Number\n").to_stdout
  end

  it "#100 - OLHC, Scan word" do
    enq = ->q,m,v { q[m]? q[m][1,3]=[*[*q[m][1,2],v].minmax,v] : q[m]=[*[v]*4]; q }
    olhc = ->s { s.reduce({}) {|q,e| min,_,price=e.scan(/\w+/); enq[q,min,price.to_i] } }
    agg_olhc = ->stocks { %w(open low high close).zip(olhc[stocks].values.transpose) }
    prt_olhc = ->str { agg_olhc[str].each {|tag,prices| puts "#{tag} = #{prices}" } }

    stocks_str = ["1:02, 1100", "1:20, 1170", "1:59, 1090", "2:30, 1030", "2:31, 1110", "2:42, 1150", "2:55, 1210", "2:56, 1190", "3:02, 1120", "3:09, 1100", "4:15, 1090", "4:20, 1080", "4:55, 1050", "4:56, 1020", "4:57, 1000"]
    tagged_olhc = [["open", [1100, 1030, 1120, 1090]], ["low", [1090, 1030, 1100, 1000]], ["high", [1170, 1210, 1120, 1090]], ["close", [1090, 1190, 1100, 1000]]]
    expect( agg_olhc[stocks_str] ).to eq tagged_olhc
    # prt_ohlc.()
  end

end
<file_sep># 코딩도장 수련기 - 수련의 명암

이 글은 [루비 입문자의 수련 경험](http://codingdojang.com/profile/answer/3058) 공유 목적으로 작성되었습니다.

## 1. 수련  
> #### a. 시작
> - 책과 라이브 코딩 영상을 보니 간지난다. 알고싶다 루비.
> - 행사코드 없이 쉽고, 빠르게, 간결하게 코드를 작성하고 싶다.

> #### b. 시행착오
> - 루비를 잘 알고 싶은데 흥미가 생기지 않는다. 책을 읽었는데 돌아서면 잊는다.
> - '까먹지 말자'는 차원에서 문제풀이 사이트에서 한두문제씩 풀기 시작.
> - [프로젝트 오일러](http://euler.synap.co.kr/)에서 시작.
> - 무엇을 모르는지, 무엇에 취약한지, 어떤 해결법이 있는지 피드백이 없어 흥미 급감. 그리고 중단.
> - 3 ~ 4개월 이후, [코딩도장](http://codingdojang.co.m/)에서 다시 시작. 단순히, 다른 사람의 풀이를 볼 수 있어서 선택. 

> #### c. 경과
> - 더듬더듬 쉬운 문제부터 풀기 시작.
> - 조금씩 익숙해지며 짧은 코드, 빠른 코드를 원하게 됨.
> - 각종 모듈과 메소드, 이디엄, [컬렉션 파이프라인](http://martinfowler.com/articles/collection-pipeline), 성능(메소드, 루비 자료구조) 인지.
> - RSpec으로 테스트 작성 시작.
> - 다른 사람, 다른 언어의 풀이들을 보며 인식을 넓힘, 부족한 점 인식.
> - 흥미가 유지됨. 조금씩 자신감이 생기고 관심사가 확장됨. 몇몇 언어에 흥미를 갖게 됨.

> #### d. 정체와 해소
> - 쉬운 문제들을 풀고나니 안 풀리는 문제들만 남고 정체지점에 도달.
> - 풀리지 않는 문제들의 공통점을 뽑아서 몇 가지 취약점을 알게 됨.
> - 괜찮은 풀이를 이해하는 방식으로 취약문제 하나씩 풀어나감.
> - 취약문제가 해소될 때 마다 잘 모르던 알고리즘과 해결법 학습.
> - 많이 나아졌지만 그럼에도 불구하고 여전히 많은 정체가 남음.

## 2. 소고

> #### a. 수련의 밝은 부분
> - 문제풀이는 흥미를 유지시켜주는 좋은 학습법이였다.
> - 반복되는 수련은 좋든 싫든 습관을 빠르게 정착시켜주었다.
> - 풀리지 않는 문제의 공통점을 통해 모르는 것과 취약점을 명확히 인식할 수 있었다.

> #### b. 수련의 어두운 부분
> - 잘못된 수련은 좋지 않은 습관도 빠르게 정착시켰다.(약기 남발)
> - 문제풀이 사이트에서 다루지 않는 복잡한 문제, 복잡한 구성, 구조화는 수련되지 않았다.

## 3. 결론
- 문제풀이는 초보에게 좋은 수련방법이다.
- 평소의 수련중에도 좋은 습관이 정착되도록 노력하는 것이 중요하다.
- 흥미를 유지하고 정체에 도달해서 모르는 것을 인식하고 "모른다"는 불편을 견뎌내면 나아진다.
- 조그마한 문제풀이 사이트의 수련만으로는 부족하다. 보충해 줄 수단도 필요하다.

## Tips
팁 실행해보기
```ruby
#  gem install
cmd > gem install rspec 
cmd > gem install rspec-mocks
cmd > gem install 'rspec-benchmark'

# irb에서 Tips 실행하기 위한 준비
cnd > irb
irb > require 'rspec'
irb > require 'rspec/mocks/standalone'
irb > require 'benchmark'
irb > require 'rspec-benchmark'

irb > extend RSpec::Matchers
irb > include RSpec::Benchmark::Matchers
irb > # 실행하고 싶은 팁 코드를 복사해서 irb에 붙여넣고 실행결과를 확인한다.
```
- [Array#transpose](https://github.com/cavinkwon/ruby_coding_dojang/blob/master/tips/1_transpose.md)
- [Enumerable#chunk](https://github.com/cavinkwon/ruby_coding_dojang/blob/master/tips/2_chunk.md)
- [Stack](https://github.com/cavinkwon/ruby_coding_dojang/blob/master/tips/3_stack.md)
- [Enumerator::lazy](https://github.com/cavinkwon/ruby_coding_dojang/blob/master/tips/4_lazy.md)
- [Array#dig](https://github.com/cavinkwon/ruby_coding_dojang/blob/master/tips/5_dig.md)
- [Matrix](https://github.com/cavinkwon/ruby_coding_dojang/blob/master/tips/6_matrix.md)
- [Some Algorithms](https://github.com/cavinkwon/ruby_coding_dojang/blob/master/tips/7_some_algo_ds.md)
- [Shorthands & Syntatic Sugars](https://github.com/cavinkwon/ruby_coding_dojang/blob/master/tips/8_code_golf.md)
- [Irb with RSpec](https://github.com/cavinkwon/ruby_coding_dojang/blob/master/tips/9_test.md)

## Etc
- 루비가 느리게 느껴지지 않았다.
- 여럿이 같이 수련했더라면 더 좋았겠다.
- 수련 사이트로 [HackerRank](https://www.hackerrank.com/), [코딩도장](https://www.hackerrank.com/) 추천.
<file_sep>require 'byebug'

describe "Coding Dojang - http://codingdojang.com" do
  it "#41 - Every Other Digit" do
    convert = lambda do |str|
      str.chars.map.with_index {|val,i| i.odd? && val.to_i > 0 ? "*" : val }*''
    end
    expect( convert.("a1b2cde3~g45hi6") ).to eq "a*b*cde*~g4*hi6"
  end

  it "#42 - Light More Light" do
    # 어떤 수의 제곱 - 홀수개의 약수(on)
    # 제곱 이외의 수(소수 포함) - 짝수개의 약수(off)
    is_on = ->num { num**0.5 % 1 > 0 ? "no" : "yes" }
    chk_lights = proc { puts gets("0").chop.split.map(&:to_i).map &is_on }

    $stdin = StringIO.new("3\n6241\n8191\n0\n")
    expect{ chk_lights.() }.to output( "no\nyes\nno\n" ).to_stdout
  end

  it "#43 - Optimum polynomial" do
    # 최적의 다항식 찾기
    # n을 입력해서 나온 수열 op[n]을 이용해 차이수열을 만들어낸뒤
    # 차이수열을 이용해 op[n+1] = op.last + inc(증분).last로 bop와 fit를 유추해낸다.
    df = ->arr { arr.each_cons(2).map {|a,b| b-a } }
    get_fit = ->op do
      return op[0] if op.size == 1
      dfs = (2...op.size).reduce([df[op]]) {|a,_| a.unshift df[a[0]] }
      inc = (1..dfs.size).reduce([0,*dfs]) {|a,k| a[k] = a[k][-1] + a[k-1]; a }
      op.last + inc.last
    end

    fit_sum = ->fn { op=[]; (1..10).reduce(0) {|sum,n| sum + get_fit[op << fn[n]] } }
    fn_u = ->n { (0..10).reduce(0) {|value,i| value += (-1)**i * (n**i) } }

    expect(fit_sum[fn_u]).to eq 37076114526
  end

  it "#44 - Ones" do
    ones= ->f,num='1' { num.to_i%f==0 ? num.size : ones[f,num+'1'] }
    expect( [3,7,9901].map(&ones) ).to eq [3,6,12]
  end

  it "#45 - Reverse And Add" do
    rev_add = ->n { [n,n.reverse].map(&:to_i).reduce :+ }
    pal = ->n,cnt=0 { n==n.reverse ? [cnt,n]*' ' : pal[rev_add[n].to_s, cnt+1] }
    prt_pal = proc { (1..gets.to_i).map {gets.to_i}.each {|e| p pal[e.to_s] } }

    expect( rev_add.("195") ).to eq 786
    expect( pal.("195") ).to eq "4 9339"
    expect( pal.("1") ).to eq "0 1"
    expect( %w(1 195 265 750).map(&pal) ).to eq ["0 1", "4 9339",
                                                 "5 45254", "3 6666"]
  end

  it "#46 - Diamond" do
    # daum devon code golf
    # a = %w(5*5 5*5 4*1*4 3*3*3 2*5*2)
    # puts (a+%w(**7**)+a.reverse).map {|e|e.chars.map {|c|c>"*" ? " "*c.to_i : c}*''}

    diamond = "     *     \n" +
              "     *     \n" +
              "    * *    \n" +
              "   *   *   \n" +
              "  *     *  \n" +
              "**       **\n" +
              "  *     *  \n" +
              "   *   *   \n" +
              "    * *    \n" +
              "     *     \n" +
              "     *     \n"
    prt_diamond = proc do
      a = %w(5*5 5*5 4*1*4 3*3*3 2*5*2)
      puts (a+%w(**7**)+a.reverse).map {|e|e.chars.map {|c|c>"*" ? " "*c.to_i : c}*''}
    end
    expect( prt_diamond ).to output(diamond).to_stdout
  end

  it "#47 - Check the Check" do
    # k에서 뻗어나가는 직선,사선에 있는 말들을 문자열로 만든뒤 in_check여부 판단
    check_incheck = ->chess_board,x,y,kv do
      v = ->ex,ey { ex<0 || ey<0 ? nil : chess_board.dig(ex,ey) }
      s_rq = [*0..7].map {|d| [v[x-d,y], v[x+d,y], v[x,y-d], v[x,y+d]] }.transpose.map(&:join)
      s_bq = [*0..7].map {|d| [v[x-d,y-d], v[x-d,y+d], v[x+d,y+d], v[x+d,y-d]] }.transpose.map(&:join)
      s_p = s_bq[(kv=="K"?0:2),2].map {|s| s[1,2] }
      s_n = (t=[1,-1].product([2,-2]); (t+t.map(&:reverse)).map {|dx,dy| v[x+dx,y+dy]}.compact)

      rq, bq, rp, rn = [%w(kR kQ), %w(kB kQ), "P", "N"]
      ([rq,bq].map {|e|e.map(&:swapcase!)}; [rp,rn].map(&:swapcase!)) if kv == "K"
      is_incheck = rq.any? {|s| s_rq.include?(s)} | s_p.any? {|s| rp == s } |
                   bq.any? {|s| s_bq.include?(s)} | s_n.any? {|s| rn == s }
    end

    result = ->nth,game do
      is_k = ->x,y { game[x][y].upcase == "K" ? [x,y,game[x][y]] : nil }
      kings = [*0..7].product([*0..7]).map(&is_k).compact
      checkmate = kings.map {|x,y,v| check_incheck[game,x,y,v] }
      winner = checkmate[0]? "black" : checkmate[1]? "white" : "no"
      "Game ##{nth} : #{winner} king is in check."
    end

    check_chess_games = proc do
      games, tmp = [], ["default"]
      until tmp.join.empty?
        tmp = (0..7).map { gets.chop.chars.map {|c| c == "." ? "" : c } }
        tmp.join.empty? ? break : games.tap {|boards| boards << tmp; puts "" }
      end
      puts games.map.with_index(1) {|game,nth| result[nth, game] }
    end

    stdin_games = "..k.....\n" +
              "ppp.pppp\n" +
              "........\n" +
              ".R...B..\n" +
              "........\n" +
              "........\n" +
              "PPPPPPPP\n" +
              "K.......\n" +

              "rnbqk.nr\n" +
              "ppp..ppp\n" +
              "....p...\n" +
              "...p....\n" +
              ".bPP....\n" +
              ".....N..\n" +
              "PP..PPPP\n" +
              "RNBQKB.R\n" +

              "........\n" +
              "........\n" +
              "........\n" +
              "........\n" +
              "........\n" +
              "........\n" +
              "........\n" +
              "........\n"
    game_result = "\n\n" +  "Game #1 : black king is in check.\n" +
                            "Game #2 : white king is in check.\n"
    $stdin = StringIO.new(stdin_games)
    expect{ check_chess_games.() }.to output( game_result ).to_stdout
  end

  it "#48 - An Easy Problem " do
    ones = ->n { n.to_s(2).chars.count('1') }
    find_j = ->n,ones_i=ones[n] { n+=1; ones[n]==ones_i ? n:find_j[n,ones_i] }
    stdin = proc { (readline('0').split(/\n/) - ["","0"]).map(&:to_i) }
    prt = proc { stdin[].map(&find_j) }

    expect( [1,2,3,4,78].map(&find_j) ).to eq [2,4,5,8,83]
    $stdin = StringIO.new("1\n2\n3\n4\n78\n0")
    expect { puts prt.() }.to output("2\n4\n5\n8\n83\n").to_stdout
  end

  it "#49 - Ugly Number" do
    primes, steps = [2,3,5], [[1],[1],[1]]
    make_steps = lambda do |ugly|
      steps.each_with_index {|val,i| val.shift if val[0]==ugly; val<<ugly*primes[i]}
    end
    nth_ugly = ->n=1500 { (0..n).reduce{steps.map(&:first).min.tap(&make_steps)} }

    # expect( nth_ugly.() ).to eq 859963392
    # expect( nth_ugly.(1550) ).to eq 1093500000
    expect( nth_ugly.(100_000) ).to eq 290142196707511001929482240000000000000
  end

  it "#50 - Euclid Problem" do
    # 알고리즘 : extended euclid, bezout, modular invert
    # ax + by = gcd(최대공약수), s,t는 i-1번째 a,b추적을 위한 변수
    # gcd,q,r = r,*gcd.divmod(r) r이 0일때 gcd는 최대공약수. gcd,r초기값은 x,y.
    #                            따라서 별도 변수 필요없이 x를 gcd, y를 나머지값으로 활용
    # a[i],s[i] = s[i-1], a[i-1]-q[i]*s[i-1] 초기값 a,s = 0,1
    # b[i],t[i] = t[i-1], b[i-1]-q[i]*t[i-1] 초기값 b,t = 1,0
    bz = lambda do |x,y|
      a,s,b,t = 1,0,0,1
      (x,q,y=y,*x.divmod(y); a,s,b,t=s,a-q*s,t,b-q*t) until y.zero?; [a,b,x]
    end

    expect( bz.(4, 6) ).to eq [-1, 1, 2]
    expect( bz.(17, 17) ).to eq [0, 1, 17]
    expect( bz.(1071,1029) ).to eq [-24, 25, 21]
    expect( bz.(78696,19332) ).to eq [212, -863, 36]

    # gcd = ->x,y { x,y = y, x%y until y.zero?; x.abs }
    # gcd = ->x,y { y.zero? ? x.abs : gcd[y,x%y] }
=begin
    def bezout(x, y)
      gcd,r=x,y;s=b=0;t=a=1;
      print "[before] : #{gcd}, 0, #{r} / #{a},#{s} / #{b},#{t} \n"
      until r.zero? do
        gcd, q, r = r, *gcd.divmod(r) # gcd와 r은 최대공약수를 위해서, q는 a,b계산을 위해
        a, s = s, a-q*s # 전단계 q(나눔수)
        b, t = t, b-q*t
        print "[after] : #{gcd}, #{q}, #{r} / #{a},#{s} / #{b},#{t} \n"
      end
      [a, b, gcd]
    end
=end
  end

end
<file_sep>require 'maze_checker'

describe "maze_checker" do
  let (:checker) { MazeChecker.new }

  subject (:maze1) { maze1 =  "<     >\n" }
  subject (:maze2) { maze2 =  "########\n" +
                              "#<     #\n" +
                              "#  ##  #\n" +
                              "#  ##  #\n" +
                              "#     >#\n" +
                              "########\n"  }
  subject (:maze3) { maze3 =  "#######\n" +
                              "#<    #\n" +
                              "##### #\n" +
                              "#     #\n" +
                              "# #####\n" +
                              "# #   #\n" +
                              "# # # #\n" +
                              "#   #>#\n" +
                              "#######\n"  }
  subject (:maze4) { maze4 =  "<   #   >\n" }
  subject (:maze5) { maze5 =  "########\n" +
                              "#<     #\n" +
                              "#     ##\n" +
                              "#    #>#\n" +
                              "########\n"  }
  subject (:maze6) { maze6 =  "#< #  #\n" +
                              "#  #  #\n" +
                              "#  # >#\n" }

  it ".set_maze - recieve maze" do
    checker.set_maze(maze1)
    expect( checker.maze.size ).to eq 1
    checker.set_maze(maze2)
    expect( checker.maze.size ).to eq 6
  end

  it ".is_road? - sense start, dest, wall, road" do
    checker.set_maze(maze1)
    expect( [checker.start, checker.dest] ).to eq [[0,0],[0,6]]
    expect( checker.is_road?(1, 0) ).to eq false
    expect( checker.is_road?(0,-1) ).to eq false
    expect( checker.is_road?(*checker.start) ).to eq true
    expect( checker.is_road?(*checker.dest) ).to eq true
    checker.set_maze(maze2)
    expect( [checker.start, checker.dest] ).to eq [[1,1],[4,6]]
    expect( checker.is_road?(0, 0) ).to eq false
    expect( checker.is_road?(5, 7) ).to eq false
    expect( checker.is_road?(*checker.dest) ).to eq true
  end

  it ".move_next - sence nexts, move nexts" do
    checker.set_maze(maze1)
    expect( checker.next_of(*checker.start) ).to eq [[0,1]]
    expect( checker.next_of(*checker.dest) ).to  eq [[0,5]]
    expect( checker.next_of(0, 1) ).to eq [[0,0],[0,2]]
    expect( checker.move_next(*checker.start) ).to eq [[0,1]]
    expect( checker.next_of(0, 1) ).to eq [[0,2]]
  end

  context "traversable case" do
    it ".traverse - traverse all, is_traversable_maze?" do
      checker.set_maze(maze1)
      expect( checker.traverse ).to eq true
      checker.set_maze(maze2)
      expect( checker.traverse ).to eq true
      checker.set_maze(maze3)
      expect( checker.traverse ).to eq true
    end
  end

  context "not traversable case" do
    it ".traverse - traverse all, is_traversable_maze?" do
      checker.set_maze(maze4)
      expect( checker.traverse ).to eq false
      checker.set_maze(maze5)
      expect( checker.traverse ).to eq false
      checker.set_maze(maze6)
      expect( checker.traverse ).to eq false
    end
  end
end

# bfs algorithm
# 깊이 0인 1을 넣는다.
# q에 들어있는 1(깊이 0 )로  인접한 깊이 1(2 3)을 구해서 넣는다. 1을 빼낸다"
# q에 들어있는 2(깊이 1 )로  인접한 깊이 2(4,5)를 구해서 넣는다. 2를 빼낸다.
# q에 들어있는 3(깊이 1 )로  인접한 깊이 2(4,6)을 구해서 넣는다. 3을 빼낸다.
#                                       4는 이미 들어있으므로 넣지 않는다.
# q에 들어있는 4(깊이 2 )로  인접한 깊이 3(없음)을 구해서 넣는다. 4를 빼낸다.
# q에 들어있는 5(깊이 2 )로  인접한 깊이 3(없음)을 구해서 넣는다. 5를 빼낸다.
# q에 들어있는 6(깊이 2 )로  인접한 깊이 3(없음)을 구해서 넣는다. 6을 빼낸다.
# q가 비워지거나(false) 목표지에 도달(true)하면 종료한다.
<file_sep>describe "Coding Dojang - http://codingdojang.com" do
  it "#61 - Bubble Sort" do
    swap = ->a,i,cnt=0 do
      ( a[i],a[i+1]=a[i+1],a[i]; cnt+=1 ) if a[i]>a[i+1]; [cnt, a]
    end
    stage = ->cnt,a { (0..a.size-2).each {|i| cnt,a=swap[a,i,cnt]}; [cnt,a] }
    b_sort = ->arr do
      loops,swaps,cnt = 0,0,-1;
      (cnt,arr=stage[0,arr]; loops+=1; swaps+=cnt) until cnt==0; [loops,swaps]
    end

    expect( swap.( [2,1,3],0) ).to eq [1,[1,2,3]] # case : swap
    expect( swap.( [1,2,3],1) ).to eq [0,[1,2,3]] # case : sorted
    expect( stage.( 0,[2,1,5,4] ) ).to eq [2,[1,2,4,5]] # case : swap
    expect( stage.( 0,[1,2,4,5] ) ).to eq [0,[1,2,4,5]] # case : sorted
    expect( b_sort.( [3,1,4,1,5,9,2,6] ) ).to eq [5,8]
  end

  it "#62 - Two printers" do
    time = ->x,y,n { t=(n*x*y*1.0/(x+y)).ceil;  t%x+t%y > 0? t+1 : t }
    rcv = proc { puts "input data:"; (1..gets.to_i).map { gets.split.map(&:to_i) } }
    prt_times = ->input=rcv[] { puts "\nanswer:", input.map(&time)*' ' }

    $stdin = StringIO.new("2\n1 1 5\n3 5 4\n")
    expected = "input data:\n" +
               "\n" +
               "answer:\n" +
               "3 9\n"
    expect { prt_times.call }.to output( expected ).to_stdout
  end

  it "#63 - Largest prime factor" do
    largest_prime = ->num,f=2 { num%f==0 ? num=num/f : f=f+1 until num==f; f }
    expect( largest_prime.(600851475143) ).to eq 6857
  end

  it "#64 - Right triangle maximum circumference" do
    # a+b < c 일 때. a * (2a-p) < 0. 따라서 0 < a < p/2, 0 < b <= a, c = sqrt(a^2 + b^2)
    find_circum = ->p_len,circums=Hash.new(0) do
      cnt = ->a,b,c { circums[a+b+c] += 1 if (a+b+c <= p_len) && (c%1 == 0) }
      (1...p_len/2).each {|a| (1..a).each {|b| cnt[a,b,(a**2+b**2)**0.5] } }
      circums.max_by(&:last)
    end

    expect( find_circum[120] ).to eq [120, 3]
    expect( find_circum[1000] ).to eq [840, 8]
  end

  it "#65 - XOR descryption" do
    cnter, key = Hash.new(0), []
    enc_s = IO.read("spec/data/cipher.txt").split(/,/).map &:to_i
    enc_s.each_slice(3) {|_| _.each_with_index {|c,i|
      cnter[[i,c]] += 1
      key[i] = c if cnter[[i,c]] > cnter[[i,key[i]]] } }
    key = key.map {|k| k^" ".ord }
    ascii_sum = enc_s.map.with_index {|v,i| v^key[i%3] }.reduce :+
    expect( ascii_sum ).to eq 107359
  end

  it "#66 - Icecream factory" do
    min_box = ->tpts { tpts.sort.reduce([]) {|a,t| (a.flatten&t).empty?? a<<t :
                                      a.map {|box| (box&t).empty?? box : box&t }} }
    case1 = [[*-20..-15],[*-14..-5],[*-18..-13],[*-5..-3]]
    case2 = [[*-10..0], [*-10..0],[*-1..0],[*-15..-2],[*-15..-2],[*-15..-14]]
    case3 = [[*-20..-18],[*-20..-5],[*-15..-2],[*-19..-4],[*-4..-1],[*-10..-3]]
    case4 = [[*-20..-15],[*-14..-5],[*-18..-13],[*-5..-3],[-2,-1],[*-7..-2]]
    case5 = [[*-10..-5],[*-4..0],[*-11..-11]]

    expect( min_box[case1] ).to eq [[-18, -17, -16, -15], [-5]]
    expect( min_box[case2] ).to eq [[-15, -14], [-1, 0]]
    expect( min_box[case3] ).to eq [[-19, -18], [-4, -3]]
    expect( min_box[case4] ).to eq [[-18, -17, -16, -15],[-5], [-2, -1]]
    expect( min_box[case5] ).to eq [[-11], [-10, -9, -8, -7, -6, -5], [-4, -3, -2, -1, 0]]
  end

  it "#67 - Simple Balanced Parentheses" do
    is_balanced = ->str { str.scan(/[()]/).reduce("v") {|a,e| e=='('? a+e : a.chop} == "v" }

    # test data
    test_str = "(5+6)∗(7+8)/(4+3)"
    balanced = %w[ (()()()()) (((()))) (()((())())) ]
    not_balanced = %w[ ((((((()) ())) (()()(() (()))( ())(() ]
    # test case
    expect( is_balanced[test_str] ).to eq true
    expect( balanced.map(&is_balanced) ).to eq [true]*3
    expect( not_balanced.map(&is_balanced) ).to eq [false]*5
  end

  it "#68 - Decimal to to_s(n)" do
    rest = "0123456789ABCDEF"
    to_n = ->num,f { num<f ? rest[num] : to_n[num/f,f]+rest[num%f] }

    expect( to_n[233, 2] ).to eq "11101001"
    expect( to_n[233, 8] ).to eq "351"
    expect( to_n[233,16] ).to eq "E9"
  end

  it "#69 - Fibonacci. smaller than n " do
    fib = ->x{ x<2 ? x:fib[x-2]+fib[x-1] }
    fibs = ->n { (0..Float::INFINITY).lazy.map(&fib).take_while{|e|e<n}.force }
    expect( fibs[100000].size ).to eq 26
    expect( fibs[100000].last ).to eq 75025
  end

  it "#70 - fibonacci2" do
    require 'matrix'
    fibs = ->n do
      n-=1; lim=4294967291; v=m=Matrix[[1,1],[1,0]]
      (m,v=(m**2).map{|_|_%lim},n&1==1?(v*m).map{|_|_%lim}:v; n>>=1) until n<1
      v[1,1]
    end

    expect( fibs[5] ).to eq 3
    expect( fibs[1000000000000000] ).to eq 3010145777
    expect( Benchmark.realtime { fibs[10**4] } ).to be_between(0.0, 1.0)
  end

end
<file_sep>describe "Coding Dojang - http://codingdojang.com" do
  it "#81 - Mow grass" do
    row =  ->y { gets.split.map.with_index {|h,x| [y,x,h] } }
    lawns = proc { (1..gets.to_i).map { (1..gets.to_i).flat_map(&row) } }
    clean = ->m { m.any? {|ex,ey,ev| m.any? {|x,_,v|x==ex && v>ev} &&
                                     m.any? {|_,y,v|y==ey && v>ev} } ? "NO" : "YES" }
    mown = ->lawns { puts lawns.map.with_index {|m,n| "Case ##{n+1}: #{clean[m]}" } }

    case_yes = [[0, 0, "1"], [0, 1, "2"], [0, 2, "1"]]
    case_no  = [[0, 0, "1"], [0, 1, "2"], [1, 0, "2"], [1, 1, "1"]]
    expect( clean[case_yes] ).to eq "YES"
    expect( clean[case_no ] ).to eq "NO"

    stdin_lawns = "3\n" +
                  "3 3\n" +
                  "2 1 2\n" +
                  "1 1 1\n" +
                  "2 1 2\n" +
                  "5 5\n" +
                  "2 2 2 2 2\n" +
                  "2 1 1 1 2\n" +
                  "2 1 2 1 2\n" +
                  "2 1 1 1 2\n" +
                  "2 2 2 2 2\n" +
                  "1 3\n" +
                  "1 2 1\n"
    expected =  "Case #1: YES\n" +
                "Case #2: NO\n" +
                "Case #3: YES\n"
    $stdin = StringIO.new(stdin_lawns)
    expect{ mown[lawns[]] }.to output( expected ).to_stdout
  end

  it "#82 - Number Baseball" do
    cnt = ->sb,a,b { sb[0]+=1 if a==b; sb[1]+=1 if a[0]==b[0] && a[1]!=b[1]; sb }
    valid = ->n1,n2,st,ba do
      ai, bi = [n1, n2.chars].map {|e| e.map.with_index.to_a }
      ai.product(bi).reduce([0,0]) {|sb,(a,b)| cnt[sb,a,b]} == [st.to_i, ba.to_i]
    end

    guesses = proc { _,*tail = gets.split; tail.each_slice(3).to_a }
    matched = proc {|all,guess| all.select {|e| valid[e, *guess] } }
    answers = proc { p guesses[].reduce([*"1".."9"].permutation(3), &matched).size }

    $stdin = StringIO.new("4 123 1 1 356 1 0 327 2 0 489 0 1\n")
    expect{ answers.() }.to output("2\n").to_stdout
  end

  it "#83 - Investment in stocks" do
    income = proc { prn, _, *per = $stdin.read.split.map(&:to_i)
                    (per.reduce(prn) {|sum,p| sum*(p*0.01+1) } - prn).round }
    stat = ->incom { s=(-1..1).zip(%w(bad same good)).to_h; [incom, s[incom<=>0]] }

    #=> stdin/out test
    $stdin = StringIO.new("10000\n4\n10 -10 5 -5\n")
    expect { puts stat[income.call] }.to output("-125\nbad\n").to_stdout
  end

  it "#84 - JSON extraction" do
    # json 배열 획득 > set에 입력 > 버퍼별로 set에 새로 추가된 요소를 map.
    require 'set'
    rcved = ->buffers,sets=Set.new do
      buffers.map {|s| s.gsub(/{(.|^})+?}/).to_a.delete_if {|e| !sets.add?(e) } }
    end

    buffers = [ '[ { "seq" : 0, "content" : "abcd"',
                '[ { "seq" : 0, "content" : "abcd" },
                   { "seq" : 1, "content" : "d"',
                '[ { "seq" : 0, "content" : "abcd" },
                   { "seq" : 1, "content" : "dsfswde" },
                   { "seq" : 2, "content"' ]
    expected = [ [],
                 ['{ "seq" : 0, "content" : "abcd" }'],
                 ['{ "seq" : 1, "content" : "dsfswde" }'] ]
    expect( rcved[buffers] ).to eq expected
  end

  it "#86 - Ascii art N" do
    art = ->n=gets.to_i { (1..n).map {|i| a="N".rjust(i).ljust(n); a[0]=a[-1]="N"; a} }
    expect( art.(1) ).to eq ["N"]
    expect( art.(3) ).to eq ["N N",
                             "NNN",
                             "N N"]
    expect( art.(5) ).to eq ["N   N",
                             "NN  N",
                             "N N N",
                             "N  NN",
                             "N   N"]
  end

  it "#89 - Paint image" do
    # BFS algorithms & Matrix
    paint_image = proc do
      xy, start = (1..2).map { gets.split.map(&:to_i) }
      image = Matrix[*(1..xy[0]).map { gets.chars[0, xy[1]] }]
      q, find_color, paint = [start[0,2]], image[*start[0,2]].to_s, start[2].to_s

      cell = ->x,y { x>=0 && y>=0 && image[x,y] == find_color }
      next_of = ->x,y { [[1,0],[-1,0],[0,-1],[0,1]].map {|r,c| [x+r,y+c] }.select &cell }
      nexts = ->x,y { image.send(:[]=, x, y, paint); next_of[x,y] }
      q += nexts[*q.shift] until !q[0]
      puts image.to_a.map(&:join)
    end

    $stdin = StringIO.new("10 10\n" +
                      "5 5 3\n" +
                      "0000000000\n" +
                      "0000001000\n" +
                      "0000110100\n" +
                      "0011000010\n" +
                      "0100000010\n" +
                      "0100000010\n" +
                      "0100000100\n" +
                      "0010001000\n" +
                      "0001011000\n" +
                      "0000100000\n")
    expect { paint_image[] }.to output("0000000000\n" +
                                       "0000001000\n" +
                                       "0000113100\n" +
                                       "0011333310\n" +
                                       "0133333310\n" +
                                       "0133333310\n" +
                                       "0133333100\n" +
                                       "0013331000\n" +
                                       "0001311000\n" +
                                       "0000100000\n").to_stdout
  end

  it "#90 - CamelCase to PotholeCase" do
    to_pathole = ->camel { camel.gsub(/([A-Z0-9])/,'_\1').downcase }
    # pc = ->s {s.chars.reduce([]) {|a,e| a<<(e==e.upcase ? "_"+e.downcase : e)}*''}
    expect( to_pathole.("codingDojang") ).to eq "coding_dojang"
    expect( to_pathole.("numGoat30") ).to eq "num_goat_3_0"
  end

end
<file_sep>describe "Coding Dojang - http://codingdojang.com" do
  it "#121 - Trucks : ACM 2016" do
    def min_times
      (_,w,l),trucks = (1..2).map {gets.split.map &:to_i}
      add = ->b,c,t { b.shift; b.reduce(:+) <= l-t ? [b<<t, c+1] : add[b<<0, c+1, t] }
      puts trucks.reduce([[0]*w,0]) {|a,truck| add[*a,truck] }[1] + w
    end

    # stdin datas
    case1 = "4 2 10\n7 4 5 6\n"
    case2 = "1 100 100\n10\n"
    case3 = "10 100 100\n10 10 10 10 10 10 10 10 10 10\n"
    $stdin = StringIO.new(case1 + case2 + case3)

    expect{ min_times }.to output("8\n").to_stdout   # case 1
    expect{ min_times }.to output("101\n").to_stdout # case 2
    expect{ min_times }.to output("110\n").to_stdout # case 3
  end

  it "#122 - Lottery Game" do
    def lottery_game
      (n, sum), nums = 2.times.map { gets.split.map(&:to_i) }
      (1..n).flat_map {|i| nums.combination(i).select {|e| e.reduce(:+) == sum } }
            .map {|e| puts e.sort.join(' ') }
    end

    $stdin = StringIO.new("10 50\n25 27 3 12 6 15 9 30 21 19\n")
    expect { lottery_game }.to output("6 19 25\n").to_stdout

    # conditional combination
    def lottery_game_fast
      (n, sum), nums = 2.times.map { gets.split.map(&:to_i) }
      comb = ->head,rest,out do
        return out if (head + rest).empty?
        if rest.empty?
          head.sum == sum ? out << head : out
        else
          comb[head +[rest[0]], rest[1..-1], out] if head.sum(0) <= sum - rest[0]
          (head + rest[1..-1]).sum(0) >= sum ? comb[head, rest[1..-1], out] : out
        end
      end

      puts comb[[],nums,[]].map {|e| e.sort.join(' ') }
    end
  end

  it "#124 - Lisp Computer(s-exp +-*/)" do
    # 1. lambda eval way
    calc = proc {|op,*args| args.reduce(op) || 0 }
    lisp_eval = ->exp { eval exp.gsub(/[(\s)]/, '('=>"calc[:", ' '=>',', ')'=>']') }
    # 2. stack way
    calc = proc {|op,*args| args.reduce(op) || 0 }
    eval_top = ->s { t=[]; t.unshift(s.pop) until t[0].is_a? Symbol; s << calc[*t] }
    lisp_eval = ->str do
      tokens = str.gsub(/[()]/, "("=>"",")"=>" )").split.
                   map {|e| e == ")" ? e : e =~ /[[:digit:]]/ ? e.to_i : e.to_sym }
      tokens.reduce([]) {|stack,e| e == ")" ? eval_top[stack] : stack << e }.pop
    end
    # 3. stack way shorthands
    calc = proc {|op,*args| args.reduce(op) || 0 }
    eval = ->s { t=[]; t.unshift(s.pop) until t[0].is_a? Symbol; s << calc[*t] }
    push = ->s,e { e == ")" ? eval[s] : s << (e =~ /[[:digit:]]/ ? e.to_i : e.to_sym) }
    lisp_eval = ->s { s.gsub(/[()]/, "("=>"",")"=>" )").split.reduce([], &push).pop }

    cases = ["(+)"] +
        ["(- 10 3)", "(* 2 3)"] +
        ["(- 10 3 5)", "(* 2 3 4)"] +
        ["(* (+ 2 3) (- 5 3))", "(/ (+ 9 1) (+ 2 3))"] +
        ["(* 1 (- 2 3) 4 (+ 2 -1) 3)" ]
    expect( cases.map(&lisp_eval) ).to eq [0, 7, 6, 2, 24, 10, 2, -12]
  end

  it "#125 - Binary Tree - Layout 1" do
    add = ->par,node do
      par ? node.y = par.y + 1 : (return node)
      chd = node.val < par.val ? par.l : par.r
      chd ? add[chd, node] : (node.val < par.val ? par.l = node : par.r = node)
      par
    end

    layout = ->btree_str=gets.chop,cnt=0 do
      Node = Struct.new(:val, :x, :y, :l, :r)
      nodes = btree_str.chars.map {|c| Node.new(c, 0, 1)}
      btree = nodes.reduce(&add)

      trav = ->node do
        trav[node.l] if node.l
        node.x = cnt+=1
        puts node.values[0..2].join(" ")
        trav[node.r] if node.r
      end
      trav[btree]
    end

    in_ordered = "a 1 4\n" + "c 2 3\n"  + "e 3 6\n"  + "g 4 5\n" +
                 "h 5 4\n" + "k 6 2\n"  + "m 7 3\n"  + "n 8 1\n" +
                 "p 9 3\n" + "q 10 5\n" + "s 11 4\n" + "u 12 2\n"
    expect{ layout["nkcmahgeupsq"] }.to output(in_ordered).to_stdout
  end

  it "#126 - Binary Tree - Layout 2" do
    add = ->par,node do
      par ? node.y = par.y + 1 : (return node)
      chd = node.val < par.val ? par.l : par.r
      chd ? add[chd, node] : (node.val < par.val ? par.l = node : par.r = node)
      par
    end

    calc_x = ->node,par,max_y do
      left_x = par.x + 2**(max_y-node.y) * (node == par.l ? -1 : 1) if par
      node.x = (node.l ? node.l.x + 2**(max_y-node.y-1) : par&.x > 0 ? left_x : 1)
      node.r.x = node.x + 2**(max_y-node.y-1) if node.r
    end

    layout = ->tree_str=gets.chop do
      Node = Struct.new(:val, :x, :y, :l, :r)
      nodes = tree_str.chars.map {|c| Node.new(c, 0, 1) }
      btree, max_y = nodes.reduce(&add), nodes.max_by(&:y).y

      trav = ->node,par=nil do
        trav[node.l, node] if node.l
        calc_x[node, par, max_y]
        puts node.values[0..2].join(" ")
        trav[node.r, node] if node.r
      end
      trav[btree]
    end

    in_ordered =  "a 1 4\n"  + "c 3 3\n"  + "d 4 5\n"  + "e 5 4\n"  +
                  "g 6 5\n"  + "k 7 2\n"  + "m 11 3\n" + "n 15 1\n" +
                  "p 19 3\n" + "q 21 4\n" + "u 23 2\n"
    expect{ layout["nkcmaedgupq"] }.to output(in_ordered).to_stdout
  end
end
<file_sep>describe "Coding Dojang - http://codingdojang.com" do
  it "#71 - Find numbers" do
    # Reduce로 카테시안 프로덕트 재귀처리.
    # ex) 프로덕트n = 프로덕트(프로덕트n-1,n) #=> 가령, 3까지의 조합은 cases[3] = cases[cases[2],3]
    cases = ->a,b,op=['+','-',''] { [*a].product(op,[b]).map(&:join) }
    case_cnt = ->n { (2..9).reduce(1, &cases).select {|e| eval(e)==n } }
    # 두 수의 조합, reduce에 의한 recursion, count 확인
    expect(cases[1,2]).to eq %w(1+2 1-2 12)
    cases_123_result = %w(1+2+3 1+2-3 1+23 1-2+3 1-2-3 1-23 12+3 12-3 123)
    expect(cases[cases[1,2],3]).to eq cases_123_result

    expect(case_cnt[100].size).to eq 11
    # one-liner
    # (2..9).reduce(1){|a,e|[*a].product(['+','-',''],[e]).map &:join}.count{|e|eval(e)==100}
  end

  it "#72 - tic-tac-toe game" do
    made = ->u { %w(123 456 789 147 258 369 159 357).any? {|e| (e.chars&u)[2] } }
    prt = ->w,m { puts "[Winner: #{w}]", m.each_slice(3).map {|row| row*" " } }
    play = ->p1=[],p2=[],map=[*1..9],n=gets.chop do
      map[n.to_i-1] = (p1|p2).size.odd? ? (p2<<n; "X") : (p1<<n; "O")
      winner = made[p1]? "P1" : made[p2]? "P2" : ("None" if (p1+p2)[8])
      winner ? prt[winner,map] : play[p1,p2,map]
    end

    expect_p1_win = "[Winner: P1]\n" +
                    "O 2 3\n" +
                    "X O 6\n" +
                    "7 X O\n"
    expect_p2_win = "[Winner: P2]\n" +
                    "O O X\n" +
                    "4 X X\n" +
                    "X O O\n"
    expect_draw   = "[Winner: None]\n" +
                    "X O O\n" +
                    "O O X\n" +
                    "X X O\n"
    # for stdin
    test_data = ->p1,p2 { StringIO.new(p1.zip(p2).flatten*"\n") }

    # case : P1 Win
    $stdin = test_data[[9,5,1], [4,8]]
    expect{ play[] }.to output( expect_p1_win ).to_stdout

    # case : P2 Win
    $stdin = test_data[[1,2,9,8,4], [3,6,7,5]]
    expect{ play[] }.to output( expect_p2_win ).to_stdout

    # case : draw
    $stdin = test_data[[4,5,9,2,3], [8,7,6,1]]
    expect{ play[] }.to output( expect_draw ).to_stdout
  end

  it "#73 - Compress String" do
    compress = ->str { str.gsub(/((\w)\2*)/) { $2+$1.size.to_s } }
    expect( compress.("aaabbcccccca") ).to eq "a3b2c6a1"
  end

  it "#74 - Count zeros" do
    cnt_zeros_factorial = ->n { n<5? 0 : n/5 + cnt_zeros_factorial[n/5] }
    expect( cnt_zeros_factorial.(12) ).to eq 2
    expect( cnt_zeros_factorial.(25) ).to eq 6
  end

  it "#75 - <NAME>" do
    discnt = ->pack { [0, 1, 0.95, 0.9, 0.8, 0.75][pack.size] * 8 * pack.size }
    price = ->cart { cart.empty? ? 0 : cart.map(&discnt).reduce(:+) }
    min_cart = ->cart,book do
      make_cases = ->i,t=cart.dup { t[i]+=[book]; t if t.all? {|_|_.size==_.uniq.size} }
      ((0...cart.size).map(&make_cases).compact << cart+[[book]]).min_by &price
    end
    min_price = ->books,cart=[],book=books.shift do
      book ? min_price[books, min_cart[cart,book]] : price[cart]
    end

    no_discnt_cases = [[], [0], [0,0]]
    simple_cases    = [[0,1], [0,2,4]]
    several_cases   = [[0,0,1], [0,0,1,1], [0,0,1,2,2,3]]
    edge_cases      = [ [0,0,1,1,2,2,3,4],  [0, 0, 0, 0, 0,
                                             1, 1, 1, 1, 1,
                                             2, 2, 2, 2,
                                             3, 3, 3, 3, 3,
                                             4, 4, 4, 4] ]

    expect( no_discnt_cases.map(&min_price) ).to eq [8*0, 8*1, 8*2]
    expect( simple_cases.map(&min_price) ).to  eq [(8 * 2 * 0.95), (8 * 3 * 0.9)]
    expect( several_cases.map(&min_price) ).to eq [(8 * 2 * 0.95) + (8 * 1 * 1.0),
                                                   (8 * 2 * 0.95) * 2,
                                                   (8 * 2 * 0.95) + (8 * 4 * 0.8)]
    expect( edge_cases.map(&min_price) ).to eq [(8 * 4 * 0.8) * 2,
                                                (8 * 5 * 0.75) * 3 +
                                                (8 * 4 * 0.8 ) * 2 ]
  end

  it "#76 - Morse translation" do
    DIC = %w(.- -... -.-. -.. . ..-. --. .... .. .--- -.- .-.. -- -. --- .--.
             --.- .-. ... - ..- ...- .-- -..- -.-- --..).zip('a'..'z').to_h
    translate = ->morse { morse.split('  ').map {|w| w.split.map(&DIC)*'' }*' ' }

    test_morse_str = ".... .  ... .-.. . . .--. ...  . .- .-. .-.. -.--"
    expect( translate.(test_morse_str) ).to eq "he sleeps early"
  end

  it "#77 - Life Game" do
    prev_state = ->s,current=s.chars.map(&:to_i) do
      seeds = [[0,0],[0,1],[1,0],[1,1]]
      gen = ->seed { current.reduce(seed) {|a,c| a << (a[-2] ^ a[-1 ] ^ c) }*'' }
      prevs = seeds.map(&gen).select {|e| e[0,2] == e[-2,2] }.map {|e| e[1..-2] }
      prevs[1] ? "Multiple" : prevs[0] || "No"
    end
    expect( prev_state["00011101"] ).to eq "01100101"
    expect( prev_state["000"] ).to eq "Multiple"
    expect( prev_state["000001"] ).to eq "No"
    expect( prev_state["11110"] ).to  eq "10010"
  end

  it "#78 - Print Number" do
    expect("gwnae".to_i("!".ord)).to eq 20150111
    bit_str = ->n { [20150111.to_s(n), n, n.chr] }
    (11..36).map(&bit_str).reject {|e| e[0]=~/\d/ }
    #"ߟo".codepoints.map {|e| '%04d' % e }.join.to_i
    # [[31, "lpbqi"], [33, "gwnae"], [36, "bzvxb"]]
    # ["2015".hex].pack("U") ["0111".hex].pack("U") # "―" "đ"
    # "\u{2015}" [2015].pack('U*') "ߟ"
    # p "国".unpack("U*").first
    # "07DF".hex = 2015 = unicode "ߟ".codepoints 2015.to_s(16)
    # "6F".hex = 111 = unicode "o".codepoints
  end

  it "#79 - JSON Validator" do
    # Hash(키가 Symbol), Array로 파싱되는 것은 true. 이외의 것들은 parse error.
    def is_valid_json(str)
      eval_exp = eval(str)
      (eval_exp.class == Hash && eval_exp.keys.all? {|e| e.class == Symbol} ) ||
      (eval_exp.class == Array) ? "true" : Exception
      rescue Exception; "Parse error!"
    end

    # json_lib 크로스 체크
    require 'json'
    def chk_with_jsonlib(str)
      JSON.parse(str) ? "true" : Exception
      rescue Exception; "Parse error!"
    end

    # test data
    valids = ['{"level":3, "attr":{"power":120, "name":"hero"},
              "friendIds":[12, 23, 34, 23]}', '[12, 23, 34, "23"]']
    not_valids = ['{"level":3, "attr":{"power":120, "name":"hero"},
                  "friendIds":[12, 23, 34, 23}', '["level":3, 23, 34, "23"]']
    hash_but_json_err = '{"key" => value}' # 해쉬지만 json이 아닌 것.
    all_cases = valids + not_valids + [hash_but_json_err]

    # test
    expect( valids.map {|s| is_valid_json(s) } ).to eq ["true"]*2
    expect( not_valids.map {|s| is_valid_json(s) } ).to eq ["Parse error!"]*2
    expect( is_valid_json(hash_but_json_err) ).to eq "Parse error!"

    # json_lib로 파싱한 결과와 크로스 체크
    expect( all_cases.map {|s| is_valid_json(s) } ).
            to eq all_cases.map {|s| chk_with_jsonlib(s) }
  end

  it "#80 - Staying seconds" do
    # 크리스탈 p (0..23).map {|h| h.to_s=~/3/? 60:15 }.sum*60
    stay = ->n { [*0..23].product([*0..59]).count {|t| t*''=~/3/}*60 }
    expect( stay.('3') ).to eq 29700
  end

end
<file_sep>#### 2. Enumerable#chunk 또는 #chunk_while로 배열을 순차적으로 묶어낼 수 있다.
- [Intervals 문제](http://codingdojang.com/scode/440)  
연속적으로 증가하는 숫자들을 하나로 묶어낼 수 있다.
```ruby
pairs = -> { (1..gets.to_i).map { gets.split.map &:to_i }.sort }
intervals = -> { pairs[].chunk_while{|(_,a),(b,_)|a>=b}.map {|e| e.flatten.minmax*' '} }

$stdin = StringIO.new("5\n1 4\n5 6\n6 9\n8 10\n10 10\n") # test data
expect(intervals.call).to eq ["1 4", "5 10"]
```

- [Dash Insert 문제](http://codingdojang.com/scode/529)  
홀수와 짝수가 연속되지 않은 것들을 하나로 묶어낼 수 있다.
```ruby
def dash_insert(str=gets.chop)
  head, *tail = str.chars.map(&:to_i).chunk_while {|a,b| a%2+b%2 == 1}.to_a
  puts (head + tail.map {|e| ["*-"[e[0]%2] ,e] }).join
end

$stdin = StringIO.new("4546793\n")
expect{ dash_insert() }.to output("454*67-9-3\n").to_stdout
```

- [개미수열 문제](http://codingdojang.com/scode/516)  
똑같은 숫자들을 하나로 묶어낼 수 있다.
```ruby
ant = ->seq=[1] { seq.chunk{|_|_}.flat_map {|h,g|[g.size,h]} }
iterate = ->n { n==1? [1].lazy : ant[iterate[n-1]] }
ant_seq = ->nth,n { puts iterate[100].take(100).force.last }

expect{ ant_seq[100,100] }.to eq "1"
```
