右折禁止(アルゴリズム・パズル)

アルゴリズム・パズルを Ruby で解いてみました。
20170526124128
 

class TurnLeft
  class Position
    def initialize(x, y)
      @x, @y = x, y
    end
    attr_accessor :x, :y
    
    def +(dir)
      Position.new(@x + dir[0], @y + dir[1])
    end
  end
  
  class Field
    def initialize
      @yoko = Array.new(Height + 1) {Array.new(Width , 0)}
      @tate = Array.new(Width  + 1) {Array.new(Height, 0)}
    end
    
    def set(po, dir)
      if dir[1].zero?
        x = (dir[0] > 0) ? po.x : po.x - 1
        @yoko[po.y][x] = 1
      else
        y = (dir[1] > 0) ? po.y : po.y - 1
        @tate[po.x][y] = 1
      end
    end
    private :set
    
    def get(po, dir)
      if dir[1].zero?
        x = (dir[0] > 0) ? po.x : po.x - 1
        return 1 if x >= Width or x < 0
        a = @yoko[po.y][x]
      else
        y = (dir[1] > 0) ? po.y : po.y - 1
        return 1 if y >= Height or y < 0
        a = @tate[po.x][y]
      end
      set(po, dir) if a.zero?
      a
    end
    
    def dup
      Marshal.load(Marshal.dump(self))
    end
  end
  
  def go
    puts main(Position.new(0, 0), [1, 0], Field.new)
  end
  
  def main(po, dir, field)
    co = 0
    nxt = po + dir
    return 1 if nxt.x == Width and nxt.y == Height   #ゴールか?
    return 0 if nxt.x > Width or nxt.x < 0 or nxt.y > Height or nxt.y < 0
    return 0 unless field.get(po, dir).zero?         #すでに通っているか?
    
    co += main(nxt, dir, field.dup)                  #直進
    co += main(nxt, [-dir[1], dir[0]], field.dup)    #左折
  end
  private :main
end

Width, Height = 6, 4

TurnLeft.new.go

答えは 2760通りです。かかった時間は自分の環境で約 3.7秒でした。


※追記(2018/3/13)
別の方法で解いてみました。

def yoko?(x, y, x1)
  return false if x1 < 0 or x1 > Width
  x2 = [x, x1].min
  return false if @yoko[y][x2] == 1
  x2
end

def tate?(x, y, y1)
  return false if y1 < 0 or y1 > Height
  y2 = [y, y1].min
  return false if @tate[x][y2] == 1
  y2
end

def solve(x, y, dir)
  walk = lambda do |dir|
    dirs = [[1, 0], [0, -1], [-1, 0], [0, 1]]
    x1 = x + dirs[dir][0]
    y1 = y + dirs[dir][1]
    if (dir == 0 or dir == 2) and (x2 = yoko?(x, y, x1))
      @yoko[y][x2] = 1
      solve(x1, y1, dir)
      @yoko[y][x2] = 0
    elsif (dir == 1 or dir == 3) and (y2 = tate?(x, y, y1))
      @tate[x][y2] = 1
      solve(x1, y1, dir)
      @tate[x][y2] = 0
    end
  end
  
  if x == Width and y == 0
    @co += 1
  else
    walk.call(dir)            #直進
    walk.call((dir + 1) % 4)  #左折
  end
end

Width, Height = 6, 4

@yoko = Array.new(Height + 1) {Array.new(Width , 0)}
@tate = Array.new(Width  + 1) {Array.new(Height, 0)}
@co = 0

solve(0, Height, 0)
puts @co    #=>2760

解くのにかかった時間は 0.3秒ほどです。
 

GTK+ で落書き 8(Ruby)

Gem 'oekaki' で落書きです。
oekaki | RubyGems.org | your community gem host
GTK+でお絵かきしてみた(Ruby) - Camera Obscura

 
再帰的な tree を描いてみました。画像では動きはないですが、じつはアニメーションです。

 

require 'oekaki'

Width, Height = 400, 300
Turn = 20
Length = 40
Ratio = 0.9
Depth = 8

θ = PI * Turn / 180
left  = Matrix[[cos( θ), -sin( θ)], [sin( θ), cos( θ)]]
right = Matrix[[cos(-θ), -sin(-θ)], [sin(-θ), cos(-θ)]]


Oekaki.app width: Width, height: Height, title: "tree" do
  draw do
    color(0, 0, 0)
    rectangle(true, 0, 0, Width, Height)
  end
  
  ar = [[Vector[Width / 2, 1], Vector[0, Length]]]
  branch_num = 0
  
  id = timer(80) do
    ob = ar.shift
    v1 = left  * ob[1]
    v2 = right * ob[1]
    p1 = ob[0] + v1
    p2 = ob[0] + v2
    
    color(0, 65535, 0)
    line(ob[0][0], Height - ob[0][1], p1[0], Height - p1[1])
    line(ob[0][0], Height - ob[0][1], p2[0], Height - p2[1])
    
    ar << [p1, v1 * Ratio]
    ar << [p2, v2 * Ratio]
    
    branch_num += 2
    Gtk.timeout_remove(id) if branch_num >= 2 ** (Depth + 1) - 2
    true
  end
end

いわゆる「スライドパズル(15パズル)」もどきを Ruby で解いてみる

スライドパズルっていうおもちゃがありますよね。4×4 のマス目に空白がひとつあって(残りはコマ)、コマは空白にスライドさせて動かすしかなく、それを特定のパターンに揃えるというものです。ここではルールを少し改変して、特定のマスを別の特定のマスに移動させるのに最短で何手かかるかを考えます。

例えば 3×2 のパズルでいちばん右下のマスが空白であり、いちばん左上のマスにあるコマをいちばん右下へもってくるには、最短で 9手かかります。

●●●
● ○

●●●
●○ 

●● 
●○●

● ●
●○●

●○●
● ●

●○●
 ●●

 ○●
●●●

○ ●
●●●

○● 
●●●

○●●
●● 

9手で終了

ただしこれは、下の方から見ていって下さい(プログラムの関係でそうなっています)。

僕が見たアルゴリズムパズルの本では、10×10 のパズルでやるのと同等な問題が与えてありました(駐車場で車を脱出させるという体裁になっていました)。これは自分の書いたプログラムだと、69手ということになります(調べたパターンの数は 8927個)。計算時間はこれだと(自分の環境で)およそ 41秒と、かなり微妙な数字になりました。瞬殺は無理でしたね。これ以上広いパズルだと、根本から考えなおさないとダメかも知れません。

コードは以下です。幅優先探索で求めています。また、既に出たパターンに逢着した場合はスキップしています。

class Parking
  def initialize
    start = Pattern.new
    start.car.x, start.car.y = 0, 0
    start.space.x, start.space.y = Width - 1, Height - 1
    @@patterns = [start]
  end
  
  def solve
    buffer = @@patterns.dup
    while (po = buffer.shift)
      [po.up, po.down, po.left, po.right].each do |i|
        next unless i
        if i[1]    #true ならば終了
          @@co = 0
          i[0].show
          puts "#{@@co - 1}手で終了"
          puts "調べたパターン数は#{@@patterns.size}"
          exit
        end
        buffer << i[0]
      end
    end
  end
end

class Pattern < Parking
  class Car   < Struct.new(:x, :y); end
  class Space < Struct.new(:x, :y); end
  
  def initialize
    @car   = Car.new
    @space = Space.new
    @parent = nil
  end
  attr_accessor :car, :space, :parent
  
  def up
    x, y = space.x, space.y - 1
    return false if y < 0
    check(x, y)
  end
  
  def down
    x, y = space.x, space.y + 1
    return false if y >= Height
    check(x, y)
  end
  
  def left
    x, y = space.x - 1, space.y
    return false if x < 0
    check(x, y)
  end
  
  def right
    x, y = space.x + 1, space.y
    return false if x >= Width
    check(x, y)
  end
  
  def check(x, y)
    nxt = Pattern.new
    nxt.space.x, nxt.space.y = x, y
    nxt.car = (@car.x == x and @car.y == y) ? @space : @car
    
    @@patterns.each do |pa|
      return false if nxt.car == pa.car and nxt.space == pa.space 
    end
    nxt.parent = self
    
    @@patterns << nxt
    [nxt, (nxt.car.x == Width - 1 and nxt.car.y == Height - 1)]
  end
  private :check
  
  def putout
    Height.times do |i|
      st = "" * Width
      st[@car.x]   = "" if @car.y   == i
      st[@space.x] = " " if @space.y == i
      puts st
    end
    puts
  end
  
  def show
    @@co += 1
    putout
    @parent.show if @parent
  end
  
  def inspect
    "<Pattern:car(#{@car.x},#{@car.y}), space(#{@space.x},#{@space.y})>"
  end
end

Width = 4; Height = 4

Parking.new.solve

なお、このプログラムは Pattern#putout で String リテラルの破壊的変更を行っています。
 
ちなみに「15パズル(4×4)」の場合、21手で終了します。時間は約 0.1秒です。

Ubuntu 17.04 でインターネット接続できない

かなり深刻なバグだと思います。新規インストール、アップグレードの双方で起こるようです。また、派生のフレーバーすべてでも起きるようです(Ubuntu Budgie でも発生しました)。発生した PC は ThinkPad T410i です。

特徴は一見ネット接続ができているように表示されることです。ネット接続のところを見ると「接続」になっています。それなのに、Firefox やシステムのアップデートで接続できません。


対策としては、/etc/resolv.conf を編集します。

nameserver xx.xx.xx.xx

となっているところ(xx..云々のところには具体的なアドレスが入っています)を、$ sudo vi /etc/resolv.conf などで

nameserver 8.8.8.8

に編集し直します(元のアドレスは控えておいた方がよいでしょう)。これで [logout]→[login] でネット接続すれば OK です。このとき、同じローカルネットワークに接続している他の PC のネット接続ができなくなると思われるので、ルーターの電源を入れ直すなどしてネットワークを更新して下さい。これで他の PC からもネット接続できればおしまいです。


※参考
Bug #1654918 “No Internet access with default of Automatic (DHC...” : Bugs : network-manager package : Ubuntu
Bug #1679535 “! Cannot connect to internet in Ubuntu 17.04 (DNS ...” : Bugs : network-manager package : Ubuntu

RubyPico でクラスのリファレンスを便利に

サンプルコードの class_help.rb を少し変えただけですが、だいぶ使いやすくなったと思います。まずクラスの一覧が出るので、調べたいクラスをタッチすれば詳細が表示されます。[戻る] でループします。
 
コードは Gist に。
Created by RubyPico at Tue May 02 20:17:08 2017 · GitHub

RubyPico でファイルを Gist にアップロードする

GitHubAPI用の token を取得して下さい(参照)。それを以下のコードの「***」のところに入れます。あとは実行するだけ。コードの保存用にも使えますね。

upload_to_gist.rb

def main
  fname = Popup.input("ファイル名を入力して下さい")
  return unless fname
  unless File.exist?(fname) and File.file?(fname)
    puts "ファイルが存在しないか、ディレクトリです"
    return
  end
  
  file_content = File.open(fname) {|io| io.read}

  # https://developer.github.com/v3/gists/#create-a-gist
  json = {
    description: "Created by RubyPico at #{Time.now}",
    public: true,
    files: {
      fname => {content: file_content}
    }
  }
  
  puts "アップロード中..."
  
  result =  Browser.post(
    "https://api.github.com/gists",
    header: { "Authorization" => "token ***" },
    json: json
  )
  
  if result.length < 100
    puts result
  else
    puts fname + "を Gist にアップロードしました"
  end
end

main

mruby を Linux Mint(Ubuntu)で使ってみる

Linux Mint 18 で確認しました。

まずはここの Download から mruby をダウンロードします。自分は「mruby Stable版 v1.2.0 Linux/Mac版」を選択しました。

解凍して適当な場所に置き、ディレクトリに入って

$ make

を実行。gcc と CRuby と Bison が必要です。Bison は入っていなければ $ sudo apt-get install bison で入りますが、僕の環境にはすでに入っていました。gcc が入っていないということはないでしょうが、なければ $ sudo apt-get install build-essential で。実行すると
mruby_build.txt · GitHub
という風にビルドされました。これでおしまいです。


mruby の Hello, World! は CRuby とはだいぶちがいます。おおよそ
Hello World · mruby/mruby Wiki · GitHub
に書いてあるようにやればいいのですが、一部(C に慣れている人(自分にあらず)には自明であろう)修正が必要です。

まず

#include <stdio.h>
#include <mruby.h>
#include <mruby/compile.h>

int main(void)
{
  mrb_state *mrb = mrb_open();
  if (!mrb) { /* handle error */ }
  puts("Executing Ruby code from C!");
  mrb_load_string(mrb, "p 'hello world!'");
  mrb_close(mrb);
  return 0;
}

のコードを例えば hello.c で保存します。それで書かれているとおりに

$ gcc -Iinclude hello.c build/host/lib/libmruby.a -lm -o hello.out

とやると

gcc: error: build/host/lib/libmruby.a: そのようなファイルやディレクトリはありません

とおこられます。なのでそれに対応して

$ gcc -Imruby/include hello.c mruby/build/host/lib/libmruby.a -lm -ohello.out

と修正、そして

$ ./hello.out
Executing Ruby code from C!
"hello world!"

とうまくいきました。


では helloworld.rb みたいな rbファイルはどうやって実行するのかなあ。ここが初心者である。mruby はバイナリにコンパイルして実行するのだよね。さて。


※追記
わかった。bin/mruby が mruby のインタプリタだから、例えば

$ ./mruby/bin/mruby helloworld.rb

とかでいいわけだ。

コンパイルは bin/mrbc で行うので、

$ ./mruby/bin/mrbc helloworld.rb

バイトコードコンパイルされ、helloworld.mrb が出来る。これを実行するには

$ ./mruby/bin/mruby -b helloworld.mrb
Hello, World!

とオプションをつけて実行すればよい。

※参考
mruby概論(2):“組み込みRuby”こと「mruby」をセットアップしてみよう (2/3) - MONOist(モノイスト)
mrubyをとりあえず動かしてみただけ - ねこのて