[QUIZ] String Equations (#112)

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.

···

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

This quiz was adapted from an ACM programming challenge at the suggestion of
Gavin Kistner.

Let's define two operators for a simple set of string equations.

First, we will use the plus (+) operator and keep Ruby's meaning of
concatenation for it. Therefore, "james" + "gray" is "jamesgray".

The other operator we will support is equality (==). Two strings will be
considered equal if they are anagrams (they contain the same letters in a
possibly different ordering). In other words, "cinema" == "iceman".

Using these two operators we can build equations:

  "i" + "am" + "lord" + "voldemort" == "tom" + "marvolo" + "riddle"

This week's quiz is to write a program that accepts a list of words on STDIN and
outputs any one string equation using those words, assuming there is one. Each
word in the list may be used zero or more times, but only on one side of the
equation. The program should exit(0) on success and exit(1), without printing
any output, if a solution cannot be found.

Let's sidestep case sensitivity issues by lower casing all incoming words. You
can also remove all non-alphanumeric characters.

Posting word lists and/or equations is not spoiler material.

cool quiz.

I'm currently having trouble with this evil word list:
bwu
ha
bwuhahahahahaha

···

--
Posted via http://www.ruby-forum.com/.

James Gray

one. Each
word in the list may be used zero or more times, but only on one side of
the
equation.

Shall we also add the requirement that at least one word is used at
least once? :slight_smile:

Daniel Lucraft

···

--
Posted via http://www.ruby-forum.com/\.

Ruby Quiz (James E. Gray):

This week's quiz is to write a program that accepts a list of words on STDIN and
outputs any one string equation using those words, assuming there is one. Each
word in the list may be used zero or more times, but only on one side of the
equation. The program should exit(0) on success and exit(1), without printing
any output, if a solution cannot be found.

Shouldn't there be a requirement that an equations is invalid if there is
another, shorter equation that uses the same words? Otherwise:

  $ ruby quiz112.rb foo oof
  "foo" = "oof"
  "foo" + "foo" = "oof" + "oof"
  "foo" + "foo" + "foo" = "oof" + "oof" + "oof"
  ...

Kalman

Here is my solution.
It generates 26 equations (1 per character) in matrix form.
If the nullity of this matrix is zero then the only solution is the
zero solution (no words on either side), which is invalid.
If it is > 0 then there are infinitely many solutions, any vector of
integers in the null space corresponds to one of them.

The way to generate such a solution is:
(1) Find a vector in the null space using Rationals
(2) Multiply it with large enough number (least common multiple of the
denominators) such that all the Rationals turn into integers.
(3) All the negative numbers are counts for words on the left side,
positive for words on the right side.

Step (1) would take more time than I have right now, so I tried
searching for a library. But anything that calculates a basis for the
null space has no support for Rationals and using floats makes step
(2) pretty much impossible. So my code just returns if an equation
exists instead of generating one.

One last remark about my code: it uses Matrix#rank, which seems to be
bugged. To run it you may need to patch your matrix.rb file (see
http://www.ruby-forum.com/topic/96387)

code:

require 'matrix'

class String
  def to_freq
    ('a'..'z').map{|c| Rational(count(c)) } # use Rational for
stability in calculating rank
  end
end

def has_string_eqn(words)
  table = words.uniq.map{|w| w.downcase.gsub(/[^a-z]/,'').to_freq }.transpose
  nullity = words.length - Matrix[*table].rank
  return nullity > 0
end

I like it. :wink:

James Edward Gray II

···

On Feb 2, 2007, at 10:43 AM, Daniel Lucraft wrote:

cool quiz.

I'm currently having trouble with this evil word list:
bwu
ha
bwuhahahahahaha

Yes, see my post about this a little earlier.

James Edward Gray II

···

On Feb 2, 2007, at 11:59 AM, Daniel Lucraft wrote:

James Gray

one. Each
word in the list may be used zero or more times, but only on one side of
the
equation.

Shall we also add the requirement that at least one word is used at
least once? :slight_smile:

Here are the tests I'm using:
http://pastie.caboo.se/37546

Started
....
Finished in 0.735 seconds.
4 tests, 14 assertions, 0 failures, 0 errors

Tests are boolean only because my solution doesn't do anything else...

···

On 2/2/07, Daniel Lucraft <dan@fluentradical.com> wrote:

cool quiz.

I'm currently having trouble with this evil word list:
bwu
ha
bwuhahahahahaha

Kalman Noel wrote:

Shouldn't there be a requirement that an equations is invalid if there
is
another, shorter equation that uses the same words? Otherwise:

  $ ruby quiz112.rb foo oof
  "foo" = "oof"
  "foo" + "foo" = "oof" + "oof"
  "foo" + "foo" + "foo" = "oof" + "oof" + "oof"
  ...

Kalman

That sounds right. How about if you have an equation a == b, you should
not be able to find equations c == d, and e == f such that c + e makes a
and d + f makes b?

Daniel

···

--
Posted via http://www.ruby-forum.com/\.

If you want to make shorter equations a goal for your solution, go for it. This is not a requirement of the quiz though.

James Edward Gray II

···

On Feb 4, 2007, at 4:45 AM, Kalman Noel wrote:

Ruby Quiz (James E. Gray):

This week's quiz is to write a program that accepts a list of words on STDIN and
outputs any one string equation using those words, assuming there is one. Each
word in the list may be used zero or more times, but only on one side of the
equation. The program should exit(0) on success and exit(1), without printing
any output, if a solution cannot be found.

Shouldn't there be a requirement that an equations is invalid if there is
another, shorter equation that uses the same words? Otherwise:

  $ ruby quiz112.rb foo oof
  "foo" = "oof"
  "foo" + "foo" = "oof" + "oof"
  "foo" + "foo" + "foo" = "oof" + "oof" + "oof"
  ...

Sander Land wrote:

Step (1) would take more time than I have right now, so I tried

I guess I have too much time on my hands :slight_smile: This is a solver for
homogenous systems of linear equations: http://pastie.caboo.se/37827

It reduces the matrix to row echelon form, and then reads off the
solutions from the non-unitary columns, multiplying them up to make them
integral. It's probably not that robust in the case of degenerate or
edge case matrices, but it works for solving the word equations.
Obviously there are infinite solutions, so it returns the largest set of
linearly independent solutions. It was fun to make, I had to dig out the
linear algebra textbook I've had since my undergrad days.

And this is the solution that uses it. It does exactly the same thing as
Sander's in making the matrix, and then just uses the linear solver and
translates the solutions into word equations.

Fun quiz!

Daniel

# WordEquations

···

#
# $ cat test.txt
# i
# am
# lord
# voldemort
# tom
# marvolo
# riddle
# $ cat test.txt | ruby quiz112.rb
# i + am + lord + voldemort == tom + marvolo + riddle
#
# Prints equation closest to 80 chars in length.
#
# Also:
# $ cat test.txt | ruby quiz112.rb shortest
# $ cat test.txt | ruby quiz112.rb longest
# $ cat test.txt | ruby quiz112.rb all
#
# Generates word equations by solving the equivalent
# homogenous linear equations. E.g.
#
# a = hohoho
# b = h
# c = oo
# d = ho
# ->
# h's: 3a + b + + d = 0
# o's: 3a + 2c + d = 0
# ->
# solutions:
# [-2, 6, 3, 0] & [-1, 0, 0, 3]
# ->
# hohoho+hohoho == h+h+h+h+h+h+oo+oo+oo
# hohoho == ho+ho+ho

require 'homogenouslinearsolver'

class WordEquations
  def self.word_problem(words)
    words = words.map {|word| word.downcase.match(/\w+/); $&}.uniq
    words = words.select{|w| w}

    rows = ('a'..'z').map do |l|
      words.map {|word| Rational(word.count(l), 1) }
    end
    solutions = HomogenousLinearSolver.solve(Matrix[*rows])

    # if solution value is negative, add the word to the left side,
    # otherwise add to the right.
    solutions.collect do |sol|
      left_words =
      right_words =
      sol.each_with_index do |val, i|
        if val < 0
          left_words += [words[i]]*(val*-1)
        elsif val > 0
          right_words += [words[i]]*val
        end
      end
      left_words.join(" + ") +
        " == " + right_words.join(" + ")
    end
  end
end

if ARGV[0] == "test"
  require 'test/unit'

  class String
    def sorted
      (unpack('c*').sort).pack('c*')
    end
  end

  class TestWordEquations < Test::Unit::TestCase
    def test_hohoho
      words = %w{hohoho h oo}
      assert_equal ["hohoho + hohoho == h + h + h + h + h + h + oo + oo
+ oo"],
      WordEquations.word_problem(words)
    end

    def test_get_words_harry_potter
      words = %w{i am lord voldemort tom marvolo riddle}
      assert_equal ["i + am + lord + voldemort == tom + marvolo +
riddle"],
      WordEquations.word_problem(words)
    end

    def test_get_words_me
      words = %w{daniel lucraft fluent radical}
      assert_equal ["daniel + lucraft == fluent + radical"],
      WordEquations.word_problem(words)
    end

    def test_get_words_evil!
      words = %w{bwu ha bwuhahahahaha}
      assert_equal ["bwu + ha + ha + ha + ha + ha == bwuhahahahaha"],
      WordEquations.word_problem(words)
    end

    def test_get_words_1
      words = %w{dormitory dirty room}
      assert_equal ["dormitory == dirty + room"],
      WordEquations.word_problem(words)
    end

    def test_get_words_2
      words = %w{statue of liberty built to stay free}
      assert_equal ["statue + of + liberty == built + to + stay +
free"],
      WordEquations.word_problem(words)
    end

    def test_get_words_3
      words = %w{hohoho h oo ho}
      assert_equal ["hohoho + hohoho == h + h + h + h + h + h + oo + oo
+ oo",
                    "hohoho == ho + ho + ho"],
WordEquations.word_problem(words)
    end

    def test_no_solutions
      words = %w{foo bar}
      assert_equal , WordEquations.word_problem(words)
    end

    def test_long
      words = %w[My experience in Amsterdam is that cyclists ride where
the hell they like and aim in a state of rage at all pedestrians while
ringing their bell loudly, the concept of avoiding people being foreign
to them. My dream holiday would be a ticket to Amsterdam, immunity from
prosecution and a baseball bat- Terry Pratchett]
      results = WordEquations.word_problem(words).sort_by{|eq|
-eq.length}
      result = results[0]
      assert_equal "my + my + my + my + my + my + my + my + my + my + my
+ "+
        "my + my + my + my + my + my + my + my + my + my + my + my + my
+ "+
        "my + my + my + my + my + my + my + my + my + my + in + in + in
+ "+
        "in + in + in + in + in + is + is + is + is + is + is + is + is
+ "+
        "is + is + is + is + is + that + that + that + that + ride +
ride + "+
        "ride + ride + ride + ride + ride + ride + ride + the + the +
the + "+
        "the + the + the + the + the + the + the + the + the + the + the
+ "+
        "the + the + the + the + the + the + the + the + the + the + the
+ "+
        "the + the + the + the + the + the + the + the + the + the + a +
a + "+
        "a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a +
a + "+
        "a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a +
a + "+
        "a + a + loudly + loudly + loudly + loudly + concept + concept +
"+
        "concept + concept == amsterdam + amsterdam + amsterdam + "+
        "amsterdam + amsterdam + cyclists + cyclists + hell + hell +
hell + "+
        "they + they + they + they + they + they + they + they + they +
"+
        "they + they + they + they + they + they + they + they + they +
"+
        "they + they + they + they + they + they + they + they + they +
"+
        "they + they + they + they + they + they + they + they + they +
"+
        "and + and + and + and + and + and + and + and + aim + aim + aim
+ "+
        "aim + aim + aim + aim + aim + aim + aim + aim + aim + aim + aim
+ "+
        "aim + aim + aim + aim + aim + aim + aim + aim + aim + aim + "+
        "prosecution + prosecution + prosecution + prosecution",
        result
      sides = result.split("==")
      sides.map! {|side| side.delete("+").delete(" ")}
      left = sides[0]
      right = sides[1]
      assert left.sorted == right.sorted
    end
  end
else
  results = WordEquations.word_problem(STDIN.read.split).sort_by{|eq|
-eq.length}
  if results ==
    exit(1)
  else
    if ARGV[0] == "longest"
      puts results.first
    elsif ARGV[0] == "shortest"
      puts results.last
    elsif ARGV[0] == "all"
      results.each {|r| puts r; puts}
    else
      puts results.sort_by{|eq| (80-eq.length).abs}.first
    end
    exit(0)
  end
end

--
Posted via http://www.ruby-forum.com/\.

Sander Land wrote:

Here are the tests I'm using:
http://pastie.caboo.se/37546

Started
....
Finished in 0.735 seconds.
4 tests, 14 assertions, 0 failures, 0 errors

Tests are boolean only because my solution doesn't do anything else...

Thanks Sander that was useful in finding some bugs! I've added what
should be the longest equation possible from your test_long word list:
http://pastie.caboo.se/37659

Daniel Lucraft

···

--
Posted via http://www.ruby-forum.com/\.

Wouldn't a prolog script be better at describing all the rules than trying to figuring them out?

If someone developed a prolog script that would behave as the final ruby script should, we would then have the specs without a shadow of doubt.

Just don't look at me to write it =P

Paulo Jorge Duarte Köch
paulo.koch@gmail.com

···

On 2007/02/04, at 11:17, Daniel Lucraft wrote:

That sounds right. How about if you have an equation a == b, you should
not be able to find equations c == d, and e == f such that c + e makes a
and d + f makes b?

Wow. That's all I can think to say. :wink:

James Edward Gray II

···

On Feb 4, 2007, at 11:12 AM, Daniel Lucraft wrote:

Sander Land wrote:

Step (1) would take more time than I have right now, so I tried

I guess I have too much time on my hands :slight_smile: This is a solver for
homogenous systems of linear equations: http://pastie.caboo.se/37827

If you don't have this requirement, you have either none or an infinite
number of solution - if a solution a==b exists, the ist also the solution
a+a==b+b (and so on). So, only the minimal equations should count.

hli

···

On Mon, 5 Feb 2007 01:29:32 +0900, James Edward Gray II <james@grayproductions.net> wrote:

Shouldn't there be a requirement that an equations is invalid if
there is
another, shorter equation that uses the same words? Otherwise:

  $ ruby quiz112.rb foo oof
  "foo" = "oof"
  "foo" + "foo" = "oof" + "oof"
  "foo" + "foo" + "foo" = "oof" + "oof" + "oof"
  ...

If you want to make shorter equations a goal for your solution, go
for it. This is not a requirement of the quiz though.

--
"Only two things are infinite, the universe and human stupidity, and I'm not sure about the former."
-- Albert Einstein

My solution to the quiz below belongs to this same family of
solutions. I, like Daniel, actually solved the set of equations via
matrix transformations, although my code is self-contained and quick
'n dirty, not nicely refactored like his.

When multiple families of word equation solutions exist, my program
chooses one of them randomly to display.

Eric

···

----------------
Are you interested in on-site Ruby training that's been highly
reviewed by former students? http://LearnRuby.com

================

require 'mathn'

CompactOutput = false

# calculate the least common multiple of one or more numbers
def lcm(first, *rest)
  rest.inject(first) { |l, n| l.lcm(n) }
end

# Returns nil if there is no solution or an array containing two
# elements, one for the left side of the equation and one for the
# right side. Each of those elements is itself an array containing
# pairs, where each pair is an array in which the first element is the
# number of times that word appears and the second element is the
# word.
def solve_to_array(words)
  # clean up word list by eliminating non-letters, converting to lower
  # case, and removing duplicate words
  words.map! { |word| word.downcase.gsub(/[^a-z]/, '') }.uniq!

  # calculate the letters used in the set of words
  letters = Hash.new
  words.each do |word|
    word.split('').each { |letter| letters[letter] = true }
  end

  # create a matrix to represent a set of linear equations.
  column_count = words.size
  row_count = letters.size
  equations = []
  letters.keys.each do |letter|
    letter_counts = []
    words.each { |word| letter_counts << word.count(letter) }
    equations << letter_counts
  end

  # transform matrix into row echelon form
  equations.size.times do |row|
    # re-order the rows, so the row with a value in then next column
    # to process is above those that contain zeroes
    equations.sort! do |row1, row2|
      column = 0
      column += 1 until column == column_count ||
        row2[column].abs != row1[column].abs
      if column == column_count : 0
      else row2[column].abs <=> row1[column].abs
      end
    end

    # figure out which column to work on
    column = (0...column_count).detect { |i| equations[row][i] != 0 }
    break unless column

    # transform rows below the current row so that there is a zero in
    # the column being worked on
    ((row + 1)...equations.size).each do |row2|
      factor = -equations[row2][column] / equations[row][column]
      (column...column_count).each do |c|
        equations[row2][c] += factor * equations[row][c]
      end
    end
  end

  # only one of the free variables chosen randomly will get a 1, the
  # rest 0
  rank = equations.select { |row| row.any? { |v| v != 0 }}.size
  free = equations[0].size - rank
  free_values = Array.new(free, 0)
  free_values[rand(free)] = 2 * rand(2) - 1

  values = Array.new(equations[0].size) # holds the word_counts

  # use backward elimination to find values for the variables; process
  # each row in reverse order
  equations.reverse_each do |row|
    # determine number of free variables for the given row
    free_variables = (0...column_count).inject(0) do |sum, index|
      row[index] != 0 && values[index].nil? ? sum + 1 : sum
    end

    # on this row, 1 free variable will be calculated, the others will
    # get the predetermined free values; the one being calculated is
    # marked with nil
    free_values.insert(rand(free_variables), nil) if free_variables >
0

    # assign values to the variables
    sum = 0
    calc_index = nil
    row.each_index do |index|
      if row[index] != 0
        if values[index].nil?
          values[index] = free_values.shift

          # determine if this is a calculated or given free value
          if values[index] : sum += values[index] * row[index]
          else calc_index = index
          end
        else
          sum += values[index] * row[index]
        end
      end
    end
    # calculate the remaining value on the row
    values[calc_index] = -sum / row[calc_index] if calc_index
  end

  if values.all? { |v| v } && values.any? { |v| v != 0 }
    # in case we ended up with any non-integer values, multiply all
    # values by their collective least common multiple of the
    # denominators
    multiplier =
      lcm(*values.map { |v| v.kind_of?(Rational) ? v.denominator :
1 })
    values.map! { |v| v * multiplier }

    # deivide the terms into each side of the equation depending on
    # whether the value is positive or negative
    left, right = [], []
    values.each_index do |i|
      if values[i] > 0 : left << [values[i], words[i]]
      elsif values[i] < 0 : right << [-values[i], words[i]]
      end
    end

    [left, right] # return found equation
  else
    nil # return no found equation
  end
end

# Returns a string containing a solution if one exists; otherwise
# returns nil. The returned string can be in either compact or
# non-compact form depending on the CompactOutput boolean constant.
def solve_to_string(words)
  result = solve_to_array(words)
  if result
    if CompactOutput
      result.map do |side|
        side.map { |term| "#{term[0]}*\"#{term[1]}\"" }.join(' + ')
      end.join(" == ")
    else
      result.map do |side|
        side.map { |term| (["\"#{term[1]}\""] * term[0]).join(' +
') }.
          join(' + ')
      end.join(" == ")
    end
  else
    nil
  end
end

if __FILE__ == $0 # if run from the command line...
  # collect words from STDIN
  words = []
  while line = gets
    words << line.chomp
  end

  result = solve_to_string(words)

  if result : puts result
  else exit 1
  end
end

here's my solution for this quiz
It's quite slow, so you may want to tweak LENGTH_LIMIT constant to
have the script running in a reasonable time.
If you want to use this script to find equations for all the terms in
wikipedia you may want to look at some other solution.
It should behave as required from the original quiz.
I don't think it respects all the rules added after the original quiz
requirement :slight_smile:

With this word list

bici
bwu
ha
bwuhahahahahaha
boci
cibo
bibo
bocibici
bibobici
ci
bo

produces 166 equations

Any comment is really welcome. Thanks for the fun.

#!/usr/bin/env ruby
require 'set'
words = $stdin
#a version of hash that supports + - / %
class AlgHash < Hash
  def initialize(default = 0)
    super(default)
  end
  def keys
    super.to_set
  end
  alias len length
  %w(+ - % /).each do |sign|
    eval <<-END_EVAL
      def #{sign}(other)
        res = AlgHash.new
        total_keys = self.keys + other.keys
        total_keys.each {|k| res[k] = self[k] #{sign} other[k]}
        res
      end
    END_EVAL
  end
  def base?(other)
    other = other.signature unless other.respond_to? :keys
    return false unless self.keys == other.keys
    res = other % self
    if res.values.uniq == [0]
      if (multiplier = (other / self).values.uniq).size == 1
        multiplier.first
      end
    end
  end
end
#some utility modules we want for our special version of string
#and for Arrays
#we want to have some methods used in Set available for arrays and string
module SpecComp

  def multiple?(other)
    return false unless (self.len % other.len) == 0
    if (self.signature % other.signature).values.uniq == [0] &&
      (multiplier = (self.signature / other.signature).values.uniq).size == 1
      multiplier.first
    else
      false
    end
  end

  def base?(other)
    other.multiple?(self)
  end

  def minus(other)
    (self.signature - other.signature).delete_if {|k, v| v == 0}
  end

  %w(subset? superset? intersection).each do |comp_method|
    eval <<-END_EVAL
      def #{comp_method}(other)
        return self.origin.send("#{comp_method}", other.origin)
      end
    END_EVAL
  end

end
#a rich indexed version of string
#every string is lowercase and non alphanumeric chars are stripped
#every string has a signature which is hash with letters as keys and number
#of occurrences of the letter as values
#some arithmetics is possible on the signature of the strings
#the string reply to some Set class method that will be useful when we'll
#compare string groups
class HashedString
  attr_accessor :signature, :origin, :len
  include SpecComp
  def initialize(string)
    @signature = AlgHash.new
    sane_string = string.downcase.unpack('c*')
    sane_string.delete_if {|c| c < 49 || c > 122}
    sane_string.each {|c| @signature[c] = @signature[c] + 1}
    @len = sane_string.length
    @sym = sane_string.pack('c*').to_sym
    @origin = [@sym].to_set
  end

  def ==(other)
    self.signature == other.signature && self.origin != other.origin
  end

  def +(other)
    [self] + other
  end

  def *(integer)
    ret = []
    integer.times {ret += self}
    ret
  end

  def to_s
    return "\"#{@sym.to_s}\""
  end

  def to_ary
    [self]
  end

  def to_sym
    @sym
  end
end
#Array have signature too
class Array
  include SpecComp
  def to_s
    (self.map {|w| w.to_s}).join(' + ')
  end
  def signature
    @signature ||= self.inject(AlgHash.new) {|sum, element| sum +
element.signature}
  end
  def origin
    @origin ||= (self.map {|element| element.to_sym}).to_set
  end
  def len
    @len ||= self.inject(0) {|len, element| len + element.len}
  end
end
#the anagram finder
#LENGTH_LIMIT is necessary if you don't want your PC busy for years
class EquationFinder
  LENGTH_LIMIT = 1000
  attr_reader :success
  def initialize(words_list)
    @words_list = words_list.to_a.map! {|w| HashedString.new(w)}
    @search_hash = Hash.new() {|h, k| h[k] = []}
      @words_list.each_with_index do |word, index|
      @search_hash[word.signature.keys.to_a.sort] << word
      to_add = []
      @search_hash.each_value do |other_words|
        other_words.each do |other_word|
          if !word.subset?(other_word) && (other_word.origin.size <
LENGTH_LIMIT)
            sum = word + other_word
            to_add << sum
          end
        end
      end
      to_add.each {|v| @search_hash[v.signature.keys.to_a.sort] << v}
    end
  end
  def write_equation(left_string, right_string)
    @success = 0 unless @success
    puts left_string.to_s + " == " + right_string.to_s
  end
  def find
    @search_hash.each_value do |homogeneus_words_list|
      homogeneus_words_list.size.times do
        word = homogeneus_words_list.pop
        find_equation_in_array(word, homogeneus_words_list)
      end
    end
  end
  def find_equation_in_array(word, array)
    array.each { |other_word| equation(word, other_word) }
  end
  def equation(left, right)
    if right.intersection(left).empty?
      if left == right
        write_equation(left, right)
      elsif multiplier = left.multiple?(right)
        write_equation(left, right * multiplier)
      elsif multiplier = left.base?(right)
        write_equation(left * multiplier, right)
      else
        try_other_formulas(left, right)
      end
    end
  end
  def try_other_formulas(left, right)
    short, long = [left, right].sort! {|a, b| a.len <=> b.len}
    short = [short] if short.instance_of? HashedString
    #begin
      return false if (short.collect {|o| o.len}).min > (long.len/2)
    #rescue
    # p short
    # p short.origin
    # raise
    #end
    difference = (long.minus short)
    short.each do |short_origin|
      if multiplier = (short_origin.signature.base? difference)
        write_equation((short + short_origin * multiplier), long) if
multiplier > 0
      end
    end
  end
end
finder = EquationFinder.new($stdin)
finder.find
exit(finder.success || 1)

I'll feed the datapoint pool as well using my solution. Here is the
output of multiple runs using the Terry Pratchett quote that appeared
earlier in the thread. To make it slightly easier to interpret, I'm
using a more compact form (that deviates slightly from the quiz
requirements) to output the word equations.

1*"my" + 2*"the" == 1*"they" + 1*"them"

2*"my" + 1*"amsterdam" + 3*"ride" + 2*"where" + 1*"the" + 1*"hell" +
2*"and" + 2*"aim" == 2*"in" + 1*"is" + 2*"they" + 2*"while" +
6*"dream"

2*"amsterdam" + 1*"is" + 3*"hell" + 5*"they" + 1*"aim" + 6*"bat" ==
5*"my" + 3*"that" + 2*"ride" + 5*"the" + 2*"at" + 3*"baseball"

2*"my" + 7*"amsterdam" + 5*"is" + 27*"the" + 1*"hell" + 4*"and" +
8*"aim" + 4*"pratchett" == 8*"that" + 2*"cyclists" + 7*"ride" + 18*"a"
+ 4*"pedestrians" + 24*"them"

5*"cyclists" + 16*"the" + 3*"like" + 7*"and" + 6*"aim" + 7*"to" +
7*"pratchett" == 3*"amsterdam" + 7*"is" + 14*"that" + 4*"ride" +
4*"hell" + 5*"they" + 7*"concept" + 3*"ticket"

6*"in" + 11*"amsterdam" + 13*"is" + 18*"they" + 2*"and" + 6*"while" +
8*"pratchett" == 14*"my" + 16*"that" + 4*"cyclists" + 5*"ride" +
6*"where" + 9*"the" + 1*"hell" + 8*"aim" + 8*"pedestrians"

Eric

···

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

Shouldn't there be a requirement that an equations is invalid if
there is
another, shorter equation that uses the same words? Otherwise:

  $ ruby quiz112.rb foo oof
  "foo" = "oof"
  "foo" + "foo" = "oof" + "oof"
  "foo" + "foo" + "foo" = "oof" + "oof" + "oof"
  ...

If you want to make shorter equations a goal for your solution, go
for it. This is not a requirement of the quiz though.

If you don't have this requirement, you have either none or an infinite
number of solution - if a solution a==b exists, the ist also the solution
a+a==b+b (and so on).

That's why the quiz asks for any of the possible solutions.

So, only the minimal equations should count.

An right answer is fine, so feel free to use minimal equations.

James Edward Gray II

···

On Feb 4, 2007, at 1:05 PM, Hendrik Lipka wrote:

On Mon, 5 Feb 2007 01:29:32 +0900, James Edward Gray II > <james@grayproductions.net> wrote:

James Edward Gray II:

That's why the quiz asks for any of the possible solutions.

Ah, this is where I didn't read properly. Also this may be the reason why I
didn't actually try and start coding.

Kalman