「孤独の7」を Ruby で

rscの日記さんのところで、「孤独の7」という虫喰い算を知りました。問題は右の画像のとおりです。ここで回答を募集していたようです。


Ruby で解きました。かかった時間はほぼ 0.1秒です(答えの uniqueness のチェックなし。チェックすると 0.7秒程度)。Ruby を使ってだから、まあまあなのではないでしょうか。
lonely7.rb

def main_loop
  112.upto(143) {|i| @n = i; yield}
end

def num_loop(r)
  1.upto(9) do |i|
    yield(@n * i) if check(@n * i, r)
  end
end

def check(a, r)    #aはr桁か
  10 ** (r - 1) <= a and a < 10 ** r
end

1000.upto(9999) do |x1|
  main_loop do
    num_loop(4) do |i1|
      next unless check(a1 = x1 - i1, 2)
      0.upto(9) {|x2|
        next unless check(a2 = a1 * 10 + x2 - @n * 7, 3)
        num_loop(3) do |i2|
          0.upto(9) {|x3|
            next unless check(a3 = a2 * 10 + x3 - i2, 2)
            0.upto(9) {|x4|
              next unless a3 * 10 + x4 < @n
              0.upto(9) {|x5|
                num_loop(4) do |i3|
                  if a3 * 100 + x4 * 10 + x5 == i3
                    num = x1 * 10000 + x2 * 1000 + x3 * 100 + x4 * 10 + x5
                    puts "#{num} / #{@n} = #{num / @n}"
                    exit
                  end
                end
              }
            }
          }
        end
      }
    end
  end
end

#=>"12128316 / 124 = 97809"

単純な総当りではないです。特に 112.upto(143) と割る数を絞っているのは、7を掛けて 3桁になるというのと、1桁の数を掛けて 4桁になり得ることを使っています。また、桁数が指定できるところはすべて桁数のチェックをしています。メソッド num_loop(r)Ruby お得意のブロックを使って、(桁数チェックの入った)カスタムの制御構造を作り出しています。

ちなみにマシン・スペックは Core i5-4210U 1.70GHz×2。

追記

rsc さんの Python のコードRuby に移植してみました。上手いですねえ。僕のコードよりずっといいですね(見てもらえばわかりますが、僕と発想が真逆です)。実行時間はほぼ 0.7秒です。

require 'kaki/utils/nest_loop'

def digit(n)
  n.zero? ? 0 : n.to_s.length
end

def get_num(x, n)
  (x / n) % 10
end

[10, 10, 10].nest_loop do |a, b, c|
  next if a.zero?
  (100...200).each do |dvs|
    quo = 10000 * a + 7000 + 100 * b + c
    dvd = quo * dvs
    next unless digit(dvs * a) == 4
    next unless digit(dvs * 7) == 3
    next unless digit(dvs * b) == 3
    next unless digit(dvs * c) == 4
    tmp = [0, 0, 0, 0, 0]
    k   = [2, 3, 2, 3, 0]
    rem = dvd / 100000
    catch(:exit) do
      5.times do |i|
        tmp[i] = rem * 10 + get_num(dvd, 10 ** (4 - i))
        rem = tmp[i] % dvs
        throw(:exit) unless digit(rem) == k[i]
      end
      puts "#{dvd} / #{dvs} = #{quo}"
    end
  end
end

なお、手製のライブラリのメソッド(nest_loop)を使っています(参照)。

Ruby の OpenStruct クラスでアトリビュートっぽく

PythonJavaScript では

>>> class Country:
...     pass
... 
>>> japan = Country()
>>> japan.capital = "Tokyo"
>>> japan.capital
'Tokyo'

みたいな書き方ができるのだけれど(いわゆる「アトリビュート」)、Ruby ではこういう書き方はないのかなと思っていました。


けれども標準添付ライブラリの OpenStruct クラスで簡単にできるのですね。

irb(main):001:0> require 'ostruct'
=> true
irb(main):002:0> class Country < OpenStruct
irb(main):003:1> end
=> nil
irb(main):004:0> japan = Country.new
=> #<Country>
irb(main):005:0> japan.capital = "Tokyo"
=> "Tokyo"
irb(main):006:0> japan.capital
=> "Tokyo"

なるほど、これは便利。

Ruby でクラスメソッドを include する

Ruby でモジュールを include してクラスメソッドを作りたいとします。そのとき、

module Utility
  def self.output(str)
    puts "output: #{str}"
  end
end

class A
  include Utility
end

A.output("Hello!")    #=>undefined method `output' for A:Class (NoMethodError)

はうまくいきません。

ではどうするかというと、こうします。

module Utility
  def output(str)
    puts "output: #{str}"
  end
end

class A
  class << self
    include Utility
  end
end

A.output("Hello!")    #=>"output: Hello!"

 
これはうまくいきます。class << self は特異クラスをオープンします。これはじつは self がクラスA だからで、これがインスタンスならば同様に特異メソッドがオープンされるのです。
 

module Utility
  def output(str)
    puts "output: #{str}"
  end
end

obj = Object.new

class << obj
  include Utility
end

obj.output("Hello!")    #=>"output: Hello!"

 
なお、同じことは Object#extend を使ってもできます(参照)。
 

メタプログラミングRuby 第2版

メタプログラミングRuby 第2版

Ruby のメソッド間で変数を共有する

Ruby のメソッド間では、ふつうは変数は共有されません。

def counter_set
  x = 0
end

def inc
  x += 1
end

def counter_value
  x
end

counter_set
inc
inc
counter_value

は最初の inc 呼び出しのところで

undefined method `+' for nil:NilClass (NoMethodError)

のエラーが出ます。これは Ruby の仕様で、却ってメソッドは安全ともいえるわけです。もし変数を共有したければ、メソッドではなく、クロージャである proc を使えば事足ります。

x = 0

inc = proc do
  x += 1
end

inc.call
inc.call
x          #=>2

という具合に。


しかし、これでは「イヤだ」、メソッドを使いたいと仰るわがままな方もおられましょう。これはじつは、Rubyメタプログラミングを使えば可能です。

def counter_set
  x = 0
  
  define_method(:inc) do
    x += 1
  end
  
  define_method(:counter_value) do
    x
  end
end

counter_set
inc
inc
counter_value    #=>2

こんな具合ですね。Module#define_method のメソッド定義がブロック(Ruby のブロックはクロージャです)で行われているために、変数 x が透過して保持されます。

同様のことは、メソッドだけでなくクラスやモジュールでも可能です。


メタプログラミングRuby 第2版

メタプログラミングRuby 第2版


なお、上のはなんちゃってメソッドですが、もし冗談にせよ使うなら、こんな感じの方がいいかも知れません。

module Kernel
  def counter_set
    x = 0
    Kernel.send(:define_method, :inc) do
      x += 1
    end
    
    Kernel.send(:define_method, :counter_value) do
      x
    end
  end
end

これなら、クラスの中でなど、どこでも使えます。例えばこんな感じ。

counter_set

class A
  inc
  def initialize
    inc
  end
end

counter_value    #=>1
A.new
counter_value    #=>2

GTK+ で落書き 7(Ruby)

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


引き続き Tool#star を使っています。

 

require 'oekaki'

Oekaki.app do
  draw do
    color(0, 0, 0)
    rectangle(true, 0, 0, 300, 300)
  end
    
  po = Vector[0, 150]
  θ = PI / 50
  a = Matrix[[cos(θ), -sin(θ)], [sin(θ), cos(θ)]]
  
  timer(20) do
    color(0, 0, 0)
    rectangle(true, 0, 0, 300, 300)
    color(0, 65535, 0)
    star(false, 150, 150, 150 + po[0], 150 - po[1])
    po = a * po
  end
end

オブジェクト指向じゃんけんゲームの Ruby 移植版

前回エントリの C# バージョンの、Ruby 移植版です。
 

module JankenAsobi
  class Player
    def initialize
      @wincount = 0
    end
    attr_reader :wincount, :name
    
    def count
      @wincount += 1
    end
  end

  class Cp < Player
    def initialize
      @name = "Computer"
      super
    end
    
    def show_hand
      rand(3)
    end
  end
  
  class Man < Player
    def initialize
      print "あなたの名前を入力して下さい:"
      @name = gets.chomp
      @name = "名無し" if !@name or @name.empty?
      super
    end
    
    def show_hand
      begin
        print "#{@name}の手を入力して下さい(0:グー, 1:チョキ, 2:パー):"
        n = gets.chomp
      end until n == "0" or n == "1" or n == "2"
      n.to_i
    end
  end

  class Judge
    Hand = ["グー", "チョキ", "パー"]
    
    def initialize(p1, p2)
      @player1 = p1
      @player2 = p2
      puts "#{@player1.name} 対 #{@player2.name} :じゃんけん開始\n "
    end
    
    def game(n)
      puts "*** #{n}回戦 ***"
      hand1 = @player1.show_hand
      hand2 = @player2.show_hand
      judgement(hand1, hand2)
    end
    
    def judgement(h1, h2)
      winner = @player1
      print "#{Hand[h1]} 対 #{Hand[h2]}で "
      if h1 == h2
        puts "引き分けです。"
        return
      elsif (h1 - h2) % 3 == 1
        winner = @player2
      end
      puts "#{winner.name}の勝ちです。"
      winner.count
    end
    private :judgement
    
    def winner
      p1 = @player1.wincount
      p2 = @player2.wincount
      finalwinner = @player1
      print "\n*** 最終結果 ***\n#{p1}#{p2} で "
      if p1 == p2
        puts "引き分けです。"
        return
      elsif p1 < p2
        finalwinner = @player2
      end
      puts "#{finalwinner.name}の勝ちです。"
    end
  end

  def self.play
    player1 = Cp.new
    player2 = Man.new
    judge = Judge.new(player1, player2)
    5.times {|i| judge.game(i + 1)}
    judge.winner
  end
end

JankenAsobi.play

じゃんけんゲームで C# のオブジェクト指向プログラミングのお勉強

以前は OOP のお勉強に本のじゃんけんゲームのコードを移植してやっていたのですが、自分で作ってみることにしました。昨日一日で C# のお勉強をした(参照)ので、その試しです。あとで同じのを Ruby で書いてみるつもりです(追記:書きました)。

題材はじゃんけんゲームです。こんな感じ。


C# のコードです。えらく長くなってしまいました。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace JankenAsobi {
    class Program {
        static void Main(string[] args) {
            Cp  player1 = new Cp();
            Man player2 = new Man();
            Judge judge = new Judge(player1, player2);
            for (int i = 1; i <= 5; i++) {
                judge.Game(i);
            }
            judge.Winner();
            Console.Read();
        }
    }

    abstract class Player {
        public int wincount;
        public String name;
        public String Name {
            get { return name; }
            }
        public Player() {
            this.wincount = 0;
        }
        public abstract int ShowHand();
        public void Count() {
            wincount++;
        }
        public int WinCount {
            get { return wincount; }
        }
    }

    class Cp : Player {
        public Cp() {
            this.name = "Computer";
            }
        public override int ShowHand() {
            Random rnd = new Random();
            return rnd.Next(0, 3);
        }
    }

    class Man : Player {
        public Man() {
            Console.Write("あなたの名前を入力して下さい:");
            this.name = Console.ReadLine();
            if (name == "") {
                name = "名無し";
            }
        }
        public override int ShowHand() {
            int n;
            do {
                Console.Write(this.name + "の手を入力して下さい(0:グー, 1:チョキ, 2:パー):");
                try { 
                    n = int.Parse(Console.ReadLine());
                }
                catch {
                    Console.WriteLine("数字を入れて下さい");
                    n = -1;
                }
            } while (n != 0 && n != 1 && n != 2);
            return n;
        }
    }

    class Judge {
        public Player player1;
        public Player player2;
        public Judge(Player p1, Player p2) {
            this.player1 = p1;
            this.player2 = p2;
            Console.WriteLine(player1.Name + " 対 " + player2.Name + " :じゃんけん開始\n");
        }
        public int hand1, hand2;
        public void Game(int n) {
            Console.WriteLine("*** {0}回戦 ***", n);
            hand1 = player1.ShowHand();
            hand2 = player2.ShowHand();
            Judgement(hand1, hand2);
        }
        private void Judgement(int h1, int h2) {
            Player winner = player1;
            Console.Write(Hand(h1) + " 対 " + Hand(h2) + "で ");
            if (h1 == h2) {
                Console.WriteLine("引き分けです。");
                return;
            } else if ((3 + h1 - h2) % 3 == 1) {
                winner = player2;
            }
            Console.WriteLine(winner.Name + "の勝ちです。");
            winner.Count();
        }
        private string Hand(int h) {
            string[] hs = {"グー", "チョキ", "パー" };
            return hs[h];
        }
        public void Winner() {
            int p1, p2;
            p1 = player1.WinCount;
            p2 = player2.WinCount;
            Player finalwinner = player1;
            Console.Write("\n*** 最終結果 ***\n{0} 対 {1} で ", p1, p2);
            if (p1 == p2) {
                Console.WriteLine("引き分けです。");
                return;
            } else if (p1 < p2) {
                finalwinner = player2;
            }
            Console.WriteLine(finalwinner.Name + "の勝ちです。");
        }
    }
}
  • Program
    • Main(): 最初に呼ばれるメソッドです。
  • Player: 抽象クラスです。
    • ShowHand(): 抽象メソッドです。手を見せます。
    • Count(): 勝ったときに呼ばれて、勝数を 1増やします。
    • Name: プロパティです。プレーヤーの名前を返します。
    • WinCount: プロパティです。プレーヤーの勝った回数を返します。
  • Cp: Playerクラスを継承します。対戦相手のコンピュータ側の処理をします。
  • Man: Playerクラスを継承します。人間側の処理をします。
  • Judge: 審判のクラスです。ゲームを進行させていきます。
    • Game(int n): 第 n 回戦を進行させるメソッドです。
    • Judgement(): private メソッドです。第 n 回戦の勝ち負けの処理をします。
    • Winner(): すべての勝負が終わったあとの処理(最終勝者の処理など)をします。

 
 
 
一応 exe ファイルを添付しておくので、Windows を使っておられる方はダウンロードしてダブルクリックで遊べます(しょうもないものですけれど)。ただ、出所不明のアプリということで、いっぱい警告が出ると思いますが(笑)。
JankenAsobi.exe 直