文字列から2次元配列を作る

どう書く.orgの「島の数をカウントする」にトライしていて、以下のような文字列が与えられたとき、扱いやすく2次元配列に変換しようとしてたんだけどちょっとハマッた。

area_str = "□■■□
                □□■□
                □■□□
                □■■□"

結論を先に書いておくと、次のようにするといけた。※ただし1.8.7以降でないと動きません。

  area = [].tap{|ary| area_str.lines{|line| ary.push(line.strip.chars.to_a)}}

1.9からのtapメソッドを使ってみた♪これを使うことで1行で書ける。それまでは以下のように書く必要があった。

  area = []
  area_str.lines{|line| area.push(line.strip.chars.to_a)}

本題に戻って、ハマッたところはcharsメソッドがうまく働かなかったところ。配列の数を調べてみると8となっている。4が期待値なのに!原因は全角文字がうまく識別されておらず変に1バイトで分割されていること。原因は分かってもどうすればいいのかがよく分からなかった。。ドキュメントとかを読んでいたら分かった。プログラム冒頭に次のコードを入れることで解決。

  $KCODE = 's' # SJISの意味

島の数をカウントする

解けたから投稿したんだけど、後から削除できないんだね。。他の人の答えを見ると自分のは最初から2次元配列にしてあるから反則な気がしたので、変えたかったんだけど。。何度も投稿するのも微妙なので、やめた。ということでここに最終解答を載せておく。

WHITE = ""
BLACK = ""

@area
@mark

def scan(area_str)
  @area = [].tap{|ary| area_str.lines{|line| ary.push(line.strip.chars.to_a)}}
  @mark = Array.new(@area.size).map{ Array.new(@area[0].size, false) }

  puts "白の島は#{loop(WHITE)}です。"
  puts "黒の島は#{loop(BLACK)}です。"
end

def loop(color)
  counter = 0
  @area.each_index{|y| @area[y].each_index{|x| counter+=1 if marking(y, x, color)}}

  counter
end

def marking(y, x, color)
  return false unless y.between?(0, @area.size-1) && x.between?(0, @area[0].size-1) && @area[y][x] == color && !@mark[y][x]

  @mark[y][x] = true

  marking(y,   x+1, color)
  marking(y+1, x,   color)
  marking(y,   x-1, color)
  marking(y-1, x,   color)

  true
end

# プログラムスタート!
$KCODE = "s"

area1 = "□■■□
         □□■□
         □■□□
         □■■□"
scan(area1)