String Equations (#112)

The three rules of Ruby Q.:

  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 Q. by submitting ideas as often as you can:

http://www.rubyquiz.com/

  1. Enjoy!

Suggestion: A [QUIZ] in the subject of emails about the problem helps
everyone
on Ruby T. 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 K…

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

James G.

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 L.

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

cool quiz.

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

I like it. :wink:

James Edward G. II

On 2/2/07, Daniel L. [email protected] wrote:

cool quiz.

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

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…

Sander L. wrote:

Here are the tests I’m using:
Parked at Loopia

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 L.

Ruby Q. (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

Kalman N. 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

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

James G.

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:

Yes, see my post about this a little earlier.

James Edward G. II

On 2007/02/04, at 11:17, Daniel L. 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?

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
[email protected]

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
Bugs in Matrix#rank - Ruby - Ruby-Forum)

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

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)

On Feb 4, 2007, at 4:45 AM, Kalman N. 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.

James Edward G. II

Sander L. 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: Parked at Loopia

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

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

Sander L. 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: Parked at Loopia

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

James Edward G. II

On Mon, 5 Feb 2007 01:29:32 +0900, James Edward G. II
[email protected] wrote:

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). So, only the minimal equations should count.

hli

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

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

“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 G. II

James Edward G. 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

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