Archive for the 'Ruby' Category

Quiz: Count the ways to find a word by walking on a grid (4)

Wednesday, April 2nd, 2008
# Author : David Tran
# Date   : 2008-04-02
# Quiz   : http://davidtran.doublegifts.com/blog/?p=139
# File   : quiz.rb

class Quiz

  def initialize(grid_file, goal_file)
    grid_data = IO.readlines(grid_file).map {|e| e.chomp}.map {|e| e.split(/\s+/)}
    goal = IO.read(goal_file).chomp
  
    @rows = grid_data.size
    @cols = grid_data[0].size
    cell_size = grid_data[0][0].size
  
    # split goal string to build up dictionary
    dictionary = Hash.new {|h, e| h[e] = []}
    index = 0
    goal.scan(/#{'.' * cell_size}/).each do |w|
      dictionary[w] << index
      index += 1
    end
  
    @last_nb = (goal.size / cell_size) - 1
    @grid = Array.new(@rows) { Array.new(@cols) { Hash.new } }
    starts = []
  
    @rows.times do |r|
      @cols.times do |c|
        dictionary[grid_data[r][c]].each do |v|
          @grid[r][c][v] = -1
          starts << [r,c] if v == 0
        end
      end
    end

    solutions = starts.inject(0) { |s, (i,j)| s += find_solution(i, j, 0) }
    puts "Total Solution = #{solutions}"
  end
  
  def find_solution(i, j, nb)
    return 1 if nb == @last_nb
    return @grid[i][j][nb] if @grid[i][j][nb] >= 0
    @grid[i][j][nb] = get_neighbors(i, j, nb).inject(0) { |s, (x, y)| s += find_solution(x, y, nb+1) }
  end
  
  def get_neighbors(i, j, nb)
    [[-1, -1], [-1, 0], [-1, 1], [0, -1], [0, 1], [1, -1], [1, 0], [1, 1]].inject([]) do |a, (x,y)|
      a << neighbor(i+x, j+y, nb+1)
    end.compact
  end
  
  def neighbor(i, j, nb)
    (i >= 0 && i < @rows && j >= 0 && j < @cols && @grid[i][j].key?(nb)) ? [i, j] : nil
  end
  
end

if $0 == __FILE__
  (puts "Usage: ruby quiz.rb  grid_file  goal_file"; exit) if ARGV.size < 2
  Quiz.new(ARGV[0], ARGV[1])
end

# ============================== #

# Author : David Tran
# Date   : 2008-04-02
# File   : test.rb
# Object : find the maximum solutions

open('grid.txt', 'w') {|f50.times { f << 'A ' * 50 << "\n"}}
max = 0
index = -1
50.times do |i|
  i += 1
  open('goal.txt', 'w') { |f| f << 'A' * i }
  result = %x{ ruby quiz.rb grid.txt goal.txt }
  if result =~ /Solution = (\d+)/
    solutions = $1.to_i
    puts "#{i.to_s.rjust(2, '0')} => #{solutions}"
    if (solutions > max)
      max = solutions
      index = i
    end
  end
end

puts
puts "Maximum => #{index} => #{max}"


__END__
Result:

01 => 2500
02 => 19404
03 => 152292
04 => 1198512
05 => 9453300
06 => 74662104
07 => 590305088
08 => 4670864544
09 => 36982919796
10 => 292979734056
11 => 2322050158968
12 => 18410909224368
13 => 146024921567904
14 => 1158535320863088
15 => 9194071282628352
16 => 72981174038164224
17 => 579439667561976564
18 => 4601415854638476552
19 => 36547133286285125864
20 => 290326968106929048336
21 => 2306684143572560961624
22 => 18329503368942296048880
23 => 145670217447616179975600
24 => 1157829208250036601664992
25 => 9203827271760017849641312
26 => 73171094997928920840845904
27 => 581774919929278927886179248
28 => 4626070561470449649010421664
29 => 36788241251602079481574654656
30 => 292579041503607826263355150176
31 => 2327088439774693588696558829376
32 => 18510420519854069350331486524800
33 => 147248802959429409354656962751220
34 => 1171434060031010511872752993856328
35 => 9319939961558447074697552088702600
36 => 74154290207123230340766878367722064
37 => 590046376097012157795995971369959624
38 => 4695281006073221324094307242033576432
39 => 37364705320671553187068052340260719824
40 => 297361737077320616265561468778248253728
41 => 2366634780471229416464762512469663693592
42 => 18836455040472517436022242181973614080112
43 => 149929836577708085403038515575864523777776
44 => 1193430584532300786500980677298194867380832
45 => 9500046751158181070821773471417360223448304
46 => 75626347587870791654047249972768528120486752
47 => 602058474789522292652482399670191674941123040
48 => 4793158368431771829259255859223541964804203200
49 => 38161186114402617099716572074367083097283018976
50 => 303835410591851117616135618108340196903254429200

Maximum => 50 => 303835410591851117616135618108340196903254429200

Ruby Quiz (#131) Maximum Sub-Array

Sunday, July 15th, 2007

# My solution for Ruby Quiz - Maximum Sub-Array (#131) - http://www.rubyquiz.com/quiz131.html
# Blog  : http://davidtran.doublegifts.com/blog/?p=89
# Quiz  : Given an array of integers, find the sub-array with maximum sum.
# Notes : 
#   (1) max_sub finds a solution. (golf code)
#   
#   (2) max_sub2 finds the shortest of sub-arrays which all have the same maximum sum.
#
#   (3) max_sub3 finds a solution with complexity ~ Theta (n * log n).
#   The reference is: http://www.cse.ust.hk/faculty/golin/COMP271Sp03/Notes/L02.pdf
#   It implements the Divide-and-Conquer solution base on reference paper page 6~9.

def max_sub(a)
a[(0...(n=a.size)).inject([]){|r,i|(i...n).inject(r){|r,j|r<<(i..j)}}.sort_by{|r|a[r].inject{|i,j|i+j}}[-1]]
end

def max_sub2(ary)
  return ary if ary.size <= 0

  n = ary.size - 1

  range = (0..n).inject([]) do |a, i|
    (i..n).inject(a) { |a, j| a << (i .. j) }
  end.sort_by do |r|
    [ ary[r].inject { |a, b| a + b },  # sum of sub array; if sum are equal,
      r.begin - r.end                  # we want the shortest of sub arrays.
    ]
  end.last

  ary[range]
end

#=============================================================================#

def max_sub_array_from_begin(ary) # max sub-array contains first element
  index = 0
  max = ary[index]
  sum = 0
  ary.each_with_index do |e, i|
    sum += e
    if sum > max
      max = sum
      index = i
    end
  end
  [0..index, max]
end

def max_sub_array_from_end(ary) # max sub-array contains last element
  r, max = max_sub_array_from_begin(ary.reverse) # maybe slow because reverse...
  [(ary.size - 1 - r.end) .. (ary.size - 1), max]
end

def mcs_middle(ary, i, j)
  pivot = (i+j) / 2 + 1
  r1, max1 = max_sub_array_from_end(ary[i...pivot])
  r2, max2 = max_sub_array_from_begin(ary[pivot..j])
  [r1.begin .. (pivot + r2.end), max1 + max2]
end

def mcs(ary, i, j)
  return (i..j) if i == j
  r1 = mcs(ary, i, (i+j)/2)
  r2 = mcs(ary, (i+j)/2+1, j)
  s1 = ary[r1].inject{|a,b|a+b}
  s2 = ary[r2].inject{|a,b|a+b}
  r3, s3 = mcs_middle(ary, i, j)
  if s1 > s2
    (s3 > s1) ? r3 : r1
  else
    (s3 > s2) ? r3 : r2
  end
end

def max_sub3(ary)
  return ary if ary.size <= 0
  ary[mcs(ary, 0, ary.size - 1)]
end


# some tests ...
a1 = [-1, 2, 5, -1, 3, -2, 1]
a2 = [-50, 6, -20, 1, 2, 3, -7]
p max_sub(  a1 )
p max_sub(  a1 )
p max_sub2( a2 )
p max_sub3( a1 )
p max_sub3( a2 )

L-system (Fractal)

Tuesday, June 19th, 2007

lsys_fractal_plant.jpg


#!/usr/bin/env ruby -w
#--------------------------------------------------------------------#
#                                                                    #
#  Program   : lsystem.rb                                            #
#  Object    : L-system (Lindenmayer system) class.  ( Fractal )     #
#  Author    : David Tran                                            #
#  Version   : 2007-06-19                                            #
#  Blog      : http://davidtran.doublegifts.com/blog/?p=83           #
#              http://davidtran.doublegifts.com/blog/?p=82           #
#  Reference : http://en.wikipedia.org/wiki/L-system                 #
#                                                                    #
#--------------------------------------------------------------------#
class Lsystem   #  D0L-system; deterministic context-free L-system
  attr_reader :start, :rules

  def initialize(start, rules)
    @start = start
    @rules = rules
    @production = start
  end

  def next
    @production.gsub!(/./) { |c| (r = @rules[c]) ? r : c }
  end
end


if __FILE__ == $0
  require 'RMagick'

  def fractal_gif(name, width, height, iterations, lsystem, &render)
    imageList = Magick::ImageList.new
    iterations.times do |i|
      image = Magick::Image.new(width, height)
      image.delay = 100
      production = (i==0) ? lsystem.start : lsystem.next
      puts "iteration ##{i}\n#{i < 4 ? production : '...'}"
      render[Magick::Draw.new, i, production].draw(image)
      imageList << image
    end
    imageList.write(name + '.gif')
  end

  def fractal_jpg(name, width, height, iterations, lsystem, &render)
    production = lsystem.start
    (iterations - 1).times { production = lsystem.next }
    image = Magick::Image.new(width, height)
    render[Magick::Draw.new, iterations - 1, production].draw(image)
    image.write(name + '.jpg')
  end

  puts "lsys_koch_curve.gif ..."
  fractal_gif('lsys_koch_curve', 400, 150, 8,
    Lsystem.new('F', 'F' => 'F+F--F+F')
  ) do |draw, iteration, prod|
      draw.translate(0, 140)
      size = 400.0 / (3 ** iteration)
      prod.split(//).each do |c|
        case c
          when 'F' : draw.line(0, 0, size, 0).translate(size, 0)
          when '+' : draw.rotate(-60)
          when '-' : draw.rotate(60)
        end
      end
      draw
    end

  puts "lsys_sierpinski_triangle.gif ..."
  fractal_gif(
    'lsys_sierpinski_triangle', 350, 600, 9,
    Lsystem.new('A', 'A' => 'B-A-B', 'B' => 'A+B+A')
  ) do |draw, iteration, prod|
      draw.translate(0, 300).stroke('blue')
      size = 350.0 / (2 ** iteration)
      prod.split(//).each do |c|
        case c
          when 'A','B' : draw.line(0, 0, size, 0).translate(size, 0)
          when '+' : draw.rotate(-60)
          when '-' : draw.rotate(60)
        end
      end
      draw
    end


  fractal_plant_render = proc do |draw, iteration, prod|
    draw.stroke('green').text(25, 470, "Created by David Tran")
    draw.rotate(-90).translate(-490, 250)
    size = 3
    prod.split(//).each do |c|
      case c
        when 'F' : draw.line(0, 0, size, 0).translate(size, 0)
        when '+' : draw.rotate(-25)
        when '-' : draw.rotate(25)
        when '[' : draw.push
        when ']' : draw.pop
      end
    end
    draw
  end

  puts "lsys_fractal_plant.gif ..."
  fractal_gif('lsys_fractal_plant', 400, 500, 7,
    Lsystem.new('X', 'X' => 'F-[[X]+X]+F[+FX]-X', 'F' => 'FF'),
    &fractal_plant_render)

  puts "lsys_fractal_plant.jpg ..."
  fractal_jpg('lsys_fractal_plant', 400, 500, 7,
    Lsystem.new('X', 'X' => 'F-[[X]+X]+F[+FX]-X', 'F' => 'FF'),
    &fractal_plant_render)

end

lsys_koch_curve.gif

lsys_sierpinski_triangle.gif

lsys_fractal_plant.gif

IFS (Fractal)

Monday, June 18th, 2007

ifs_fern.jpg


#!/usr/bin/env ruby -w
#---------------------------------------------------------------------#
#                                                                     #
#  Program   : ifs.rb                                                 #
#  Object    : Iterated Function System class.  ( Fractal -- IFS )    #
#  Author    : David Tran                                             #
#  Version   : 2007-06-18                                             #
#  Blog      : http://davidtran.doublegifts.com/blog/?p=82            #
#              http://davidtran.doublegifts.com/blog/?p=48            #
#              http://davidtran.doublegifts.com/blog/?p=25            #
#  Reference : http://en.wikipedia.org/wiki/Iterated_function_system  #
#              http://en.wikipedia.org/wiki/Sierpinski_gasket         #
#              http://www.sewanee.edu/Physics/PHYSICS123/IFS.html     #
#              http://www.alpheccar.org/en/posts/show/69              #
#                                                                     #
#---------------------------------------------------------------------#
class IFS
  #
  #  Each rule is an array of [w, a, b, c, d, e, f] where
  #  X' = aX + bY + e
  #  Y' = cX + dY + f
  #  and w is weight (probability) for the rule.
  #  
  def initialize(rules, start_point=[0.0, 0.0])
    @point = start_point
    @rules = rules

    # change weight to range
    @rules.inject(0.0) do |weight_begin, r|
      weight_end = weight_begin + r[0]
      r[0] = weight_begin ... weight_end
      weight_end
    end
  end

  def get_next_points(nb)
    (1..nb).inject([]) { |pts, _| pts << get_next_point }
  end

  def get_next_point
    w, a, b, c, d, e, f = get_rule
    x, y = @point
    @point = [ a*x + b*y + e, c*x + d*y + f ]
  end

  def get_rule
    loop do
      random = rand
      @rules.each { |r| return r if r[0].include?(random) }
    end
  end
end


if __FILE__ == $0
  require 'RMagick'

  def fractal_gif(name, width, height, color, imgs, pts_per_img, rules, transform)
    ifs = IFS.new(rules)
    imageList = Magick::ImageList.new
    image = Magick::Image.new(width, height)
    image.delay = 30
    imageList << image
    imgs.times do
      image = image.copy
      pts_per_img.times do
        x, y = transform[*ifs.get_next_point]
        image.pixel_color(x, y, color)
      end
      imageList << image
    end
    imageList.write(name + ".gif")
  end

  def fractal_jpg(name, width, height, color, points, rules, transform)
    ifs = IFS.new(rules)
    image = Magick::Image.new(width, height)
    points.times do
      x, y = transform[*ifs.get_next_point]
      image.pixel_color(x, y, color)
    end
    image.write(name + ".jpg")
  end

  puts "ifs_fern.jpg ..."
  fractal_jpg('ifs_fern', 300, 500, 'green', 70000,
              [ [0.01,  0.00,  0.00,  0.00, 0.16, 0, 0.00],
                [0.08,  0.20, -0.26,  0.23, 0.22, 0, 1.60],
                [0.08, -0.15,  0.28,  0.26, 0.24, 0, 0.44],
                [0.74,  0.75,  0.04, -0.04, 0.85, 0, 1.60]
              ],
              proc { |x, y| [x * 40 + 150, 450 - y * 40] }
             )

  puts "ifs_fern.gif ..."
  fractal_gif('ifs_fern', 300, 350, 'green', 50, 1000,
              # -5 <= x <= 5  and  0 <= y <= 10
              [ [0.01,  0.00,  0.00,  0.00, 0.16, 0, 0.00],
                [0.07,  0.20, -0.26,  0.23, 0.22, 0, 1.60],
                [0.07, -0.15,  0.28,  0.26, 0.24, 0, 0.44],
                [0.85,  0.85,  0.04, -0.04, 0.85, 0, 1.60]
              ],
              proc { |x, y| [x * 30 + 150, 325 - y * 30] }
             )

  puts "ifs_koch_curve.gif ..."
  fractal_gif('ifs_koch_curve', 400, 135, 'black', 50, 200,
              [ [0.25, 0.333,  0.000,  0.000, 0.333, 0.000, 0.000],
                [0.25, 0.167, -0.287,  0.287, 0.167, 0.333, 0.000],
                [0.25, 0.167,  0.287, -0.287, 0.167, 0.500, 0.287],
                [0.25, 0.333,  0.000,  0.000, 0.333, 0.667, 0.000]
              ],
              proc { |x, y| [x*400, 130 - y*400] }
             )

  puts "ifs_sierpinski_triangle.gif ..."
  fractal_gif('ifs_sierpinski_triangle', 400, 300, 'magenta', 50, 1000,
              [ [0.33, 0.5, 0, 0, 0.5, 0.0, 0.0],
                [0.33, 0.5, 0, 0, 0.5, 0.5, 0.5],
                [0.33, 0.5, 0, 0, 0.5, 1.0, 0.0]
              ],
              proc { |x, y| [x*200, 250 - y*200] }
             )

  puts "ifs_sierpinski_carpet.gif ..."
  fractal_gif('ifs_sierpinski_carpet', 200, 200, 'blue', 50, 500,
              [ [0.125, 0.333, 0, 0, 0.333, 0.000, 0.000],
                [0.125, 0.333, 0, 0, 0.333, 0.333, 0.000],
                [0.125, 0.333, 0, 0, 0.333, 0.666, 0.000],
                [0.125, 0.333, 0, 0, 0.333, 0.000, 0.333],
                [0.125, 0.333, 0, 0, 0.333, 0.000, 0.666],
                [0.125, 0.333, 0, 0, 0.333, 0.333, 0.666],
                [0.125, 0.333, 0, 0, 0.333, 0.666, 0.333],
                [0.125, 0.333, 0, 0, 0.333, 0.666, 0.666],
              ],
              proc { |x, y| [x*200, y*200] }
             )
end

ifs_koch_curve.gif
ifs_sierpinski_triangle.gif
ifs_sierpinski_carpet.gif
ifs_fern.gif

Rename JPEG files

Friday, June 15th, 2007

#!/usr/bin/env ruby -w
#---------------------------------------------------------------------#
#                                                                     #
#  Program   : renamejpg.rb  -  Rename JPEG files                     #
#  Object    : Rename all JPEG files on a directory.                  #
#              (Pure ruby code without any EXIF library)              #
#              The new name will be YYYYMMDDhhmmsscc.jpg              #
#              where YYYYMMDDhhmmss is date time on EXIF data and     #
#              cc is a counter for files having the same date time.   #
#  Author    : David Tran                                             #
#  Version   : 2007-06-15                                             #
#  Blog      : http://davidtran.doublegifts.com/blog/?p=74            #
#  Reference : http://www.exif.org/specifications.html                #
#                                                                     #
#---------------------------------------------------------------------#
def get_exif_time(file_name)  # DateTime of Exif: Tag=306 (132.H)
  open(file_name) do |f|
    f.binmode
    header_size = 12
    header = f.read(header_size)
    return nil if header !~ /\xFF\xD8\xFF\xE1..Exif\x00\x00/

    # MM (0×4D4D) for big-endian byte order; II (0×4949) for little
    buf = f.read(4)
    little_endian = (buf[0] == ?I)
    format2bytes, format4bytes = little_endian ? %w[v V] : %w[n N]

    # Skip to 0th IFD Offset
    buf = f.read(4)
    offset = buf.unpack(format4bytes)[0]
    f.seek(offset + header_size)

    # Number of Interoperability (tags)
    buf = f.read(2)
    tags = buf.unpack(format2bytes)[0]

    date_time_tag = 0×0132
    tags.times do
      buf = f.read(12)
      if buf.unpack(format2bytes)[0] == date_time_tag
        offset = buf[-4,4].unpack(format4bytes)[0]
        f.seek(offset + header_size)
        buf = f.read(19) # skip last null char
        # return Time.mktime(*buf.scan(/\d+/))
        return buf.scan(/\d+/)
      end
    end
  end
  return nil
end

def rename(file_name)
  raise "no Exif data" unless (exif_time = get_exif_time(file_name))
  exif_time = exif_time.join
  count = "00"
  loop do
    new_name = exif_time + count + ".jpg"
    if File.exist?(new_name)
      break file_name if file_name == new_name
      count = count.next
    else
      File.rename(file_name, new_name)
      break new_name
    end
  end
end

if __FILE__ == $0
  Dir.chdir(ARGV[0] || '.')
  renamed = error = 0
  Dir['*'].each do |f|
    begin
      puts "#{f} => #{rename(f)}"
      renamed += 1
    rescue
      puts "ERROR: #{f} => not jpeg file or no Exif data"
      error += 1
    end
  end
  puts
  puts "Total renamed : #{renamed}"
  puts "Total error : #{error}"
end

Script to help my WordPress blog

Friday, June 15th, 2007

#!/usr/bin/env ruby -w
#------------------------------------------------------------------------#
#                                                                        #
#  Program   : wpblog.rb                                                 #
#  Object    : Script to help WordPress blog.                            #
#              (1) Call vim to get html file with syntax highlight.      #
#              (2) Reformat to bypass WordPress auto-format.             #
#              (3) If platform is Win32 then copy result to              #
#                  system clipboard and delete html file generated.      #
#  Author    : David Tran                                                #
#  Version   : 2007-06-15                                                #
#  Blog      : http://davidtran.doublegifts.com/blog/?p=48               #
#  References:                                                           #
#      http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/92753 #
#      http://snippets.dzone.com/posts/show/3032                         #
#      http://snippets.dzone.com/posts/show/990                          #
#                                                                        #
#------------------------------------------------------------------------#
def reformat(lines)  # for WordPress
  new_lines = lines.map do |line| line.
    gsub(/'/,       "&##{?'};").
    gsub(/\\/,      "&##{?\\};").
    # gsub(/"/,     "&##{?"};").
    gsub(/--/,      "-&##{?-};").
    gsub(/\.\.\./,  "..&##{?.};")
  end

  new_lines.delete_at(0) while new_lines[0] !~ /^<pre>/
  new_lines[0].sub!(/^<pre>/, '<pre><code>')

  new_lines.delete_at(-1) while new_lines[-1] !~ /^<\/pre>/
  new_lines[-1].sub!(/^<\/pre>/, '</code></pre>')

  new_lines.each_index do |i|
    next unless new_lines[i] =~ /^\s*$/ && new_lines[i+1] =~ /^\s*$/
    new_lines[i] = "<br>" + new_lines[i]
  end

  new_lines
end

def copy_to_clipboard(data)
  require "win32/clipboard"
  # Win32::Clipboard.data = data
  Win32::Clipboard.set_data(data)
end

def win32?
  RUBY_PLATFORM =~ /win32/i
end

def html(file_name)
  `vim -f +"syn on" +"TOhtml" +"wq" +"q" #{file_name}`
  new_file = file_name + ".html"
  lines = reformat(IO.readlines(new_file))
  if win32?
    copy_to_clipboard(lines.join)
    File.delete(new_file)
  else
    open(new_file, 'w') { |f| f << lines }
  end

  # require 'tempfile'
  # new_file = Tempfile.new('html')
  # new_file.close
  # `vim -f +"syn on" +"run! syntax/2html" +"wq! #{new_file.path}" +"q" #{file_name}`
  # copy_to_clipboard(reformat(IO.readlines(new_file.path)).join)
end

if __FILE__ == $0
  file_name = ARGV[0]
  if !(file_name && File.file?(file_name))
    puts "Usage: #$0 file_name"
    exit 1
  else
    html(file_name)
  end
end

Ruby Quiz (#125) Fractals

Wednesday, May 30th, 2007

My solution for the Ruby Quiz (#125) Fractals.
(posted on Ruby-talk mailing list here.)

fractals.rb.gif


#---------------------------------------------------------------------#
#                                                                     #
#  Program   : Fractals (Ruby Quiz #125)                              #
#  Author    : David Tran                                             #
#  Date      : 2007-05-30                                             #
#  Blog      : http://davidtran.doublegifts.com/blog/?p=48            #
#  Reference : http://mathworld.wolfram.com/PerpendicularVector.html  #
#  Note      : Using vector calculation to compute each level's       #
#              points. The first level line can be in any direction.  #
#                                                                     #
#---------------------------------------------------------------------#
require 'enumerator'
require 'RMagick'

LEVEL_0 = [[0, 0], [350, 200]]

def next_level(polylines)
  polylines.enum_cons(2).inject([polylines.first]) do |array, (p1, p2)|
    x1, y1 = p1
    x2, y2 = p2
    a = (x2 - x1) / 3.0
    b = (y2 - y1) / 3.0
    array << [x1+a, y1+b] << [x1+a-b, y1+b+a] <<
      [x1+2*a-b, y1+2*b+a] << [x1+2*a, y1+2*b] << p2
  end
end

exit unless __FILE__ == $0
imageList = Magick::ImageList.new
level = LEVEL_0
(ARGV[0].to_i + 1).times do |i|
  level = next_level(level) if i > 0
  image = Magick::Image.new(400, 300)
  image.delay = 100
  draw = Magick::Draw.new
  draw.fill_opacity(0)
  draw.stroke('black')
  draw.polyline(*level)
  draw.text(300,100,"level #{i}")
  draw.draw(image)
  imageList << image
end
imageList.write(File.basename($0) + ".gif")

Ruby Quiz (#124) Magic Squares

Sunday, May 20th, 2007

My solution for the Ruby Quiz (#124) Magic Squares.
(posted on Ruby-talk mailing list here.)


#---------------------------------------------------------------#
#                                                               #
#  Program   : Magic Square                                     #
#  Author    : David Tran                                       #
#  Date      : 2007-05-20                                       #
#  Blog      : http://davidtran.doublegifts.com/blog/?p=27      #
#  Reference : http://mathworld.wolfram.com/MagicSquare.html    #
#                                                               #
#---------------------------------------------------------------#
class MagicSquare

  def initialize(size = 3)
    raise "Error: size must greater than 2." if size < 3
    @magic_square = if (size % 2 != 0)
      OddMagicSquare.new(size)
    elsif (size % 4 == 0)
      DoublyEvenMagicSquare.new(size)
    else
      SinglyEvenMagicSquare.new(size)
    end
  end

  def size
    @magic_square.size
  end

  def [](i,j)
    @magic_square[i,j]
  end

  def to_s
    digits = (size * size).to_s.size
    divider = '+' + '-' * ((digits + 2) * size + (size - 1)) + "+\n"
    (0...size).inject(divider) do |s, i|
      (0...size).inject(s + "|") do |s, j|
        s + " #{self[i,j].to_s.rjust(digits)} |"
      end + "\n" + divider
    end
  end

  def is_magic_square?
    size = self.size
    n = size * size

    array = Array.new(n)
    (0...size).each do |i|
      (0...size).each do |j|
        index = self[i,j] - 1
        return false if (index < 0) || (index >= n) || array[index]
        array[index] = true
      end
    end
    return false unless array.all?

    sum = size * (size * size + 1) / 2
    (0...size).each do |i|
      return false if sum != (0...size).inject(0) { |s,j| s + self[i,j] }
      return false if sum != (0...size).inject(0) { |s,j| s + self[j,i] }
    end
    return false if sum != (0...size).inject(0) { |s,i| s + self[i,i] }
    return false if sum != (0...size).inject(0) { |s,i| s + self[i, size-1-i] }
    true
  end

  private
  #------------------------------------------------------------------#
  class OddMagicSquare
    attr_reader :size

    def initialize(size)
      @size = size
      n = @size * @size
      @array = Array.new(n)
      i, j = 0, @size/2
      (1..n).each do |v|
        @array[get_index(i,j)] = v
        a, b = i-1, j+1
        i, j = self[a,b] ? [i+1, j] : [a, b]
      end
    end

    def [](i, j)
      @array[get_index(i,j)]
    end

    private
    def get_index(i, j)
      (i % @size) * @size + (j % @size)
    end
  end
  #------------------------------------------------------------------#
  class DoublyEvenMagicSquare
    attr_reader :size

    def initialize(size)
      @size = size
    end

    def [](i, j)
      i, j = i % @size, j % @size
      value = (i * @size) + j + 1
      i, j = i % 4, j % 4
      ((i == j) || (i + j == 3)) ? (@size*@size+1-value) : value
    end
  end
  #------------------------------------------------------------------#
  class SinglyEvenMagicSquare
    attr_reader :size

    L = [4, 1, 2, 3]
    U = [1, 4, 2, 3]
    X = [1, 4, 3, 2]

    def initialize(size)
      @size = size
      @odd_magic_square = MagicSquare.new(@size/2)
    end

    def [](i, j)
      i, j = i % @size, j % @size
      ii, jj = i / 2, j / 2
      center = @size / 2 / 2
      value = @odd_magic_square[ii, jj]
      case
        when ii < center then L
        when ii == center then (jj == center) ? U : L
        when ii == center+1 then (jj == center) ? L : U
        else X
      end [i%2*2 + j%2] + 4 * (value - 1)
    end
  end
  #------------------------------------------------------------------#
end

if __FILE__ == $0
  puts MagicSquare.new(ARGV[0].to_i)
end

Fibonacci number

Tuesday, April 24th, 2007

One-liner to find nth Fibonacci number:

ruby -e "p Hash.new{|h,n| n<2 ? 1 : h[n-1]+h[n-2]}[ARGV[0].to_i]"   [number]

Ruby Quiz (#119) Getting to 100

Tuesday, April 10th, 2007

My solution for the Ruby Quiz (#119) Getting to 100.
(posted on Ruby-talk mailing list here.)


# Reuse permutations method on my Ruby Quiz (#106) Chess960 solution.
def permutations(elements)
  return [elements] if elements.size <= 1
  result = []
  elements.uniq.each do |p|
    _elements = elements.dup
    _elements.delete_at(elements.index(p))
    permutations(_elements).each do |perm|
      result << (perm << p)
    end
  end
  result
end

def find(digits, operators, target)
  raise "Error: More operators than digits." if (digits.size <= operators.size)
  operators[digits.size - 2] = nil if (operators.size != digits.size - 1)
  found = 0
  stars = "*" * 25
  perm = permutations(operators)
  perm.each do |operator|
    expression = digits.zip(operator).flatten.join
    value = eval(expression)
    if value == target
      found += 1
      puts stars
      puts "#{expression} = #{value}"
      puts stars
    else
      puts "#{expression} = #{value}"
    end
  end
  puts "#{perm.size} possible equations tested."
  puts "#{found} equations satisfied."
end

if ($0 == __FILE__)
  digits    = ARGV[0] || "123456789"
  operators = ARGV[1] || "+--"
  target    = ARGV[2] || "100"

  if (digits =~ /[^1-9]/ || operators =~ /[^-+*\/]/)
    puts "Usage: #$0  digits  operators  target"
    exit
  end

  find(digits.split(//), operators.split(//).map{|e| " #{e} "}, target.to_i)
end