Word ladder

Yet another shortest path problem. Given two words of equal length the task is to transpose the first into the second.

Only one letter may be changed at a time and the change must result in a word in unixdict, the minimum number of intermediate words should be used.

Demonstrate the following:

A boy can be made into a man: boy -> bay -> ban -> man

With a little more difficulty a girl can be made into a lady: girl -> gill -> gall -> gale -> gaze -> laze -> lazy -> lady

A john can be made into a jane: john -> cohn -> conn -> cone -> cane -> jane

A child can not be turned into an adult.

Optional transpositions of your choice.

Solution:
require "set"

Words = File.open("unixdict.txt").read.split("\n").
  group_by { |w| w.length }.map { |k, v| [k, Set.new(v)] }.
  to_h

def word_ladder(from, to)
  raise "Length mismatch" unless from.length == to.length
  sized_words = Words[from.length]
  work_queue = [[from]]
  used = Set.new [from]
  while work_queue.length > 0
    new_q = []
    work_queue.each do |words|
      last_word = words[-1]
      new_tails = Enumerator.new do |enum|
        ("a".."z").each do |replacement_letter|
          last_word.length.times do |i|
            new_word = last_word.clone
            new_word[i] = replacement_letter
            next unless sized_words.include? new_word and
                        not used.include? new_word
            enum.yield new_word
            used.add new_word
            return words + [new_word] if new_word == to
          end
        end
      end
      new_tails.each do |t|
        new_q.push(words + [t])
      end
    end
    work_queue = new_q
  end
end

[%w<boy man>, %w<girl lady>, %w<john jane>, %w<child adult>].each do |from, to|
  if ladder = word_ladder(from, to)
    puts ladder.join " → "
  else
    puts "#{from} into #{to} cannot be done"
  end
end


Word wrap

Even today, with proportional fonts and complex layouts, there are still cases where you need to wrap text at a specified column.

;Basic task:
The basic task is to wrap a paragraph of text in a simple way in your language.

If there is a way to do this that is built-in, trivial, or provided in a standard library, show that.  Otherwise implement the minimum length greedy algorithm from Wikipedia.

Show your routine working on a sample of text at two different wrap columns.

;Extra credit:
Wrap text using a more sophisticated algorithm such as the Knuth and Plass TeX algorithm.
If your language provides this, you get easy extra credit,
but you ''must reference documentation'' indicating that the algorithm
is something better than a simple minimum length algorithm.

If you have both basic and extra credit solutions, show an example where
the two algorithms give different results.

Solution:
class String
  def wrap(width)
    txt = gsub("\n", " ")
    para = []
    i = 0
    while i < length
      j = i + width
      j -= 1 while j != txt.length && j > i + 1 && !(txt[j] =~ /\s/)
      para << txt[i ... j]
      i = j + 1
    end
    para
  end
end

text = <<END
In olden times when wishing still helped one, there lived a king
whose daughters were all beautiful, but the youngest was so beautiful
that the sun itself, which has seen so much, was astonished whenever
it shone in her face.  Close by the king's castle lay a great dark
forest, and under an old lime-tree in the forest was a well, and when
the day was very warm, the king's child went out into the forest and
sat down by the side of the cool fountain, and when she was bored she
took a golden ball, and threw it up on high and caught it, and this
ball was her favorite plaything.
END

[72,80].each do |w|
  puts "." * w
  puts text.wrap(w)
end


World Cup group stage

It's World Cup season (or at least it was when this page was created)!

The World Cup is an international football/soccer tournament that happens every 4 years.   Countries put their international teams together in the years between tournaments and qualify for the tournament based on their performance in other international games.   Once a team has qualified they are put into a group with 3 other teams.

For the first part of the World Cup tournament the teams play in "group stage" games where each of the four teams in a group plays all three other teams once.   The results of these games determine which teams will move on to the "knockout stage" which is a standard single-elimination tournament.   The two teams from each group with the most standings points move on to the knockout stage.

Each game can result in a win for one team and a loss for the other team or it can result in a draw/tie for each team.
:::*   A win is worth three points.
:::*   A draw/tie is worth one point.
:::*   A loss is worth zero points.

;Task:
:*   Generate all possible outcome combinations for the six group stage games.   With three possible outcomes for each game there should be 36 = 729 of them.
:*   Calculate the standings points for each team with each combination of outcomes.
:*   Show a histogram (graphical,   ASCII art, or straight counts--whichever is easiest/most fun) of the standings points for all four teams over all possible outcomes.

Don't worry about tiebreakers as they can get complicated.   We are basically looking to answer the question "if a team gets x standings points, where can they expect to end up in the group standings?".

''Hint: there should be no possible way to end up in second place with less than two points as well as no way to end up in first with less than three.   Oddly enough, there is no way to get 8 points at all.''

Solution:
teams = [:a, :b, :c, :d]
matches = teams.combination(2).to_a
outcomes = [:win, :draw, :loss]
gains = {win:[3,0], draw:[1,1], loss:[0,3]}
places_histogram = Array.new(4) {Array.new(10,0)}

# The Array#repeated_permutation method generates the 3^6 different
# possible outcomes
outcomes.repeated_permutation(6).each do |outcome|
  results = Hash.new(0)

  # combine this outcomes with the matches, and generate the points table
  outcome.zip(matches).each do |decision, (team1, team2)|
    results[team1] += gains[decision][0]
    results[team2] += gains[decision][1]
  end

  # accumulate the results
  results.values.sort.reverse.each_with_index do |points, place|
    places_histogram[place][points] += 1
  end
end

fmt = "%s :" + "%4s"*10
puts fmt % [" ", *0..9]
puts fmt % ["-", *["---"]*10]
places_histogram.each.with_index(1) {|hist,place| puts fmt % [place, *hist]}


Write float arrays to a text file

Write two equal-sized numerical arrays 'x' and 'y' to
a two-column text file named 'filename'.

The first column of the file contains values from an 'x'-array with a
given 'xprecision', the second -- values from 'y'-array with 'yprecision'.

For example, considering:
    x = {1, 2, 3, 1e11};
    y = {1, 1.4142135623730951, 1.7320508075688772, 316227.76601683791};
                                                           /* sqrt(x) */
    xprecision = 3;
    yprecision = 5;

The file should look like:
    1    1
    2    1.4142
    3    1.7321
    1e+011   3.1623e+005

This task is intended as a subtask for [[Measure relative performance of sorting algorithms implementations]].

Solution:
# prepare test data
x = [1, 2, 3, 1e11]
y = x.collect { |xx| Math.sqrt xx }
xprecision = 3
yprecision = 5

# write the arrays
open('sqrt.dat', 'w') do |f|
  x.zip(y) { |xx, yy| f.printf("%.*g\t%.*g\n", xprecision, xx, yprecision, yy) }
end

# print the result file
open('sqrt.dat', 'r') { |f| puts f.read }


Write language name in 3D ASCII

Write/display a language's name in '''3D''' ASCII.

(We can leave the definition of "3D ASCII" fuzzy,
so long as the result is interesting or amusing,
not a cheap hack to satisfy the task.)

;Related tasks:
* draw a sphere
* draw a cuboid
* draw a rotating cube
* draw a Deathstar

Solution:
text = <<EOS
####        #
#   #       #
#   #       #
####  #  #  ###  #   #
# #   #  #  #  #  # #
#  #  #  #  #  #   #
#   #  ###  ###   #
                 #
                #
EOS

def banner3D_1(text, shift=-1)
  txt = text.each_line.map{|line| line.gsub('#','__/').gsub(' ','   ')}
  offset = Array.new(txt.size){|i| " " * shift.abs * i}
  offset.reverse! if shift < 0
  puts offset.zip(txt).map(&:join)
end
banner3D_1(text)

puts
# Other display:
def banner3D_2(text, shift=-2)
  txt = text.each_line.map{|line| line.chomp + ' '}
  offset = txt.each_index.map{|i| " " * shift.abs * i}
  offset.reverse! if shift < 0
  txt.each_with_index do |line,i|
    line2 = offset[i] + line.gsub(' ','   ').gsub('#','///').gsub('/ ','/\\')
    puts line2, line2.tr('/\\\\','\\\\/')
  end
end
banner3D_2(text)

puts
# Another display:
def banner3D_3(text)
  txt = text.each_line.map(&:rstrip)
  offset = [*0...txt.size].reverse
  area = Hash.new(' ')
  box = [%w(/ / / \\), %w(\\ \\ \\ /)]
  txt.each_with_index do |line,i|
    line.each_char.with_index do |c,j|
      next if c==' '
      x = offset[i] + 2*j
      box[0].each_with_index{|c,k| area[[x+k,i  ]] = c}
      box[1].each_with_index{|c,k| area[[x+k,i+1]] = c}
    end
  end
  (xmin, xmax), (ymin, ymax) = area.keys.transpose.map(&:minmax)
  puts (ymin..ymax).map{|y| (xmin..xmax).map{|x| area[[x,y]]}.join}
end

banner3D_3 <<EOS
####         #
#   #        #
#   #        #
#   #        #
####   #  #  ####  #     #
# #    #  #  #   #  #   #
#  #   #  #  #   #   # #
#   #  #  #  #   #    #
#    #  ###  ####    #
                    #
                   #
EOS


Yellowstone sequence

The '''Yellowstone sequence''', also called the '''Yellowstone permutation''', is defined as:

For n <= 3,

    a(n) = n

For n >= 4,

    a(n) = the smallest number not already in sequence such that a(n) is relatively prime to a(n-1) and
           is not relatively prime to a(n-2).

The sequence is a permutation of the natural numbers, and gets its name from what its authors felt was a spiking, geyser like appearance of a plot of the sequence.

;Example:
a(4) is 4 because 4 is the smallest number following 1, 2, 3 in the sequence that is relatively prime to the entry before it (3), and is not relatively prime to the number two entries before it (2).

;Task
: Find and show as output the first  '''30'''  Yellowstone numbers.

;Extra
:  Demonstrate how to plot, with x = n and y coordinate a(n), the first 100 Yellowstone numbers.

;Related tasks:
:*   Greatest common divisor.
:*   Plot coordinate pairs.

;See also:
:*   The OEIS entry:   A098550 The Yellowstone permutation.
:*   Applegate et al, 2015: The Yellowstone Permutation [https://arxiv.org/abs/1501.01669].

Solution:
def yellow(n)
  a = [1, 2, 3]
  b = { 1 => true, 2 => true, 3 => true }
  i = 4
  while n > a.length
    if !b[i] && i.gcd(a[-1]) == 1 && i.gcd(a[-2]) > 1
      a << i
      b[i] = true
      i = 4
    end
    i += 1
  end
  a
end

p yellow(30)


Zeckendorf number representation

Just as numbers can be represented in a positional notation as sums of multiples of the powers of ten (decimal) or two (binary); all the positive integers can be represented as the sum of one or zero times the distinct members of the Fibonacci series.

Recall that the first six distinct Fibonacci numbers are:  1, 2, 3, 5, 8, 13.

The decimal number eleven can be written as 0*13 + 1*8 + 0*5 + 1*3 + 0*2 + 0*1 or 010100 in positional notation where the columns represent multiplication by a particular member of the sequence. Leading zeroes are dropped so that 11 decimal becomes 10100.

10100 is not the only way to make 11 from the Fibonacci numbers however;  0*13 + 1*8 + 0*5 + 0*3 + 1*2 + 1*1 or 010011 would also represent decimal 11. For a true Zeckendorf number there is the added restriction that ''no two consecutive Fibonacci numbers can be used'' which leads to the former unique solution.

;Task:
Generate and show here a table of the Zeckendorf number representations of the decimal numbers zero to twenty, in order.

The intention in this task to find the Zeckendorf form of an arbitrary integer.  The Zeckendorf form can be iterated by some bit twiddling rather than calculating each value separately but leave that to another separate task.

;Also see:
*   OEIS A014417   for the the sequence of required results.
*   Brown's Criterion - Numberphile

;Related task:
*   [[Fibonacci sequence]]

Solution:
def zeckendorf
  return to_enum(__method__) unless block_given?
  x = 0
  loop do
    bin = x.to_s(2)
    yield bin unless bin.include?("11")
    x += 1
  end
end

zeckendorf.take(21).each_with_index{|x,i| puts "%3d: %8s"% [i, x]}


Zeckendorf number representation

Solution:
def zeckendorf(n)
  return 0 if n.zero?
  fib = [1,2]
  fib << fib[-2] + fib[-1] while fib[-1] < n
  dig = ""
  fib.reverse_each do |f|
    if f <= n
      dig, n = dig + "1", n - f
    else
      dig += "0"
    end
  end
  dig.to_i
end

for i in 0..20
  puts '%3d: %8d' % [i, zeckendorf(i)]
end


Zeckendorf number representation

Solution:
def zeckendorf(n)
  0.step.lazy.map { |x| x.to_s(2) }.reject { |z| z.include?("11") }.first(n)
end

zeckendorf(21).each_with_index{ |x,i| puts "%3d: %8s"% [i, x] }


Zero to the zero power

Some computer programming languages are not exactly consistent   (with other computer programming languages)
when   ''raising zero to the zeroth power'':     00

;Task:
Show the results of raising   zero   to the   zeroth   power.

If your computer language objects to      '''0**0'''      or      '''0^0'''      at compile time,   you may also try something like:
            x = 0
            y = 0
            z = x**y
            say  'z='  z

'''Show the result here.'''
And of course use any symbols or notation that is supported in your computer programming language for exponentiation.

;See also:
* The Wiki entry: Zero to the power of zero.
* The Wiki entry: Zero to the power of zero: History.
* The MathWorld(tm) entry: exponent laws.
** Also, in the above MathWorld(tm) entry, see formula ('''9'''): x^0=1.
* The OEIS entry: The special case of zero to the zeroth power

Solution:
require 'bigdecimal'

[0, 0.0, Complex(0), Rational(0), BigDecimal("0")].each do |n|
  printf "%10s: ** -> %s\n" % [n.class, n**n]
end


Zhang-Suen thinning algorithm

Solution:
class ZhangSuen
  NEIGHBOUR8 = [[-1,0],[-1,1],[0,1],[1,1],[1,0],[1,-1],[0,-1],[-1,-1]]  # 8 neighbors
  CIRCULARS = NEIGHBOUR8 + [NEIGHBOUR8.first]                       # P2, ... P9, P2
  def initialize(str, black="#")
    s1 = str.each_line.map{|line| line.chomp.each_char.map{|c| c==black ? 1 : 0}}
    s2 = s1.map{|line| line.map{0}}
    xrange = 1 ... s1.size-1
    yrange = 1 ... s1[0].size-1
    printout(s1)
    begin
      @r = 0
      xrange.each{|x| yrange.each{|y| s2[x][y] = s1[x][y] - zs(s1,x,y,1)}}  # Step 1
      xrange.each{|x| yrange.each{|y| s1[x][y] = s2[x][y] - zs(s2,x,y,0)}}  # Step 2
    end until @r == 0
    printout(s1)
  end
  def zs(ng,x,y,g)
    return 0 if ng[x][y] == 0 or                                    # P1
               (ng[x-1][y] + ng[x][y+1] + ng[x+g][y-1+g]) == 3 or   # P2, P4, P6/P8
               (ng[x-1+g][y+g] + ng[x+1][y] + ng[x][y-1]) == 3      # P4/P2, P6, P8
    bp1 = NEIGHBOUR8.inject(0){|res,(i,j)| res += ng[x+i][y+j]}     # B(P1)
    return 0 if bp1 < 2 or 6 < bp1
    ap1 = CIRCULARS.map{|i,j| ng[x+i][y+j]}.each_cons(2).count{|a,b| a<b}   # A(P1)
    return 0 if ap1 != 1
    @r = 1
  end
  def printout(image)
    puts image.map{|row| row.map{|col| " #"[col]}.join}
  end
end

str = <<EOS
...........................................................
.#################...................#############.........
.##################...............################.........
.###################............##################.........
.########.....#######..........###################.........
...######.....#######.........#######.......######.........
...######.....#######........#######.......................
...#################.........#######.......................
...################..........#######.......................
...#################.........#######.......................
...######.....#######........#######.......................
...######.....#######........#######.......................
...######.....#######.........#######.......######.........
.########.....#######..........###################.........
.########.....#######.######....##################.######..
.########.....#######.######......################.######..
.########.....#######.######.........#############.######..
...........................................................
EOS

ZhangSuen.new(str)

task_example = <<EOS
00000000000000000000000000000000
01111111110000000111111110000000
01110001111000001111001111000000
01110000111000001110000111000000
01110001111000001110000000000000
01111111110000001110000000000000
01110111100000001110000111000000
01110011110011101111001111011100
01110001111011100111111110011100
00000000000000000000000000000000
EOS

ZhangSuen.new(task_example, "1")