[QUIZ] Word Loop (#149)

The three rules of Ruby Quiz:

1. Please do not post any solutions or spoiler discussion for this quiz until
48 hours have passed from the time on this message.

2. Support Ruby Quiz by submitting ideas as often as you can:

http://www.rubyquiz.com/

3. Enjoy!

Suggestion: A [QUIZ] in the subject of emails about the problem helps everyone
on Ruby Talk follow the discussion. Please reply to the original quiz message,
if you can.

···

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

Here's a fun little challenge from the Educational Computing Organization of
Ontario.

Given a single word as input try to find a repeated letter inside of it such
that you can loop the text around and reuse that letter. For example:

  $ ruby word_loop.rb Mississippi
   i
   p
   p
  Mis
   ss
   si

or:

  $ ruby word_loop.rb Markham
  Ma
  ar
  hk

or:

  $ ruby word_loop.rb yummy
  yu
  mm

If a loop cannot be made, your code can just print an error message:

  $ ruby word_loop.rb Dana
  No loop.

Maybe it's because it's a Friday afternoon, but I don't understand the
quiz problem. Could someone who understands this try explaining it
(without, of course, discussing any code or even pseudo-code to
approach it).

I've looked over those examples a few times and I'm totally baffled.
What does "loop the text around and reuse that letter" mean?

···

On Dec 7, 1:45 pm, Ruby Quiz <ja...@grayproductions.net> wrote:

Given a single word as input try to find a repeated letter inside of it such
that you can loop the text around and reuse that letter. For example:

        $ ruby word_loop.rb Mississippi
         i
         p
         p
        Mis
         ss
         si

or:

        $ ruby word_loop.rb Markham
        Ma
        ar
        hk

or:

        $ ruby word_loop.rb yummy
        yu
        mm

I read this quiz on the mailing list in GMail and I didn't get this
challenge at first. Then I realized that you need to imagine the
examples in a fixed-width font.

Just FYI. :slight_smile:

···

On Dec 7, 2007 3:45 PM, Ruby Quiz <james@grayproductions.net> wrote:

The three rules of Ruby Quiz:

1. Please do not post any solutions or spoiler discussion for this quiz until
48 hours have passed from the time on this message.

2. Support Ruby Quiz by submitting ideas as often as you can:

http://www.rubyquiz.com/

3. Enjoy!

Suggestion: A [QUIZ] in the subject of emails about the problem helps everyone
on Ruby Talk follow the discussion. Please reply to the original quiz message,
if you can.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

Here's a fun little challenge from the Educational Computing Organization of
Ontario.

Given a single word as input try to find a repeated letter inside of it such
that you can loop the text around and reuse that letter. For example:

        $ ruby word_loop.rb Mississippi
         i
         p
         p
        Mis
         ss
         si

or:

        $ ruby word_loop.rb Markham
        Ma
        ar
        hk

or:

        $ ruby word_loop.rb yummy
        yu
        mm

If a loop cannot be made, your code can just print an error message:

        $ ruby word_loop.rb Dana
        No loop.

I presume allowing multiple overlaps is valid (if not encouraged?).
For example, "absolvable" can make use of three overlaps:

.....
.abs.
.vlo.
..e..
.....

And "chincherinchee", which is a South African perennial, can be
arranged with seven overlaps. I'll leave it as a mini-challenge you
can try....

Eric

···

====

On-site, hands-on Ruby training is available from http://LearnRuby.com
!

Here is my solution that tries to make all possible arrangements and
displays solutions with the maximum number of overlaps.

Eric

···

----

Are you interested in on-site Ruby training that uses well-designed,
real-world, hands-on exercises? http://LearnRuby.com

====

# Solution to Ruby Quiz #149 provided by LearnRuby.com
#
# The code to this solution can also be found at:
# http://learnruby.com/examples/ruby-quiz-149.shtml
#
# This solution tries to find all solutions and then selects those
# with the maximum number of overlaps. Good words to try are
# "absolvable" and "chincherinchee". It is implemented with a
# recursive search.

# The Grid class is used to hold a grid where each cell contains a
# character or, if it's empty, nil. Furthermore, the grid keeps track
# of how many overlaps there are in the word that spins within it,
# since that's the measure of how good the arrangement is.
class Grid
  attr_reader :overlaps

  def initialize
    @grid = Array.new
    @overlaps = 0
  end

  def [](position)
    sub_array(position[0])[position[1]]
  end

  def []=(position, value)
    sub_array(position[0])[position[1]] = value
  end

  def inc_overlaps
    @overlaps += 1
  end

  def dec_overlaps
    @overlaps -= 1
  end

  def to_s
    @grid.map do |row|
      if row
        row.map { |c| c || ' ' }.join('')
      else
        ''
      end
    end.join("\n")
  end

  def dup
    other = Grid.new
    @grid.each_with_index do |row, i|
      other.grid[i] = row.dup unless row.nil?
    end
    other.overlaps = @overlaps
    other
  end

  # Trim grid so it's as small as possible. Assumes that filled areas
  # are contiguous, so that an empty row would not sit between
  # non-empty rows.
  def trim!
    @grid.delete_if { |row| row.nil? || row.all? { |c| c.nil? } }
    trim_columns = empty_columns
    @grid.each_index do |i|
      @grid[i] = @grid[i][trim_columns..-1]
      @grid[i].pop while @grid[i].last.nil?
    end
    self
  end

  protected

  attr_reader :grid
  attr_writer :overlaps

  # Utility method that returns the sub-array of @grid at index.
  # Since the sub-array may have not yet been created, will create it
  # if necessary.
  def sub_array(index)
    sub_array = @grid[index]
    sub_array = @grid[index] = Array.new if sub_array.nil?
    sub_array
  end

  # returns the number of entries at the start of each sub-array that
  # are empty, so the grid can be "left-trimmed".
  def empty_columns
    index = 0
    index += 1 while @grid.all? { |row| row.nil? || row[index].nil? }
    index
  end
end

# This module contains some convenience methods to move a position in
# a direction, and to figure out a new direction when a right turn is
# made.
module Direction
  RightTurns = {
    [0, 1] => [1, 0],
    [1, 0] => [0, -1],
    [0, -1] => [-1, 0],
    [-1, 0] => [0, 1],
  }

  # returns the rightwards direction
  def self.rightwards
    [0, 1]
  end

  def self.turn_right(direction)
    RightTurns[direction]
  end

  def self.move(position, direction)
    [position[0] + direction[0], position[1] + direction[1]]
  end
end

# Recursively tries placing characters on grid until the full word is
# placed or a conflict is found. During the recursion we try two
# options -- continuing in same direction or making a right turn.
# Results are accumulated in result parameter, which is an array of
# Grids.
def find_loops(word, letter_index, grid, position, direction, result)
  new_position = Direction.move(position, direction)

  character_placed = false

  # one of three conditions -- cell is empty, cell contains a letter
  # that matches the current word letter, cell contains a letter that
  # doesn't match current word letter
  if grid[new_position].nil?
    # empty cell -- put character in
    grid[new_position] = word[letter_index, 1]
    character_placed = true
  elsif grid[new_position] == word[letter_index, 1]
    # cell with matching character, increment overlap count
    grid.inc_overlaps
  else
    # cell with non-matching character, quit searching this branch
    return
  end

  # have we placed the entire word; add to results, otherwise recurse
  # continuting in same direction and after making a right turn
  if letter_index == word.size - 1
    result << grid.dup
  else
    # try going in the same direction and also try making a right turn
    find_loops(word, letter_index + 1, grid,
               new_position, direction, result)
    find_loops(word, letter_index + 1, grid,
               new_position, Direction.turn_right(direction), result)
  end

  # restore the grid or overlap count depending on earlier condition
  if character_placed
    grid[new_position] = nil
  else
    grid.dec_overlaps
  end
end

# This is the entry point to the loop-finding process. It places
# first letter in a new grid and calls the recursive find_loops
# methods.
def solve(word)
  size = word.length
  grid = Grid.new

  # starting position allows room for word to go leftwards or upwards
  # without bumping into left or top edges; assumes initially heading
  # rightwards and only right turns can be made
  position = [size - 5, size - 4]
  direction = Direction.rightwards

  # put first character at starting position
  grid[position] = word[0, 1]

  # generate solutions in results parameter
  results = []
  find_loops(word, 1, grid, position, direction, results)
  results
end

# find the best results and display them
def display_best_results(results)
  # the best results have most overlaps, so sort so most overlaps
  # appear at front of array, and then use the first element's
  # overlaps as best score
  results = results.sort_by { |r| -r.overlaps }
  best_score = results.first.overlaps

  puts "Number of overlaps: #{best_score}"
  puts "=" * 40
  results.select { |r| r.overlaps == best_score }.each do |result|
    puts result.trim!
    puts "=" * 40
  end
end

if $0 == __FILE__
  if ARGV.size == 1
    display_best_results(solve(ARGV[0].downcase))
  else
    $stderr.puts "Usage: #{$0} _word_"
    exit 1
  end
end

The three rules of Ruby Quiz:

1. Please do not post any solutions or spoiler discussion for this quiz
until 48 hours have passed from the time on this message.

2. Support Ruby Quiz by submitting ideas as often as you can:

http://www.rubyquiz.com/

3. Enjoy!

Suggestion: A [QUIZ] in the subject of emails about the problem helps
everyone on Ruby Talk follow the discussion. Please reply to the
original quiz message, if you can.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

···

On Fri, 07 Dec 2007 15:45:02 -0500, Ruby Quiz wrote:
=-=-=-=-=

Here's a fun little challenge from the Educational Computing
Organization of Ontario.

Given a single word as input try to find a repeated letter inside of it
such that you can loop the text around and reuse that letter. For
example:

  $ ruby word_loop.rb Mississippi
   i
   p
   p
  Mis
   ss
   si

or:

  $ ruby word_loop.rb Markham
  Ma
  ar
  hk

or:

  $ ruby word_loop.rb yummy
  yu
  mm

If a loop cannot be made, your code can just print an error message:

  $ ruby word_loop.rb Dana
  No loop.

def loopword word
  matchinfo=
    word.match(/(.*?)(.)(.(?:..)+?)\2(.*)/i)
  if matchinfo
    _,before,letter,looplets,after=matchinfo.to_a
    pad=" "*before.size
    after.reverse.split(//).each{|l| puts pad+l}
    looplets=looplets.split(//)
    puts before+letter+looplets.shift
    until looplets.empty?
      puts pad+looplets.pop+looplets.shift
    end
  else
    puts "No loop."
  end
end

loopword "Mississippi"
puts
loopword "Markham"
puts
loopword "yummy"
puts
loopword "Dana"
puts
loopword "Organization"

outputs:

i
p
p
Mis
ss
si

Ma
ar
hk

yu
mm

No loop.

n
Or
ig
ta
an
zi

(The last is a testcase which makes sure that I get the #shifts and #pops
right)

--
Ken (Chanoch) Bloom. PhD candidate. Linguistic Cognition Laboratory.
Department of Computer Science. Illinois Institute of Technology.
http://www.iit.edu/~kbloom1/

My solution...

def find_knot letters #returns first and last index which become a "knot"
  letters.each_with_index do |letter1, ind1|
    (ind1+4).upto(letters.length - 1) do |ind2|
      if (ind2 - ind1) % 2 == 0
        if letters[ind2].casecmp(letter1) == 0
          return [ind1, ind2]
        end
      end
    end
  end
  return nil
end

def loop word
  letters = word.split(//)
  first, last = find_knot letters
  if first.nil?
    puts "No loop"
  else
    (letters.length-1).downto(last+1) { |i|
      print " "*first
      puts letters[i]
    }
    print letters[0..first+(last-first)/2-1]
    puts " "*first
    first.times {print " "}
    (last-1).downto(first+(last-first)/2) { |i| print letters[i] }
    puts
  end
end

loop "Mississippi"
puts
loop "Markham"
puts
loop "Yummy"
puts
loop "Dana"

Ruby Quiz wrote:

···

The three rules of Ruby Quiz:

1. Please do not post any solutions or spoiler discussion for this quiz until
48 hours have passed from the time on this message.

2. Support Ruby Quiz by submitting ideas as often as you can:

http://www.rubyquiz.com/

3. Enjoy!

Suggestion: A [QUIZ] in the subject of emails about the problem helps everyone
on Ruby Talk follow the discussion. Please reply to the original quiz message,
if you can.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

Here's a fun little challenge from the Educational Computing Organization of
Ontario.

Given a single word as input try to find a repeated letter inside of it such
that you can loop the text around and reuse that letter. For example:

  $ ruby word_loop.rb Mississippi
   i
   p
  Mis
   ss
   si

or:

  $ ruby word_loop.rb Markham
  Ma
  ar
  hk

or:

  $ ruby word_loop.rb yummy
  yu
  mm

If a loop cannot be made, your code can just print an error message:

  $ ruby word_loop.rb Dana
  No loop.

OK, here is my solution to this extremly fun quiz! :slight_smile:

#!/usr/bin/ruby

class Quiz149
  def initialize(word)
    @word = word
    @pos = 0 # currently observed char
    @knots = # current knots positions
    @combos = {} # a set of knot combos found so far
    @size = @word.length*2 - 1
    @arr = Array.new(@size) { Array.new(@size, ?.) } # a size x size
of dots
    @hist = # position history
  end

  def (x, y)
    @arr[y]
  end

  def =(x, y, c)
    @arr[y] = c
  end

  def print
    @arr.each { |line| puts line.map{ |c| c.chr }.join }
    puts
  end

  def length
    @word.length
  end

  def loop(x = self.length - 1, y = self.length - 1)
    @hist.push([x, y])
    c = self[x, y]
    self[x, y] = @word[@pos]
    @pos += 1
    if @pos >= length # reached end of the word
      if !@knots.empty?
        self.print unless @combos[@knots]
        @combos[@knots] = true
      end
    else
      looptry(x + 1, y ) # right
      looptry(x, y - 1) # up
      looptry(x - 1, y ) # left
      looptry(x, y + 1) # down
    end
    @pos -= 1
    self[x, y] = c
    @hist.pop()
  end

  def no_loop? # was there any solution?
    @combos.empty?
  end

···

On Dec 7, 10:45 pm, Ruby Quiz <ja...@grayproductions.net> wrote:

Here's a fun little challenge from the Educational Computing Organization of
Ontario.

Given a single word as input try to find a repeated letter inside of it such
that you can loop the text around and reuse that letter. For example:

        $ ruby word_loop.rb Mississippi
         i
         p
         p
        Mis
         ss
         si

######################################################################
  private

  def looptry(x, y)
    # could not make this look any uglier :wink:
    return if @hist.size >= 2 && x == @hist[-2][0] && y == @hist[-2]
[1]

    c = @word[@pos]
    f = self[x, y]
    if f == c || f == ?.
      @knots.push(@pos) if f == c
      loop(x, y)
      @knots.pop() if f == c
    end
  end
end

STDIN.each do |line|
  quiz = Quiz149.new(line.chomp.downcase)
  quiz.loop()
  puts "No loop." if quiz.no_loop?
end

I think it finds all of the possible solutions. A set of already
known solutions is kept to track down the equivalent ones and do not
print them.

It outputs something like this:

$ ./loop.rb
mississippi
.....................
.....................
.....................
.....................
.....................
.....................
.....................
.....................
.....................
..............ppi....
..........mississ....
.....................
.....................
.....................
.....................
.....................
.....................
.....................
.....................
.....................
.....................

.....................
.....................
.....................
.....................
.....................
.....................
.....................
.....................
.....................
...........ssi.......
..........miss.......
...........ppi.......
.....................
.....................
.....................
.....................
.....................
.....................
.....................
.....................
.....................

.....................
.....................
.....................
.....................
.....................
.....................
.....................
.....................
...........pi........
...........psi.......
..........miss.......
.....................
.....................
.....................
.....................
.....................
.....................
.....................
.....................
.....................
.....................

.....................
.....................
.....................
.....................
.....................
.....................
.....................
.....................
.....................
............si.......
..........miss.......
............ippi.....
.....................
.....................
.....................
.....................
.....................
.....................
.....................
.....................
.....................

markham
.............
.............
.............
.............
.............
......ahk....
......mar....
.............
.............
.............
.............
.............
.............

.............
.............
.............
.............
.............
.......hk....
......mar....
.............
.............
.............
.............
.............
.............

.............
.............
.............
.............
.............
.......hk....
......mar....
.......m.....
.............
.............
.............
.............
.............

yummy
.........
.........
.........
....mm...
....yu...
.........
.........
.........
.........

dana
No loop.

Of course, it could be tweaked to remove the unneeded dots when
printing, however I pretty much love it this way. :wink:

--
Alex Shulgin

Gavin Kistner wrote:

···

On Dec 7, 1:45 pm, Ruby Quiz <ja...@grayproductions.net> wrote:

        yu
        mm

Maybe it's because it's a Friday afternoon, but I don't understand the
quiz problem. Could someone who understands this try explaining it
(without, of course, discussing any code or even pseudo-code to
approach it).

I've looked over those examples a few times and I'm totally baffled.
What does "loop the text around and reuse that letter" mean?

I agree, I'm lost as well...
--
Posted via http://www.ruby-forum.com/\.

I've looked over those examples a few times and I'm totally baffled.
What does "loop the text around and reuse that letter" mean?

Imagine your word as a string. You're trying to make a knot, just a fold
really, in the string, and the bit where the string folds over itself is
a repeated letter.

- donald

> Given a single word as input try to find a repeated letter inside of it such
> that you can loop the text around and reuse that letter. For example:

> $ ruby word_loop.rb Mississippi
> i
> p
> p
> Mis
> ss
> si

> or:

> $ ruby word_loop.rb Markham
> Ma
> ar
> hk

> or:

> $ ruby word_loop.rb yummy
> yu
> mm

Maybe it's because it's a Friday afternoon, but I don't understand the
quiz problem. Could someone who understands this try explaining it
(without, of course, discussing any code or even pseudo-code to
approach it).

I've looked over those examples a few times and I'm totally baffled.
What does "loop the text around and reuse that letter" mean?

Ah, got it.

[y] -> [u]
^ |
> v
[m] <- [m]

[M] -> [a]
^ |
> v
[a] [r]
^ |
> v
[h] <- [k]

       [i]
        ^
        >
       [p]
        ^
        >
       [p]
        ^
        >
[M] -> [i] -> [s]
        ^ |
        > v
       [s] [s]
        ^ |
        > v
       [s] <- [i]

···

On Dec 7, 2:27 pm, Phrogz <phr...@mac.com> wrote:

On Dec 7, 1:45 pm, Ruby Quiz <ja...@grayproductions.net> wrote:

I wish I read read that before I wasted too much time staring at the
examples. Thanks.

···

On Dec 7, 2007 3:30 PM, Christian von Kleist <cvonkleist@gmail.com> wrote:

On Dec 7, 2007 3:45 PM, Ruby Quiz <james@grayproductions.net> wrote:
> The three rules of Ruby Quiz:
>
> 1. Please do not post any solutions or spoiler discussion for this quiz
until
> 48 hours have passed from the time on this message.
>
> 2. Support Ruby Quiz by submitting ideas as often as you can:
>
> http://www.rubyquiz.com/
>
> 3. Enjoy!
>
> Suggestion: A [QUIZ] in the subject of emails about the problem helps
everyone
> on Ruby Talk follow the discussion. Please reply to the original quiz
message,
> if you can.
>
>
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
>
> Here's a fun little challenge from the Educational Computing
Organization of
> Ontario.
>
> Given a single word as input try to find a repeated letter inside of it
such
> that you can loop the text around and reuse that letter. For example:
>
> $ ruby word_loop.rb Mississippi
> i
> p
> p
> Mis
> ss
> si
>
> or:
>
> $ ruby word_loop.rb Markham
> Ma
> ar
> hk
>
> or:
>
> $ ruby word_loop.rb yummy
> yu
> mm
>
> If a loop cannot be made, your code can just print an error message:
>
> $ ruby word_loop.rb Dana
> No loop.
>
>

I read this quiz on the mailing list in GMail and I didn't get this
challenge at first. Then I realized that you need to imagine the
examples in a fixed-width font.

Just FYI. :slight_smile:

Phrogz wrote:

Given a single word as input try to find a repeated letter inside of it such
that you can loop the text around and reuse that letter. For example:

        $ ruby word_loop.rb Mississippi
         i
         p
        Mis
         ss
         si

<snip>

Maybe it's because it's a Friday afternoon, but I don't understand the
quiz problem. Could someone who understands this try explaining it
(without, of course, discussing any code or even pseudo-code to
approach it).

It took me a few monents too. The letters have to be arranged in a grid so that by moving one square at a time, the word is spelled out. The test is to see whether the word can be spelled out by, at some point, moving over the same grid square twice.

I'm taking from the MISSISSIPI example that moves have to be in the same direction as the last move, or at a 90 degree angle to it, otherwise it could be spelled by moving up back onto "S" after the second "I" in the bottom right.

a

···

On Dec 7, 1:45 pm, Ruby Quiz <ja...@grayproductions.net> wrote:

I apologize for all of the confusion. I had trouble explaining this one, which is why I pretty much punted and showed a bunch of examples. :wink:

There are some better visual descriptions in this thread now though, so hopefully everyone gets it now.

James Edward Gray II

···

On Dec 7, 2007, at 3:30 PM, Phrogz wrote:

Maybe it's because it's a Friday afternoon, but I don't understand the
quiz problem. Could someone who understands this try explaining it
(without, of course, discussing any code or even pseudo-code to
approach it).

Here is my solution. It finds the longest cycle possible.

Joe L

input = ARGV.first

size = input.length
size -= 1 if size % 2 == 0

a = nil

in2 = input.downcase
while !a && size > 2 do
  a = in2.index(/(.).{#{size}}(\1)/)
  size -= 2 if !a
end

if a then
  b = a + size + 1
  (input.length-1).downto(b+1) do |i|
    puts input[i,1].rjust(a+1)
  end
  puts input[0,a+2]
  space = a
  a += 2
  b -= 1
  while a < b do
    puts "#{"".rjust(space)}#{input[b,1]}#{input[a,1]}"
    a += 1
    b -= 1
  end
else
  puts "No Loop."
end

That's just awesome. Great find!

James Edward Gray II

···

On Dec 9, 2007, at 1:50 PM, Eric I. wrote:

I presume allowing multiple overlaps is valid (if not encouraged?).
For example, "absolvable" can make use of three overlaps:

.....
.abs.
.vlo.
..e..
.....

Here is my first solution. By default, it finds the smallest circle.
If you remove one line, it will find all simple loops.

Thomas.

#!/usr/bin/env ruby
# Author:: Thomas Link (micathom AT gmail com)
# Created:: 2007-12-08.

class Quiz149
    Solution = Struct.new(:idx, :width, :height)

    def initialize(word)
        @word = word
        @wordic = word.downcase
        @wordsize = word.size
        @solutions = []
    end

    def find_solutions
        for height in 1 .. (@wordsize - 4)
            for width in 1 .. (@wordsize - 2 * height - 2)
                for idx in 0 .. (@wordsize - 2 * height - 2 * width -
1)
                    if @wordic[idx] == @wordic[idx + width * 2 +
height * 2]
                        @solutions << Solution.new(idx, width, height)
                        return self # Remove this line to find all
solutions
                    end
                end
            end
        end
        self
    end

    def print_solutions
        if @solutions.empty?
            puts 'No loop.'
            puts
        else
            @solutions.each_with_index do |sol, sol_idx|
                canvas_x = sol.idx + sol.width + 1
                canvas_y = @wordsize - sol.idx - sol.height - 2 *
sol.width
                canvas = Array.new(canvas_y) {' ' * canvas_x}
                pos_x = -1
                pos_y = canvas_y - sol.height - 1
                @word.scan(/./).each_with_index do |char, i|
                    if i <= sol.idx + sol.width
                        pos_x += 1
                    elsif i <= sol.idx + sol.width + sol.height
                        pos_y += 1
                    elsif i <= sol.idx + 2 * sol.width + sol.height
                        pos_x -= 1
                    else
                        pos_y -= 1
                    end
                    if canvas[pos_y][pos_x] == 32
                        canvas[pos_y][pos_x] = char
                    end
                end
                puts canvas.join("\n")
                puts
            end
        end
        self
    end

end

if __FILE__ == $0
    if ARGV.empty?
        Quiz149.new('Mississippi').find_solutions.print_solutions
        Quiz149.new('Markham').find_solutions.print_solutions
        Quiz149.new('yummy').find_solutions.print_solutions
        Quiz149.new('Dana').find_solutions.print_solutions
    else
        ARGV.each {|w| Quiz149.new(w).find_solutions.print_solutions}
    end
end

My second solution uses a recursive algorithm and provides a curses
interface to show all sorts of knots/loops.

Thomas.

#!/usr/bin/env ruby
# Author:: Thomas Link (micathom AT gmail com)
# Created:: 2007-12-08.

···

#
# Nervous letters movie. If you run the script with -c, only clock-
wise
# permutations will be shown.

require 'curses'

class NervousLetters
    class << self
        def solve_clockwise(word)
            @@directions = {
                :right => [ 1, 0, :right, :down],
                :left => [-1, 0, :left, :up],
                :up => [ 0, -1, :right, :up],
                :down => [ 0, 1, :left, :down],
            }
            solve(word)
        end

        def solve_any(word)
            @@directions = {
                :right => [ 1, 0, :right, :up, :down],
                :left => [-1, 0, :left, :up, :down],
                :up => [ 0, 1, :right, :left, :up],
                :down => [ 0, -1, :right, :left, :down],
            }
            solve(word)
        end

        def solve(word)
            Curses.init_screen
            Curses.noecho
            Curses.curs_set(0)
            begin
                @@solutions = []
                @@stepwise = true
                pos0 = word.size + 1
                @@canvas_size = pos0 * 2
                NervousLetters.new([], ':', word.scan(/./),
                                   pos0, pos0, :right,
                                   false, true)
            ensure
                Curses.curs_set(1)
                Curses.close_screen
            end
            if @@solutions.empty?
                puts 'No loop.'
            else
                puts "#{@@solutions.size} solutions."
            end
        end
    end

    attr_reader :letters, :letter, :pos_x, :pos_y

    def initialize(letters, letter, word, pos_x, pos_y, direction,
has_knot, at_knot)
        @letters = letters.dup << self
        @letter = letter
        @pos_x = pos_x
        @pos_y = pos_y
        if word.empty?
            new_solution if has_knot
        else
            @word = word.dup
            @next_letter = @word.shift
            @has_knot = has_knot
            _, _, *turns = @@directions[direction]
            turns.each do |turn|
                next if at_knot and turn != direction
                dx, dy, _ = @@directions[turn]
                try_next(pos_x + dx, pos_y + dy, turn)
            end
        end
    end

    def try_next(pos_x, pos_y, direction)
        has_knot = false
        @letters.each do |nervous|
            if pos_x == nervous.pos_x and pos_y == nervous.pos_y
                if @next_letter.downcase != nervous.letter.downcase
                    return
                else
                    has_knot = true
                    break
                end
            end
        end
        NervousLetters.new(@letters, @next_letter, @word,
                           pos_x, pos_y, direction,
                           @has_knot || has_knot, has_knot)
    end

    def new_solution
        @@solutions.last.draw(self) unless @@solutions.empty?
        draw
        @@solutions << self
        if @@stepwise
            Curses.setpos(@@canvas_size + 1, 0)
            Curses.addstr('-- PRESS ANY KEY (q: quit, r: run) --')
        end
        Curses.refresh
        if @@stepwise
            ch = Curses.getch
            case ch
            when ?q
                exit
            when ?r
                @@stepwise = false
            end
        else
            # sleep 0.1
        end
    end

    def draw(eraser=nil)
        consumed = []
        @letters.each do |nervous|
            if eraser
                next if eraser.letters.include?(nervous)
                letter = ' '
            else
                letter = nervous.letter
            end
            yx = [nervous.pos_y, nervous.pos_x]
            unless consumed.include?(yx)
                Curses.setpos(*yx)
                Curses.addstr(letter)
                consumed << yx
            end
        end
    end

end

if __FILE__ == $0
    case ARGV[0]
    when '--clockwise', '-c'
        clockwise = true
        ARGV.shift
    else
        clockwise = false
    end
    for word in ARGV
        if clockwise
            NervousLetters.solve_clockwise(word)
        else
            NervousLetters.solve_any(word)
        end
    end
end

Here is the much-less-clever code used to create the quiz:

#!/usr/bin/env ruby -wKU

word = ARGV.first or abort "Usage: #{File.basename($PROGRAM_NAME)} WORD"
if word.downcase =~ /([a-z]).(?:.{2})+\1/
   before, during, after = word[0, $`.length],
                           word[$`.length, $&.length],
                           word[-$'.length, $'.length]
   indent = " " * before.length
   after.split("").reverse_each { |char| puts indent + char }
   puts before + during[0..1]
   ((during.length - 3) / 2).times do |i|
     puts indent + during[-(i + 2), 1] + during[2 + i, 1]
   end
else
   puts "No loop."
end

__END__

James Edward Gray II

···

On Dec 9, 2007, at 1:50 PM, Eric I. wrote:

Here is my solution that tries to make all possible arrangements and
displays solutions with the maximum number of overlaps.

I thought this one is too easy once you understand what it is about
but people always come up with difficult solutions. I wish I knew what
the regexp does :S

For those who cannot understand it either:

def indexes l, i
  res =
  ptr = 0
  while (ptr = l.index i, ptr)
    res << ptr
    ptr+=1
  end
  res
end
# .
#.c1=c5..c2
# . .
# c4.....c3
def draw_loop word, c1, c5
  length = (c5 - c1) -4
  width = length/4
  height = length/2 - width
  word = word[0..0].upcase + word[1..-1]
  c2 = c1 + width +1
  c3 = c2 + height +1
  c4 = c3 + width +1
  word[(c5+1)..-1].reverse.scan(/./).map{|c| " "*c1 + c + "\n"}.join +
    word[0..c2] + "\n" +
    (1..height).map{|i| " "*c1 + word[c5 - i].chr + " "*width +
word[c2 + i].chr + "\n"}.join +
    " "*c1 + word[c3..c4].reverse + "\n"
end
def wloop word
  word=word.downcase
  tried=
  ptr=0
  while ptr < word.length
    if tried.include? word[ptr]
      ptr+=1
      next
    end
    char = word[ptr]
    tried << char
    pos = indexes word, char
    next unless pos.length > 1
    i, j = 0
    while i < pos.length - 1
      j=pos.length - 1
      while j > i
        diff = pos[j] - pos[i]
        if (diff)>=4 && (diff) % 2 == 0
          return draw_loop word, pos[i], pos[j]
        end
        j-=1
      end
      i+=1
    end
    ptr += 1
  end
  "No loop \n"
end

···

On 09/12/2007, Ken Bloom <kbloom@gmail.com> wrote:

def loopword word
  matchinfo=
    word.match(/(.*?)(.)(.(?:..)+?)\2(.*)/i)
  if matchinfo
    _,before,letter,looplets,after=matchinfo.to_a
    pad=" "*before.size
    after.reverse.split(//).each{|l| puts pad+l}
    looplets=looplets.split(//)
    puts before+letter+looplets.shift
    until looplets.empty?
      puts pad+looplets.pop+looplets.shift
    end
  else
    puts "No loop."
  end
end

loopword "Mississippi"
puts
loopword "Markham"
puts
loopword "yummy"
puts
loopword "Dana"
puts
loopword "Organization"

outputs:

i
p
p
Mis
ss
si

Ma
ar
hk

yu
mm

No loop.

n
Or
ig
ta
an
zi

(The last is a testcase which makes sure that I get the #shifts and #pops
right)