Forum: Ruby Word Blender (#108)

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
James G. (Guest)
on 2007-01-06 20:31
(Received via mailing list)
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/

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

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

by Ben B.

This is a riff on the Jumble puzzle found in many (US) newspapers. More
specifically, it's based on the game TextTwist[1], made by GameHouse[2]
and
published in various places around the web.

The mechanic of TextTwist is simple. The player is given six letters and
is
tasked with unscrambling those letters into as many words as possible.
If the
player can use all six letters in a word, they proceed to the next
round.

Your task is to build the back-end engine to run a TextTwist clone.
Effectively,
this means that you must generate a list of three- to six-letter words
that can
all be constructed from the same six letters. This list must contain at
least
one six-letter word.

Bonus points for building a completely functional game!

  [1]: http://games.yahoo.com/games/texttwist.html (just one example,
java)
  [2]: http://www.gamehouse.com/
Fedor L. (Guest)
on 2007-01-07 14:48
(Received via mailing list)
It just so happened that I was learning ruby last summer and wrote a
program
to cheat for me at TextTwist. Needless to say the game got boring really
fast, but it was neat writing the program. I've modified it a bit to
instead
play the game, but I'm a fairly new user and would appreciate any
feedback,
both ruby related and general programming.

To run this you'll need a dictionary file of words, each on a new line,
called wordlist.txt and you need to require 'dict' and run
Dict.reduce(3,6)
before you run the program. This will create a reduced dictionary file
of
only words of lengths between 3 and 6. I didn't make this part of my
program's general execution as I figured doing this once was enough and
I
could do it manually.

It's got a simple text interface that looks sort of like the TextTwist
gui
layout on Yahoo. Sort of. The dictionary I use, which I did not attach
because its size (3 megs, I believe it was the full Scrabble dictionary)
makes for a much harder game than the Yahoo TextTwist as some of the
words
are really obscure.

Also, I have the problem that when running this on Windows it doesn't
allow
me to manipulate files with Ruby, giving me a Permission Denied error on
File.open. Any idea why this might be? This is if I try to run
Dict.reduce(3,6)
for example.
Ionut Artarisi (Guest)
on 2007-01-07 15:05
(Received via mailing list)
This is my first ruby program that actually does all it's supposed to
do. It
doesn't have a lot of style, but hopefully my next programs will look
better.
The wordlist is from wordlist.sourceforge.net and the first part of the
program reads the word.lst file and puts all the words in an array.

Hope it does what it's supposed to do, as i couldn't see the texttwist
on
yahoo.
I'm also attaching the Unnoficial Jargon File Wordlist word.lst file.

-Ionuţ Arţărişi
Jason M. (Guest)
on 2007-01-07 16:41
(Received via mailing list)
Here it is.  Scoring works like this: 10 points per (tile used)^2.  100
bonus points for using all 6 tiles. 5 wrong answers in a row ends the
game.
I made a dictionary file of 3-6 letter words using crop.rb.  Any
feedback is
appreciated, both from a stylistic and general programming perspective.
If
there's a way to rubify (not a word!) anything, also let me know.
Jason M. (Guest)
on 2007-01-07 16:58
(Received via mailing list)
On 1/7/07, Fedor L. <removed_email_address@domain.invalid> wrote:
> only words of lengths between 3 and 6. I didn't make this part of my
> allow me to manipulate files with Ruby, giving me a Permission Denied error
> on File.open. Any idea why this might be? This is if I try to run
> Dict.reduce(3,6) for example.


E:\ruby\programs\rubyquiz\quiz108\other submissions>ruby
texttwistGame.rb
./dict.rb:20:in `initialize': No such file or directory -
reducedwordlist3.txt (
Errno::ENOENT)
        from ./dict.rb:20:in `open'
        from ./dict.rb:20:in `get_dict'
        from texttwistGame.rb:37

fixed that, but then:

E:\ruby\programs\rubyquiz\quiz108\other submissions>ruby
texttwistGame.rb
texttwistGame.rb:17:in `pick_word': undefined method `delete_if' for
nil:NilClas
s (NoMethodError)
        from texttwistGame.rb:38

I'm too tired to go bug hunting.  Check out my submission crop.rb to see
how
I did the windows file writing.   Now that I've submitted my program I'm
going back to sleep. :)
James G. (Guest)
on 2007-01-07 18:54
(Received via mailing list)
Here's the solution I wrote while considering this quiz (it requires
Unix):

#!/usr/bin/env ruby -w

require "io/wait"

# game date cache
CACHE_FILE = ".game_words"

if File.exist? CACHE_FILE  # load from cache
   word_list = File.open(CACHE_FILE) { |file| Marshal.load(file) }
else                       # build word list
   # prepare data structure
   words_by_signature = Hash.new { |words, sig| words[sig] = Array.new }

   # read dictionary
   File.foreach(ARGV.shift || "/usr/share/dict/words") do |word|
     word.downcase!
     word.delete!("^a-z")

     next unless word.length.between? 3, 6

     (words_by_signature[word.split("").sort.join] << word).uniq!
   end

   # prepare recursive signature search
   def choices(sig, seen = Hash.new { |all, cur| all[cur] = true;
false }, &blk)
     sig.length.times do |i|
       shorter = sig[0...i] + sig[(i+1)...sig.length]
       unless seen[shorter]
         blk[shorter]
         choices(shorter, seen, &blk) unless shorter.length == 3
       end
     end
   end

   # prepare game data structure
   word_list = Hash.new

   # build game choices
   words_by_signature.keys.grep(/\A.{6}\Z/) do |possible|
     word_list[possible] = words_by_signature[possible]

     choices(possible) do |shorter_signature|
       if words_by_signature.include? shorter_signature
         word_list[possible].push(*words_by_signature
[shorter_signature])
       end
     end
   end

   # cache for faster loads
   File.open(CACHE_FILE, "w") { |file| Marshal.dump(word_list, file) }
end

### game interface (requires Unix) ###
TERMINAL_STATE = `stty -g`
system "stty raw -echo cbreak"
at_exit { system "stty #{TERMINAL_STATE}" }
clear = `clear`

# a raw mode savvy puts
def out(*args) print(*(args + ["\r\n"])) end

# for easy selection
words = word_list.keys

rounds = 0
loop do
   # select letters
   letters = current = words[rand(words.size)]
   while word_list.include? letters
     letters = letters.split("").sort_by { rand }.join
   end
   letters.gsub!(/.(?=.)/, '\0 ')

   # round data
   advance       = false
   matches       = Array.new
   current_match = String.new
   start         = Time.now
   message       = nil
   last_update   = start - 1

   # round event loop
   until Time.now >= start + 2 * 60
     # game display
     if last_update <= Time.now - 1
       print clear

       out "Your letters:  #{letters}"
       out "   Time left:  #{120 - (Time.now - start).round} seconds"
       out "  Your words:  #{matches.join(', ')}"
       out
       unless message.nil?
         out message
         out
       end
       print current_match
       $stdout.flush

       last_update = Time.now
     end

     # input handler
     if $stdin.ready?
       char = $stdin.getc
       case char
      when ?a..?z, ?A..?Z  # read input
        current_match << char.chr.downcase
        message       =  nil
         last_update   =  start - 1
       when ?\b, 127        # backspace/delete
         current_match = current_match[0..-2]
        message       =  nil
         last_update   =  start - 1
       when ?\r, ?\n        # test entered word
         if word_list[current].include? current_match
           matches << current_match
           matches = matches.sort_by { |word| [word.size, word] }
           if not advance and current_match.length == 6
             advance = true
             message = "You will advance to the next round!"
           else
             message = nil
           end
         else
           message = "Unknown word."
         end
         current_match = String.new
         last_update   = start - 1
       end
     end
   end

   # round results
   print clear
   missed = word_list[current] - matches
   unless missed.empty?
     out "Other words using \"#{letters}:\""
     out missed.sort_by { |word| [word.size, word] }.join(", ")
     out
   end
   if advance
     rounds += 1

     out "You made #{matches.size} word#{'s' if matches.size != 1}, ",
         "including at least one six letter word.  Nice work."
     out "Press any key to begin the next round."

     $stdin.getc
   else
     out "You made #{matches.size} word#{'s' if matches.size != 1}, ",
         "but failed to find a six letter word."

     break  # end game
   end
end

# game results
out "You completed #{rounds} round#{'s' if rounds != 1}.  Thanks for
playing."

__END__

James Edward G. II
Fedor L. (Guest)
on 2007-01-07 20:04
(Received via mailing list)
> E:\ruby\programs\rubyquiz\quiz108\other submissions>ruby texttwistGame.rb
> texttwistGame.rb:17:in `pick_word': undefined method `delete_if' for
> nil:NilClas
> s (NoMethodError)
>         from texttwistGame.rb:38
>
> I'm too tired to go bug hunting.  Check out my submission crop.rb to see
> how
> I did the windows file writing.   Now that I've submitted my program I'm
> going back to sleep. :)


Thanks Jason.  It seems that the problem is that as long as a file by
that
name exists, Windows doesn't let me overwrite it. I'm using the
FILE::CREAT
flag but I don't think that should be causing this. It seems to be
saying I
don't have the permission to modify the file with ruby, though I haven't
done much research into why that is so and how to fix it.
Chunyun Z. (Guest)
on 2007-01-07 22:09
(Received via mailing list)
Here is my solution.

-Chunyun

#== Synopsis
#This is the solution to Ruby Q. #108 described on
http://www.rubyquiz.com/quiz108.html.
#
#== Usage
#   text_twist.rb dictionary_file
#
#== Author
#   Chunyun Z.(removed_email_address@domain.invalid)
#
class Dictionary
  MIN_LEN, MAX_LEN = 3, 6
  attr_reader :max_letters
  def initialize(dict_file, min=MIN_LEN, max=MAX_LEN)
    @min_len = min
    @max_len = max
    @words = Hash.new {|hash,key|hash[key]=[]}
    File.foreach(dict_file) {|word|add_word(word.strip)}
    @max_letters = @words.keys.select {|key| key.size==@max_len}
  end
  def word_list(letters)
    words=[]
    permutate(letters).select {|letters|
      letters.size.between? @min_len, @max_len
    }.uniq.each {|key|
      words += @words[key]
    }
    words.sort_by {|word| word.size}
  end
  private
  def add_word(word)
    if (@min_len..@max_len)===word.size && word=~/^[a-z]+$/i
      word.downcase!
      @words[word.split(//).sort] << word
    end
  end
  def permutate(letters)
    _letters = letters.dup
    result = []
    while letter = _letters.shift
      permutate(_letters).each do |perm|
        result << [letter] + perm
      end
      result << [letter]
    end
    result
  end
end

Task = Struct.new(:letters, :words)

class GameUi
  def initialize(dict)
    @dictionary = dict
    @history_tasks = []
    @rounds = 1
    @score = 0
  end

  def next_task
    letters =
@dictionary.max_letters[rand(@dictionary.max_letters.size)]
    retry if @history_tasks.include?(letters)
    task = Task.new(letters, @dictionary.word_list(letters))
    @history_tasks << task
    task
  end

  def run_task
    @task = next_task
    @found = []
    @cleared = false
    puts "\nRound #{@rounds}. Letters: #{@task.letters*', '}. Hint:
number
of matching words: #{@task.words.size}"
    while !(word=ask("Enter your word:")).empty?
      if @found.include? word
        puts "Word already found!"
      elsif @task.words.include? word
        @found << word
        @score += word.size
        puts "Good job! You scored #{word.size} points!"
        if word.size == @task.letters.size
          @cleared = true
          puts "\nBingo! Round #@rounds cleared. You found
#{@found.size}
word#{'s' if @found.size > 1}. "
          break
        end
      else
        puts "Wrong word!"
      end
    end
    puts "Missed words: #{(@task.words-@found)*', '}."
    @cleared
  end

  def run
    while run_task
      answer = ask("\nProceed to next round?")
      break if answer !~ /^y/i
      @rounds += 1
    end
    puts "\nYou've cleared #{cleared=@cleared?@rounds:@rounds-1}
round#{'s'
if cleared > 1}, and your total score is #{@score}."
  end

  def ask question
    print question, " (Hit enter to exit)=> "
    gets.strip
  end
end
if __FILE__ == $0
  if ARGV.size != 1
    puts "Usage: #{File.basename(__FILE__)} dictionary_file"
    exit
  end
  GameUi.new(Dictionary.new(ARGV.shift)).run
end
__END__
Chunyun Z. (Guest)
on 2007-01-07 22:17
(Received via mailing list)
There is a bug in my previous submission, here it is again:

#== Synopsis
#This is the solution to Ruby Q. #108 described on
http://www.rubyquiz.com/quiz108.html.
#
#== Usage
#   text_twist.rb dictionary_file
#
#== Author
#   Chunyun Z.(removed_email_address@domain.invalid)
#
class Dictionary
  MIN_LEN, MAX_LEN = 3, 6
  attr_reader :max_letters
  def initialize(dict_file, min=MIN_LEN, max=MAX_LEN)
    @min_len = min
    @max_len = max
    @words = Hash.new {|hash,key|hash[key]=[]}
    File.foreach(dict_file) {|word|add_word(word.strip)}
    @max_letters = @words.keys.select {|key| key.size==@max_len}
  end
  def word_list(letters)
    words=[]
    permutate(letters).select {|letters|
      letters.size.between? @min_len, @max_len
    }.uniq.each {|key|
      words += @words[key]
    }
    words.sort_by {|word| word.size}
  end
  private
  def add_word(word)
    if (@min_len..@max_len)===word.size && word=~/^[a-z]+$/i
      word.downcase!
      @words[word.split(//).sort] << word
    end
  end
  def permutate(letters)
    _letters = letters.dup
    result = []
    while letter = _letters.shift
      permutate(_letters).each do |perm|
        result << [letter] + perm
      end
      result << [letter]
    end
    result
  end
end

Task = Struct.new(:letters, :words)

class GameUi
  def initialize(dict)
    @dictionary = dict
    @history_tasks = []
    @rounds = 1
    @score = 0
  end

  def run
    while run_task
      answer = ask("\nProceed to next round?")
      break if answer !~ /^y/i
      @rounds += 1
    end
    puts "\nYou've cleared #{cleared=@cleared?@rounds:@rounds-1}
round#{'s'
if cleared > 1}, and your total score is #{@score}."
  end

  private
  def next_task
    letters =
@dictionary.max_letters[rand(@dictionary.max_letters.size)]
    retry if @history_tasks.include?(letters)
    task = Task.new(letters, @dictionary.word_list(letters))
    @history_tasks << task
    task
  end

  def run_task
    @task = next_task
    @found = []
    @cleared = false
    puts "\nRound #{@rounds}. Letters: #{@task.letters*', '}. Hint:
number
of matching words: #{@task.words.size}"
    while !(word=ask("Enter your word:")).empty?
      if @found.include? word
        puts "Word already found!"
      elsif @task.words.include? word
        @found << word
        @score += word.size
        puts "Good job! You scored #{word.size} points!"
        if word.size == @task.letters.size
          @cleared = true
          puts "\nBingo! Round #@rounds cleared. You found
#{@found.size}
word#{'s' if @found.size > 1}. "
          break
        end
      else
        puts "Wrong word!"
      end
    end
    puts "Missed words: #{(@task.words-@found)*', '}."
    @cleared
  end

  def ask question
    print question, " (Hit enter to exit)=> "
    gets.strip.downcase
  end
end
if __FILE__ == $0
  if ARGV.size != 1
    puts "Usage: #{File.basename(__FILE__)} dictionary_file"
    exit
  end
  GameUi.new(Dictionary.new(ARGV.shift)).run
end
Ben Ford (Guest)
on 2007-01-08 00:01
(Received via mailing list)
Here is my solution. Takes the filename of the dictionary to use as the
first argument and optionally a word to solve for as the second
argument. If no second argument is provided, you play the game.

--

class String
  def lettercount
    split(//).uniq.map{|c| [c, count(c)]}
  end
  def fisher_yates_shuffle
    a = self.dup
    (length-1).downto(0){|i|
      j = rand(i+1)
      a[i], a[j] = a[j], a[i] if i != j
    }
    a
  end
end

class Array
  def random_element
    self[rand(length)]
  end
end

class Dictionary
  def initialize(filename)
    @words = []
    IO.foreach(filename) do |line|
      word = line.chomp
      @words << word.downcase if word.length.between?(3, 6)
    end
  end
  def blend(word)
    @words.select{|x|
      x.count(word.downcase) == x.length &&
      x.lettercount.all?{|c, n|
        n <= word.downcase.lettercount.assoc(c).last }
    }
  end
  def randomword
    @words.select{|x| x.length == 6}.random_element
  end
end

class WordBlender
  def initialize(dictionary)
    @dictionary = dictionary
  end
  def blend_to_s(word)
    word_blend = @dictionary.blend(word)
    puts "WordBlender: '#{word}' has #{word_blend.length} answers."
    puts
    max = -1
    word_blend.sort_by{|x| [x.length, x]}.each do |x|
      if x.length > max
        max = x.length
        puts "Words of length #{max}:"
      end
      puts " #{x}"
    end
  end
  def play
    puts "Welcome to WordBlender! (enter a blank line to quit)"
    puts
    round = 0
    points = 0
    continue = true
    while continue do
      points = points + 10 * round
      round = round + 1
      word = @dictionary.randomword
      word_blend = @dictionary.blend(word)
      word_shuffled = word.fisher_yates_shuffle
      puts "Round: #{round} - Blend: '#{word_shuffled}' - Total Score:
#{points}"
      current_word = ""
      current_words = []
      current_continue = true
      while continue && current_continue do
        current_word = STDIN.gets.chomp.downcase
        if current_word == ""
          puts
          puts "Final Word: '#{word}' - Final Score: #{points}"
          continue = false
        elsif current_words.include?(current_word)
          puts "'#{current_word}' already used."
        elsif word_blend.include?(current_word)
          current_words << current_word
          points = points + current_word.length * current_word.length
          current_continue = (current_word.length < word.length)
        elsif current_word.count(word) == current_word.length
          puts "'#{current_word}' not in dictionary."
        else
          puts "'#{current_word}' not found in '#{word_shuffled}'."
        end
      end
    end
  end
end

if ARGV.size == 0
  puts "Usage: wordblender.rb <filename> - play WordBlender with the
specified dictionary"
  puts "Usage: wordblender.rb <filename> <word> - show all blends for
the word using the dictionary"
elsif ARGV.size == 1
  WordBlender.new(Dictionary.new(ARGV[0])).play
elsif ARGV.size >= 2
  WordBlender.new(Dictionary.new(ARGV[0])).blend_to_s(ARGV[1])
end
Daniel F. (Guest)
on 2007-01-08 01:12
(Received via mailing list)
I made two this time.  One plays a game and one just picks the word.

Here's the one that just picks a word.  It runs very quickly, about .155
or .2 seconds.
#! /usr/bin/ruby -w

class Array
  def rand_elem
    self[rand(size)]
  end
end

# Open and read the dictionary.
dict = IO.read("/usr/share/dict/words")

# Pick a random word with 6 letters.
baseWord = dict.scan(/^[a-z]{6}$/).rand_elem

# Find words that use the same letters
selectedWords = dict.scan(/^[#{baseWord}]{3,6}$/)

# Display the words.
puts baseWord + ":\n\t" + selectedWords.join("\n\t")

And here's the one that plays a game.  Basically, it gives you the
letters and you enter the 5 best words you can think of.  The score is
the length of the word ** 3 added to a running total, but subtracted if
the word isn't valid (already said or not in the list).

#! /usr/bin/ruby -w

class Array
  def rand_elem
    self[rand(size)]
  end

  def english_join
    self[0...-1].join(', ') + ', and ' + self[-1]
  end
end

class String
  def letters
    unless $DEBUG
      split(//).uniq.sort_by{rand}
    else
      split(//)
    end
  end
end

class Game
  @@dict = nil

  def initialize
    # Open and read the dictionary.
    @@dict ||= IO.read("/usr/share/dict/words")

    @points = 0
    @round = 1
  end

  def play
    # Pick a random word with 6 letters.
    baseWord = @@dict.scan(/^[a-z]{6}$/).rand_elem

    # Find words that use the same letters
    selectedWords = @@dict.scan(/^[#{baseWord}]{3,6}$/)

    # Initialize word list & continue var.
    guessed = []
    continue = false

    # Display banner
    puts "",
      "Round #{@round}:",
      "Enter the 5 longest words you can make from the letters
#{baseWord.letters.english_join}.",
      "Invalid and repeated words count towards the 5 words but subtract
points.",
      ""

    # Gather all the points, calculate the score, and see if the player
should go to the next round.
    5.times do
      print "#{@points}\t"
      word = gets.chomp.downcase
      if !guessed.include?(word) && selectedWords.include?(word)
        @points += word.length ** 3
        guessed << word
        continue = true if word.length == 6
      else
        @points -= word.length ** 3
      end
    end

    # Go on to the next round or lose.
    if continue
      @round += 1
      play
    else
      puts "Sorry, you didn't get a 6 letter word.  You got #{@points}
points, however."
    end
  end
end

Game.new.play
Fedor L. (Guest)
on 2007-01-08 01:38
(Received via mailing list)
On 1/7/07, Daniel F. <removed_email_address@domain.invalid> wrote:

> # Find words that use the same letters
> selectedWords = dict.scan(/^[#{baseWord}]{3,6}$/)


I was really impressed when I first saw this. It doesn't quite work if
you
want to exclude reusing the same letter more than once
("hhh".scan(/^[hello]{3,6}$/) => ["hhh"]) but it comes so close to
something
I've only ever thought about implementing as a recursive method.
Unfortunately I don't know much about this but now I wonder if it's
possible
to find all partial permutations of a word with a regexp.
Daniel F. (Guest)
on 2007-01-08 02:33
(Received via mailing list)
Here is a version of the regex that works around that:
regex =
/^([a-z])(?!\1)([a-z])(?!\1|\2)([a-z])(?!\1|\2|\3)([a-z])(?!\1|\2|\3|\4)([a-z])(?!\1|\2|\3|\4|\5)([a-z])$/

Some examples:
 >> regex.match 'abcdef'
=> #<MatchData:0xb7b46a18>
 >> regex.match 'abcdefg'
=> nil
 >> regex.match 'abcddf'
=> nil
 >> regex.match 'abbdtf'
=> nil
 >> regex.match 'Abbdtf'
=> nil
 >> regex.match 'awesom'
=> #<MatchData:0xb7b3a5b0>
 >> regex.match 'hollow'
=> nil
Eric I. (Guest)
on 2007-01-08 02:35
(Received via mailing list)
This solution simply chooses a six-letter word (or allows the user to
choose one) and then displays a list of words that can be composed with
a subset of the letters (size >= 3).  Here is output from a sample run:

ape
lap
pal
pea
sap
sea
see
else
leap
pale
peas
peel
sale
seal
slap
sleep
asleep
please

Eric
------------
Interested in Ruby training with a well-reviewed instructor and
training materials?  www.LearnRuby.com
============

# Given an array of letters, a whole/partial word built up so far, and
# a hash, adds to the hash all permutations of subsets built from the
# partial word and the array of letters.  If a block is given it acts
# as a filter since the words must produce a true result when submitted
# to the block in order to be added to the hash.
def permute(letters, word, possible_words, &filter_block)
  possible_words[word] = true if filter_block.nil? ||
filter_block.call(word)
  return if letters.empty?

  letters.each_with_index do |letter, i|
    (new_letters = letters.dup).delete_at(i)
    permute(new_letters, word + letter, possible_words, &filter_block)
  end
end

# Verify that a filename was provided as the first argument and that
# it is a readable file
if ARGV[0].nil?
  $stderr.puts("Usage: #{$0} dictionary-file [word]")
  exit 1
elsif ! File.file?(ARGV[0]) || ! File.readable?(ARGV[0])
  $stderr.puts("Error: \"#{ARGV[0]}\" is not a readable file.")
  exit 2
end

# Build list of all six-letter words from dictionary file
words6 = Array.new
open(ARGV[0], "r") do |f|
  f.each_line { |w| words6 << w if w.chomp! =~ /^[a-z]{6}$/ }
end

# Determine whether a random six-letter word is chosen or the user
# specifies one.
if ARGV[1]
  # user attempted to specify a word; check its validity
  if words6.include?(ARGV[1])
    word = ARGV[1]
  else
    $stderr.puts("Error: \"#{ARGV[1]}\" is not a known six-letter
word.")
    exit 3
  end
else
  word = words6[rand(words6.size)]  # choose a random word
end

# Generate a hash of all three- to six-letter permutations using the
# letters of the chosen six-letter word.  Note: most will not be valid
# words.
possible_words = Hash.new
permute(word.split(""), "", possible_words) { |w| w.length >= 3 }

# Generate a list of all valid words that are also permutations of
# subsets of the chosen six-letter word.  This is done by
# re-reading the word file and testing each word against the
# possible permutations.
actual_words = Array.new
open(ARGV[0], "r") do |f|
  f.each_line { |w| actual_words << w if possible_words[w.chomp!] }
end

# Display the resulting actual words sorted first by length and then
# alphabetically.
puts actual_words.sort_by { |w| [w.length, w] }
Daniel F. (Guest)
on 2007-01-08 02:41
(Received via mailing list)
Oops, that doesn't match {3,6} just {6}.

 >> regex =
/^([a-z])(?!\1)([a-z])(?!\1|\2)([a-z])(?:(?!\1|\2|\3)([a-z]))?(?:(?!\1|\2|\3|\4)([a-z]))?(?:(?!\1|\2|\3|\4|\5)([a-z]))?$/
=> [a-z]1[a-z]12[a-z]:123[a-z]:1234[a-z]:12345[a-z]
 >> regex.match 'hhh'
=> nil
 >> regex.match 'hal'
=> #<MatchData:0xb7cfb7d0>
 >> regex.match 'sos'
=> nil
 >> regex.match 'sauce'
=> #<MatchData:0xb7c12bac>
 >> regex.match 'hatoff'
=> nil
 >>

That's a better one.
Dale M. (Guest)
on 2007-01-08 07:20
(Received via mailing list)
Here is my solution. I didn't post the whole thing. This is the main
engine minus the Tk UI class. You can get the full solution
(dictionary, graphics and ruby source at
http://www.dalemartenson.com/files/rubytexttwist.tar.gz

--Dale M.


class TextTwist
  # Algorithm derived from online book by Robert Sedgewick and Kevin
Wayne.
  # It was origomally written in Java.
  #
  # References:
  # http://www.cs.princeton.edu/introcs/31datatype/
  # http://www.cs.princeton.edu/introcs/31datatype/Tex...

  class Profile < Hash
    def initialize( word )
      super
      word.downcase.each_byte do |b|
        self[b.chr] = 0 unless self.has_key?(b.chr)
        self[b.chr] += 1
      end
    end

    def contains( p )
      p.each_pair do |k,v|
        return false unless self.has_key?(k)
        return false if self[k] < v
      end
      true
    end
  end

  attr_reader :word, :words

  def initialize( dictionary, word=nil )
    @dictionary = dictionary
    @dictionary.collect! {|x| x.downcase}
    @dictionary.sort!
    word.nil? ? restart : start( word )
  end

  def start( word )
    @word = word.downcase
    @profile = Profile.new( @word )
    @words = process
  end

  def process
    result = []

    @dictionary.each do |dw|
      next if dw.length < 3 || dw.length > @word.length
      result << dw if @profile.contains( Profile.new( dw ) )
    end

    result
  end

  def mix
    a = @word.split(//)
    1.upto(a.length) do
      i1 = rand(a.length)
      i2 = rand(a.length)
      t = a[i1]
      a[i1] = a[i2]
      a[i2] = t
    end
    a
  end

  def check( word )
    @words.include?( word.downcase )
  end

  def contains( word )
    @profile.contains( Profile.new( word ) )
  end

  def restart
    six_letter_words = []
    @dictionary.each do |w|
      six_letter_words << w if w.length == 6
    end

    start( six_letter_words[ rand(six_letter_words.size) ] )
  end
end

class TextTwistUI
# ... SNIP ...
end

# MAIN PROGRAM -- where the magic begins

# Using the crossword dictionary from "Moby Word Lists" by Grady Ward
(part of
# Project Gutenberg). I reduced the dictionary to only words that are 3
to 6
# characters in length.
#
# Note: This may not be the best dictionary for this game, but it
works. It
# does contain numerous obscure words which is both good and bad.
#
#     irb(main):001:0> f = File.open("crosswd.txt")
#     => #<File:crosswd.txt>
#     irb(main):002:0> of = File.open("3-6.txt","w+")
#     => #<File:3-6.txt>
#     irb(main):003:0> f.each_line do |line|
#     irb(main):004:1*   l = line.strip
#     irb(main):005:1>   if l.length >= 3 && l.length <= 6 then
#     irb(main):006:2*    of.puts( l )
#     irb(main):007:2>   end
#     irb(main):008:1> end
#     => #<File:crosswd.txt>
#     irb(main):009:0> of.close
#     => nil
#
# References:
# http://www.gutenberg.org/etext/3201

dictionary = []
f = File.open("3-6.txt")
dictionary = f.read.split

tt = TextTwist.new( dictionary )

TextTwistUI.new( tt )
Tk.mainloop
Dan M. (Guest)
on 2007-01-08 08:10
Here is my solution.  I'm not too good with regular expressions, so the
word selection script is a little bit slow.


#!/usr/bin/env ruby
#
#  Author: Dan M. - http://www.dcmanges.com
#  Ruby Q. #108 - http://rubyquiz.com/quiz108.html

class WordList
  include Enumerable

  attr_accessor :file
  attr_reader   :filters

  def initialize(file = nil)
    @file, @filters = file, []
  end

  def each
    File.open(@file, "r") do |file|
      while line = file.gets
       yield apply_filters(line.chomp)
      end
    end
  end

  protected

  def apply_filters(word)
    @filters.inject(word) do |word, filter|
      filter.call(word)
    end
  end

end

# Module to select words based on length and letter composition.
module WordFinder
  # Finds words of length +size+ which can be composed with the letters
in +base_word+
  def find_words(size, base_word)
    letter_counts = base_word.split(//).inject(Hash.new(0)) {
|hash,letter| hash[letter] += 1; hash }
    regexp = Regexp.new("^" + letter_counts.map { |letter,count|
"#{letter}{0,#{count}}"}.sort.join + "$")
    select { |word| word.to_s.size == size && word.split(//).sort.join
=~ regexp }
  end

  # Finds a random word of the given size
  def random_word_of_size(size)
    words = find_words(size, (('a'..'z').to_a * 3).join)
    words[rand(words.size)]
  end
end

WordList.send(:include, WordFinder)

# Dictionary file from: http://wordlist.sourceforge.net/
# http://prdownloads.sourceforge.net/wordlist/alt12d...
@wordlist = WordList.new("/Users/dan/Desktop/alt12dicts/2of12full.txt")
# This particular wordlist has an offset
@wordlist.filters << lambda { |word| word[17..-1] }
# Skip proper names, contractions, etc.
@wordlist.filters << lambda { |word| word =~ /^[a-z]+$/ ? word : "" }

module WordBlender
  class Round
    def initialize(wordlist, word_size = (3..6))
      @wordlist = wordlist
      @min_size, @max_size = word_size.first, word_size.last
      @qualified = false
      @hits = Hash.new { |h,k| h[k] = [] }
      load_words
    end

    def qualified?
      @qualified
    end

    def guess?(word)
      word = word.to_s.strip
      dup?(word) || qualify?(word) || hit?(word) || :miss
    end

    def letters
      @base.split(//).sort
    end

    def status
      result = []
      @min_size.upto(@max_size) do |size|
        result << [size, @hits[size].size, @words[size].size]
      end
      result.map { |data| "#{data[0]} letters: #{data[1]} of
#{data[2]}"}.join(", ")
    end

    protected

    def dup?(word)
      :dup if @hits[word.size].include?(word)
    end

    def qualify?(word)
      if @words[word.size].include?(word) and word.size == @max_size
        @hits[word.size] << word
        @qualified = true
        :qualify
      end
    end

    def hit?(word)
      if @words[word.size].include?(word)
        @hits[word.size] << word
        :hit
      end
    end

    def load_base_word
      @base = @wordlist.random_word_of_size(@max_size)
    end

    def load_words
      @words = Hash.new([])
      load_base_word
      @min_size.upto(@max_size) do |size|
        @words[size] = @wordlist.find_words(size, @base)
      end
    end
  end

  class Game
    def initialize(wordlist)
      @wordlist = wordlist
      reset
    end

    def start!
      help
      start_round
      print 'guess> '
      while input = gets
        input = input.strip
        break if input == ".quit"
        if input[0,1] == "." && respond_to?(input[1..-1])
          send(input[1..-1])
          print 'guess> '
          next
        end
        result = @round.guess?(input)
        puts case result
          when :miss
            "Wrong!"
          when :dup
            "Already guessed that!"
          when :hit
            "You got it!"
          when :qualify
            "You got it! And you qualify for the next round!"
        end + " " + input
        status unless result == :miss
        print 'guess> '
      end
      puts "Goodbye!"
    end
    alias :play! :start!

    protected

    def letters
      puts "Available Letters: " + @round.letters.sort_by {rand}.join(',
')
    end

    def next
      if @round.qualified?
        start_round
      else
        puts "You have not yet qualified for the next round!"
      end
    end

    def help
      puts <<-END_HELP
      When prompted, either enter a word or a command.
      The following commands are available:
        .quit => quits the game
        .help => display this help
        .next => goes to the next round (if qualified)
        .letters => display available letters
        .status  => show the current status of this round
      END_HELP
    end

    def reset
      @round_number = 0
    end

    def start_round
      @round_number += 1
      @round = Round.new(@wordlist)
      puts "Beginning Round #{@round_number}!"
      letters
    end

    def status
      puts @round.status
    end
  end
end

@blender = WordBlender::Game.new(@wordlist)
@blender.play!
Martin B. (Guest)
on 2007-01-08 09:28
(Received via mailing list)
My game will play almost like the one you can play at yahoo without
score. Has
cheating and giving-up commands and draws possible solutions on the
screen.
Run it with wordblender.rb <dictfile>.

martin



# wordblender.rb
#
# Usage: wordblender.rb [dictfile]
#

class String
 # Checks if string can be build out of these characters.
 #
 # "hello".build_outof?("llohe") => true
 # "world".build_outof?("dlrowl") => true
 def build_outof?(other)
   return false if self.length > other.length
   o = other.clone
   self.each_byte do |c|
      return false unless o.include?(c.chr)
      o[o.index(c.chr)] = 0
   end
   true
 end

 # Shuffle a word.
 #
 # "hello".shuffle => "oellh"
 def shuffle
   return self.scan(/./).sort_by{rand}.to_s
 end
end

class WordBlenderGame

  attr_reader :words

  # limits for words for the game
  MINCHARACTERS = 3
  MAXCHARACTERS = 6

  # time limit per game
  TIME_PER_GAME = 90

  # how to display the board
  DISPLAY_COLUMNS = 5

  # read the dictionary from a file.
  # we also keep words with length of MAXCHARACTERS to find
  # good initial letters quickly.
  def initialize(dictionary)
    @words, @maxwords = [], []
    File.open(dictionary).each do |line|
      l = line.strip.downcase
      @words << l if (l.length >= MINCHARACTERS && l.length <=
MAXCHARACTERS)
      @maxwords << l if l.length == MAXCHARACTERS
    end
  end

  # this generates a bunch of letters to play with and looks up words
  # that can be build by them from the dictionary ("candidates").
  def prepare_game()
    @letters = @maxwords[rand(@maxwords.size-1)].shuffle
    @candidates = []
    @words.each { |w| @candidates << w if w.build_outof?(@letters) }
    @candidates = @candidates.uniq    # this fixed duplicated entries
    @candidates = @candidates.sort {|x,y| x.length <=> y.length }
    @found_candidates = @candidates.collect { false }
  end

  #
  # This is to display the candidates to the screen. Draws it into
columns
  # and returns a string.
  #
  def get_board(solution=false, title="Words to find")
     result = "" ; i = 0
     sempty = ' '*(DISPLAY_COLUMNS*(MAXCHARACTERS+2))
     s = String.new(sempty)
     result << title << ":\n"

     @found_candidates.each_index do |idx|
         f = @found_candidates[idx] || solution
         s[i.modulo(DISPLAY_COLUMNS)*(MAXCHARACTERS+2)] =
f ? "[#{@candidates[idx]}]" : "["+(' '*@candidates[idx].length)+"]"
         if i.modulo(DISPLAY_COLUMNS) == DISPLAY_COLUMNS-1 then
           result << (s + "\n")
           s = String.new(sempty)
         end
         i+=1
     end
     result << s if s.include?('[')
     result << "\n"
  end


  # This plays one round of the game, returns true if won
  def play
    self.prepare_game
    message =  "Press RETURN to shuffle the letters, '!' to give up, '?'
to
cheat."

    # start the time.
    @time = TIME_PER_GAME
    timer = Thread.new { while true do @time-=1; sleep 1 end }

    # game loop
    while @found_candidates.include?(false) do

       # print board and other stuff
       puts get_board
       puts
       puts  "Time: " + @time.to_s
       puts  "Msg:  " + message if message != ''
       puts  "Use:  " + @letters
       print "Try:  "

       # get user's guess and handle it
       $stdout.flush
       s = STDIN.gets.downcase.strip

       if @time <= 0 then
         puts "Time's up!"
         break
       end

       if s == "" then
         @letters = @letters.shuffle
         message = "Letters shuffled!"
         next
       end

       break if s == '!'

       if s == '?' then
         puts get_board(true)
         message = "Cheater!"
         next
       end

       if !s.build_outof?(@letters) then
         message = "Invalid word!"
         next
       end

       if @candidates.include?(s) then
         @found_candidates[@candidates.index(s)] = true
         message = "#{s} Found!"
       else
         message =  "#{s} not listed!"
       end
    end

    Thread.kill(timer)

    # print solution
    puts get_board(true, "Solution is")

    # Check if player found a word with all characters
    @found_candidates.each_index do |idx|
      return true if @found_candidates[idx] && @candidates[idx].length
==
MAXCHARACTERS
    end
    false
  end
end

print "Loading game...";$stdout.flush
game = WordBlenderGame.new(ARGV[1] || '/usr/share/dict/words')
puts "#{game.words.size} words found."

while game.play do
  puts "You won, press any key to play next round."
  gets
end

puts "Game over!"
Ionut Artarisi (Guest)
on 2007-01-08 15:07
(Received via mailing list)
On Sunday 07 January 2007 15:04, Ionut Artarisi wrote:
> This is my first ruby program that actually does all it's supposed to do.
> It doesn't have a lot of style, but hopefully my next programs will look
> better. The wordlist is from wordlist.sourceforge.net and the first part of
> the program reads the word.lst file and puts all the words in an array.
>
> Hope it does what it's supposed to do, as i couldn't see the texttwist on
> yahoo.
> I'm also attaching the Unnoficial Jargon File Wordlist word.lst file.
>
> -Ionuţ Arţărişi

I remembered a mistake that i had done. Well not actually a mistake. I
just
wrote some 10 lines of code that did some extra work that i never
actually
used later in the program. I deleted those lines now, which resulted in
a
unidimensional array (it was bidimensional before). This version should
be
more readable and should make more sense though there's no extra
functionallity. So the version on rubyquiz.com should be replaced with
this
one, for the sake of future readers if it's not too much of a trouble.

Thank you. And thank you for rubyquiz.com. It's fantastic!
Bob S. (Guest)
on 2007-01-09 21:44
(Received via mailing list)
Here's my submission. There are two programs: the first builds a set
of puzzles from a decent word list I found on line. Files like
/usr/dict/words have too many obscure words. The second is a (very)
simplistic game interface.

A "puzzle" is just a string of six or more 3-6 letter words, sorted by
length. The output of the first program is just one line per puzzle,
with the words separated by colons. The words are ROT13 encoded. The
puzzles.rb program generates 905 different puzzles.

Bob S.

---------- puzzles.rb ----------

# puzzles.rb
# generate puzzles for use by wordblend.rb program
#
# usage: ruby puzzles.rb >puzzles.dat

require 'open-uri'
require 'yaml'

# these urls point to text files with lists of 2000 commonest
# English word "families", including plurals and other forms.
# this ends up generating reasonably good puzzles.
URIS = %w{
  http://www1.harenet.ne.jp/~waring/vocab/wordlists/...
  http://www1.harenet.ne.jp/~waring/vocab/wordlists/...
}

# minimum number of words necessary to form a puzzle
MIN_SIZE = 6

# define some helper functions
class String

  # returns string with characters in sorted order
  def sort
    split(//).sort.join
  end

  # returns true if s is a subword of the string. both
  # the string and s must be sorted!
  def subword?(s)
    i = j = 0
    while j < s.length
      i += 1 while i < length and self[i] != s[j]
      i < length or return false
      j += 1
      i += 1
    end
    true
  end

end

# grab the 3-6 letter words from word lists. sort each word by
# character (e.g. 'test' becomes 'estt'), and then accumulate
STDERR.puts "Fetching words..."
words = Hash.new {|h,k| h[k] = []}
URIS.each do |uri|
  open(uri) do |f|
    f.read.split.select {|w| w.length >= 3 and w.length <= 6}.each do
|word|
      word.upcase!
      sword = word.sort
      words[sword] << word
    end
  end
end

# find puzzles by looking at which sorted words are contained in
# other six-character sorted words.
STDERR.puts "Finding puzzles..."
n = 0
words.keys.select {|w| w.length == 6}.each do |key|
  puzzle = words.select {|ssub, subs| key.subword?(ssub)}.collect {|a|
a.last}.flatten.sort_by {|w| "#{w.length}#{w}"}
  next if puzzle.size < MIN_SIZE
  puts puzzle.join(':')
end

---------- wordblend.rb ----------

# wordblend.rb
# simplistic Word Blend puzzle game
# uses puzzles.dat file created by separate puzzles.rb program

class String
  def rot13
    tr 'A-Za-z', 'N-ZA-Mn-za-m'
  end
end

class Puzzle

  attr_reader :words, :letters, :board

  def self.pick
    @@puzzles ||= IO.readlines('puzzles.dat')
    new(@@puzzles[rand(@@puzzles.size)].chomp.rot13.split(':'))
  end

  def initialize(words)
    @words = words
    scramble
    @board = words.collect {|w| w.gsub(/./, '-')}
  end

  def scramble
    @letters = words.last.split(//).sort_by {rand}.join
    scramble if words.include? @letters
  end

  def help
    puts "Enter 'Q' to give up, 'S' to scramble letters"
  end

  def play
    help
    turn while board != words
    puts board
  end

  def turn
    puts board
    puts
    puts letters
    while true
      print "? "
      guess = gets.strip.upcase
      if guess == ''
        help
        redo
      end
      if guess == 'S'
        scramble
        puts letters
        redo
      end
      @board = words.dup if guess == 'Q'
      i = words.index(guess) and board[i] = guess
      break
    end
  end

end

# play a random game
p = Puzzle.pick
p.play
Bob S. (Guest)
on 2007-01-09 23:09
(Received via mailing list)
On 1/7/07, Fedor L. <removed_email_address@domain.invalid> wrote:
> Unfortunately I don't know much about this but now I wonder if it's possible
> to find all partial permutations of a word with a regexp.
>
>

Here's a revision to Daniel's approach that seems to work well:

# Open and read the dictionary.
dict = IO.read("/usr/share/dict/words").scan(/^[a-z]{3,6}$/)

# Pick a random word with 6 letters.
baseWord = dict.grep(/^[a-z]{6}$/).rand_elem

# Find words that use the same letters
sortWord = baseWord.scan(/./).sort.to_s
selectedWords = dict.select {|w|
Regexp.new(w.scan(/./).sort.join('.*')).match(sortWord) }

I started by just extracting only the 3-6 letter words.

Then I sort the base word so the letters are in order. Let's say the
baseWord is "parlor". Then sortWord would be "aloprr".

Now for each word in the dictionary, sort it in letter order and
create a regex. Suppose the word is "roar". The sorted version is
"aorr" and the regex is "a.*o.*r.*r". If that regex matches the
sortWord, we found a valid subword.

This could be sped up by precompiling the regexes I would guess.

Bob
Fedor L. (Guest)
on 2007-01-09 23:43
(Received via mailing list)
On 1/9/07, Bob S. <removed_email_address@domain.invalid> wrote:
> > want to exclude reusing the same letter more than once
>
>
> This could be sped up by precompiling the regexes I would guess.
>
> Bob
>
>
Very interesting, you create a matching in the opposite direction,
matching
against the picked word vs against the dictionary of words, very nice. I
like this more than Daniel's approach since it doesn't involve creating
Regexps which grow as n^2 compared to the length of our word. This ends
up
going through all the words (length 3 to 6 anyhow) in the dictionary to
find
the matches. Finding all permutations would go through the dictionary
roughly the number of subwords times, taking log of the length of the
dictionary number of steps each time. For all but the largest of words
with
lots of subwords would the latter be slower, but the Regexp approach is
very
nice and simple, and I can't decide what I like more.

It does bother me a bit though that I don't know how long each regexp
takes
to run to get its match as I know that some Regexps do take quite a
while to
match or exclude some words, just not sure if this one would be one of
them.
Daniel F. (Guest)
on 2007-01-10 01:11
(Received via mailing list)
The regexes for me are fairly fast.  For my regex that weeds out words
with repeated characters (I posted it before and it is fairly lengthy,
so I won't post it again unless by request), a 10,000 word dictionary
(the one in my /usr/share/dict/words) takes less than .5 seconds to pick
the baseword and all related words.  Under .2 seconds if it doesn't weed
out words with repeated characters.

One thing I can see to optimize in your method is all the sorting.  I
haven't tested the performance yet, but sets might be the answer.

require 'set'

class String
  def letters
    split(//).to_set
  end
end

baseWordSet = baseWord.letters
dict.select {|x| x.letters.subset?(baseWordSet)}

It's more readable, IMHO, at least.

Dan
Jason M. (Guest)
on 2007-01-10 02:12
(Received via mailing list)
On 1/9/07, Daniel F. <removed_email_address@domain.invalid> wrote:
>         end
> end
>
> baseWordSet = baseWord.letters
> dict.select {|x| x.letters.subset?(baseWordSet)}
>
> It's more readable, IMHO, at least.
>
> Dan


I used sets in my submission, and the letters of each 6 letter word were
split in the above fashion so as to provide faster scanning (at least
according to one of the books I was reading the night before - please
correct me if I'm wrong).  The answers to these questions was actually
what
I was hoping to hear when I said any feedback would be appreciated :)

And yes, it's definitely more readable - I wrestled with the idea of how
to
see if one array was a subset of another (and which way was more
efficient)
when I came across the concept of a set for the first time.
s2.subset?(s1)
sure made life easy and using it made it a lot easier to understand what
my
code was doing just by reading it.

One last question, which I've been thinking about but not able to
dedicate a
lot of time to is this: how do you 'benchmark' the speed of individual
programs?  I've been wondering how fast my program is compared to others
since I wrote it.

And finally, here's to you, Mr. Ruby Q. creator guy, for coming up
with a
Ruby quiz that really peaked my interest :)
Daniel F. (Guest)
on 2007-01-10 02:29
(Received via mailing list)
If you are using *nix (unix, linux) then you can run "time <ruby
program>" at the command line.  This might work with Macs as well
because they are based on unix but I don't have one so I'm not sure.

 From within Ruby, you can do this:
require 'benchmark'

puts Benchmark.measure do
  # program code here.
end

I agree that this was one of the best Ruby Q.zes.  It is simple and
yet has different, but equally good, types of solutions (regex, sets,
etc.).  I also enjoyed the opportunity to explore sets and things; more
RQs that can involve the standard library would be awesome.

Dan
Ken B. (Guest)
on 2007-01-10 04:11
(Received via mailing list)
On Fri, 05 Jan 2007 22:05:52 +0900, Ruby Q. wrote:
> all be constructed from the same six letters. This list must contain at least
> one six-letter word.
>
> Bonus points for building a completely functional game!
>
>   [1]: http://games.yahoo.com/games/texttwist.html (just one example, java)
>   [2]: http://www.gamehouse.com/

require 'rubygems'
require 'facets/core/enumerable/permutation'
require 'set'

#usage: texttwist [word]
# specifying a word will use /usr/share/dict/words to solve a TextTwist
# problem. If no word is specified, a random word will be selected, to
# generate an round of texttwist.

#load dictionary
matcher=Set.new
allwords=Array.new
open("/usr/share/dict/words") do |f|
   f.each do |line|
      line.chomp!
      next if line !~ /^[a-z]+$/
      matcher << line if line.length<=6
      allwords << line if line.length==6
   end
end

#generate subwords of a word
word=ARGV[0] || allwords[rand(allwords.length)]
thiswordmatcher=Set.new
word.split(//).each_permutation do |perm|
   perm=perm.join
   (3..6).each do |len|
      candidate=perm[0,len]
      if matcher.include?(candidate)
   thiswordmatcher << candidate
      end
   end
end

#output
puts word
puts "======"
thiswordmatcher.each do |subword|
   puts subword
end
Daniel F. (Guest)
on 2007-01-10 04:18
(Received via mailing list)
Hi Ken,

Ken B. wrote:
> #load dictionary
> matcher=Set.new
> allwords=Array.new

> thiswordmatcher=Set.new

I'm interested in why you used a set for matcher and an array for
allwords.  Was this a kindof random decision (many of mine are!) or is
there something that allwords needed to be able to do and so is an array
or vice versa?

Thanks,
Dan
James G. (Guest)
on 2007-01-10 05:58
(Received via mailing list)
On Jan 9, 2007, at 6:28 PM, Daniel F. wrote:

> If you are using *nix (unix, linux) then you can run "time <ruby
> program>" at the command line.  This might work with Macs as well
> because they are based on unix but I don't have one so I'm not sure.

It works on a Mac, sure.

James Edward G. II
Ken B. (Guest)
on 2007-01-10 06:10
(Received via mailing list)
On Wed, 10 Jan 2007 11:17:24 +0900, Daniel F. wrote:

> allwords.  Was this a kindof random decision (many of mine are!) or is
> there something that allwords needed to be able to do and so is an array
> or vice versa?

allwords needs array indexing to pick a random word from the array.
The sets, however, need duplicate removal and/or need include? to
operate
in O(1).

--Ken
James G. (Guest)
on 2007-01-10 17:01
(Received via mailing list)
On Jan 7, 2007, at 6:47 AM, Fedor L. wrote:

> It just so happened that I was learning ruby last summer and wrote
> a program to cheat for me at TextTwist. Needless to say the game
> got boring really fast, but it was neat writing the program. I've
> modified it a bit to instead play the game, but I'm a fairly new
> user and would appreciate any feedback, both ruby related and
> general programming.

Looking pretty good to me.

> Also, I have the problem that when running this on Windows it
> doesn't allow me to manipulate files with Ruby, giving me a
> Permission Denied error on File.open. Any idea why this might be?

Well, you pass open() three arguments:

   File.open("reducedwordlist#{i}.txt", "w", File::CREAT) do |fout|

I'm pretty sure that form has the third argument being the
permissions of the new file.  Take out the File::CREAT, the "w" mode
string handles that automatically, and see if that fixes it up.

Hope that helps.

James Edward G. II
James G. (Guest)
on 2007-01-10 17:14
(Received via mailing list)
On Jan 7, 2007, at 6:41 PM, Daniel F. wrote:

> Oops, that doesn't match {3,6} just {6}.
>
> >> regex = /^([a-z])(?!\1)([a-z])(?!\1|\2)([a-z])(?:(?!\1|\2|\3)([a-
> z]))?(?:(?!\1|\2|\3|\4)([a-z]))?(?:(?!\1|\2|\3|\4|\5)([a-z]))?$/

Wow.  That's darn clever, but probably over abusing regular
expressions a bit, don't you think?  ;)

James Edward G. II
James G. (Guest)
on 2007-01-10 17:26
(Received via mailing list)
On Jan 9, 2007, at 6:11 PM, Jason M. wrote:

> And finally, here's to you, Mr. Ruby Q. creator guy, for coming
> up with a Ruby quiz that really peaked my interest :)

Thank Ben B..  It was his idea.

James Edward G. II
James G. (Guest)
on 2007-01-10 17:28
(Received via mailing list)
On Jan 9, 2007, at 6:28 PM, Daniel F. wrote:

> I also enjoyed the opportunity to explore sets and things; more RQs
> that can involve the standard library would be awesome.

You make the quizzes and I will run them:

   removed_email_address@domain.invalid

James Edward G. II
Daniel F. (Guest)
on 2007-01-10 22:19
(Received via mailing list)
That makes sense :).

On a side note, I did make some methods for getting a rand value from
any enumerable (they can be integrated as instance methods):

def rand_value_each(enum)
  index = rand(enum.size)
  curr = 0
  enum.each {|x| return [x].flatten.last if curr == index; curr += 1 }
end

def rand_value_to_a(enum)
  [rand_value_ary(enum.to_a)].flatten.last
end

def rand_value_ary(ary)
  ary[rand(ary.size)]
end

Dan
James G. (Guest)
on 2007-01-10 22:24
(Received via mailing list)
On Jan 10, 2007, at 2:18 PM, Daniel F. wrote:

> On a side note, I did make some methods for getting a rand value
> from any enumerable (they can be integrated as instance methods):
>
> def rand_value_each(enum)
>   index = rand(enum.size)

Enumerable doesn't have a size() method.

>   curr = 0
>   enum.each {|x| return [x].flatten.last if curr == index; curr += 1 }
> end

James Edward G. II
Daniel F. (Guest)
on 2007-01-10 22:27
(Received via mailing list)
It does hurt readability, however, if it is in a variable then the
readability goes up a little.  regex is probably a bad name, tho.

noRepeats3-6Letters = ...
dict.scan(noRepeats3-6Letters)

Even better would be a method that creates a regex for an arbitrary
length so it's ugliness is never shown in its entirety in one
concentrated spot.  The king of readability, I admit, doesn't involve
/regexp?e[ns]/ at all.

class Array
def uniq?
(uniq == self)
end
end

class String
def letters
split(//)
end
end

dict.select {|x| x.length.between?(3, 6) && x.letters.uniq?}

dan
T. W. Urp (Guest)
on 2007-01-10 23:45
(Received via mailing list)
Vincent F. wrote long time ago:
>"Try transposing your matrix first, if that is what you need. Are you
>dealing with transformation matrices (which are not real matrices) ?"
>Yes indeed, SVG 2D transformation matrices. These matrices are 3x3.

They never taught me about transposing, just matrix multiplication, and
googel didnt help either. What is transposing (for transformation
matrices)?


Cameron McBride wrote:
>"the included Matrix library in ruby is NOT FAST.  There are other libs
>that do this on the C level, such as NArray, that will allow much better
>performance for some graphical uses."

Thanks. While my simple graphic
genny isnt time-critical, and a SVG file might take 2-3 seconds to
render,
I will look into NArray. Is NArray what you would recommend?
Gavin K. (Guest)
on 2007-01-10 23:55
(Received via mailing list)
T. W. Urp wrote:
> Vincent F. wrote long time ago:
> >"Try transposing your matrix first, if that is what you need. Are you
> >dealing with transformation matrices (which are not real matrices) ?"
> >Yes indeed, SVG 2D transformation matrices. These matrices are 3x3.
>
> They never taught me about transposing, just matrix multiplication, and
> googel didnt help either. What is transposing (for transformation matrices)?

If you have a 3x3 matrix like:
  a b c
  d e f
  g h i
then its transpose is:
  a d g
  b e h
  c f i

If you have a 2x3 matrix like:
  a b c
  d e f
then its transpose is:
  a d
  b e
  c f

In other words, swap rows and columns. (Excel even knows how to do
this, under Paste Special.)
Gavin K. (Guest)
on 2007-01-10 23:56
(Received via mailing list)
T. W. Urp wrote:
> They never taught me about transposing, just matrix multiplication, and
> googel didnt help either. What is transposing (for transformation matrices)?

Also, RTFM :)

C:\>ri transpose
More than one method matched your request. You can refine
your search by asking for information on one of:

     Array#transpose, Matrix#transpose


C:\>ri Array.transpose
--------------------------------------------------------
Array#transpose
     array.transpose -> an_array
------------------------------------------------------------------------
     Assumes that _self_ is an array of arrays and transposes the rows
     and columns.

        a = [[1,2], [3,4], [5,6]]
        a.transpose   #=> [[1, 3, 5], [2, 4, 6]]


C:\>ri Matrix.transpose
-------------------------------------------------------
Matrix#transpose
     transpose()
------------------------------------------------------------------------
     Returns the transpose of the matrix.

       Matrix[[1,2], [3,4], [5,6]]
         => 1 2
            3 4
            5 6
       Matrix[[1,2], [3,4], [5,6]].transpose
         => 1 3 5
            2 4 6


     (also known as t)
David K. (Guest)
on 2007-01-11 00:10
(Received via mailing list)
"T. W. Urp" <removed_email_address@domain.invalid> writes:

> Vincent F. wrote long time ago:
>>"Try transposing your matrix first, if that is what you need. Are you
>>dealing with transformation matrices (which are not real matrices) ?"
>>Yes indeed, SVG 2D transformation matrices. These matrices are 3x3.
>
> They never taught me about transposing, just matrix multiplication,
> and googel didnt help either. What is transposing (for
> transformation matrices)?

Matthew 20:16 for the dimensions.
Fedor L. (Guest)
on 2007-01-11 00:12
(Received via mailing list)
On 1/10/07, James Edward G. II <removed_email_address@domain.invalid> wrote:

> James Edward G. II
>
>
>
Aah, thanks so much. I must have misread that before, never noticed the
bit
about permissions. I tried changing that line before but it still didn't
work since I was trying to modify the file I created with the weird
permission (whatever permission option File::CREAT would be interpreted
as).
Erasing and starting over it all works as expected, creating and
overwriting
the file on subsequent calls. Thanks!
T. W. Urp (Guest)
on 2007-01-11 01:15
(Received via mailing list)
On Wed, 10 Jan 2007 13:54:43 -0800, Phrogz wrote:
> C:\>ri transpose
> More than one method matched your request. You can refine
> your search by asking for information on one of:
>
>      Array#transpose, Matrix#transpose
> C:\>ri Array.transpose
> C:\>ri Matrix.transpose

Thats very nice. I never realized such a great help tool exists! Thank
you
Phrogz!! :)
William J. (Guest)
on 2007-01-11 04:16
(Received via mailing list)
Ruby Q. wrote:
>
> published in various places around the web.
> Bonus points for building a completely functional game!
>
>   [1]: http://games.yahoo.com/games/texttwist.html (just one example, java)
>   [2]: http://www.gamehouse.com/

class String
  def chars
    split("")
  end
  def sorted
    chars.sort.join
  end
end

# Generate combinations.
def comb array, n, str = "", &blk
  0.upto(array.size - n){|i|
    if 1 == n
      yield str + array[i]
    else
      comb array[i+1..-1], n-1, str+array[i], &blk
    end
  }
end

word_groups = Hash.new {[]}
shorts = Hash.new {[]}
while word = gets do
  next unless (word=word.downcase.delete('^a-z')).size.between?(3,6)
  if 6 == word.size
    word_groups[word.sorted] += [ word ]
  else
    shorts[word.sorted] += [ word ]
  end
end

word_groups.each_key{|key|
  3.upto(5){|n|
    combinations = []
    comb( key.chars, n ){|s| combinations << s}
    combinations.uniq.each{|s| word_groups[key] += shorts[s] }}}
M. Edward (Ed) Borasky (Guest)
on 2007-01-11 04:46
(Received via mailing list)
T. W. Urp wrote:
> Vincent F. wrote long time ago:
>
>> "Try transposing your matrix first, if that is what you need. Are you
>> dealing with transformation matrices (which are not real matrices) ?"
>> Yes indeed, SVG 2D transformation matrices. These matrices are 3x3.
>>
>
> They never taught me about transposing, just matrix multiplication, and
> googel didnt help either. What is transposing (for transformation matrices)?
>
Given a matrix "a" with "m" rows and "n" columns

for i = 1 to m do
  for j = 1 to n do
    atranspose[j, i] = a[i, j]
  end
end
> I will look into NArray. Is NArray what you would recommend?
>
Yes!


--
M. Edward (Ed) Borasky, FBG, AB, PTA, PGS, MS, MNLP, NST, ACMC(P)
http://borasky-research.blogspot.com/

If God had meant for carrots to be eaten cooked, He would have given
rabbits fire.
Martin DeMello (Guest)
on 2007-01-11 13:23
(Received via mailing list)
This just solves the find-all-subwords problem:

target = ARGV[0]
dict = ARGV[1] || 'sowpods'

reduced = target.split(//).sort.uniq.join
primes = [2, 3, 5, 7, 11, 13]
factors = []
reduced.split(//).each_with_index {|e, i|
  factors[e[0]] = primes[i]
}

target_num = 1
target.each_byte {|i| target_num *= factors[i]}

IO.foreach(dict) {|word|
  word.chomp!
  next unless (word =~ /^[#{reduced}]+$/) &&
    (word.length < 7) && (word.length > 2)
  p = 1
  word.each_byte {|i| p *= factors[i]}
  puts word if target_num % p == 0
}
Fedor L. (Guest)
on 2007-01-11 16:16
(Received via mailing list)
On 1/11/07, Martin DeMello <removed_email_address@domain.invalid> wrote:
>   factors[e[0]] = primes[i]
>   word.each_byte {|i| p *= factors[i]}
>   puts word if target_num % p == 0
> }
>
>

That's neat, and is a good general way of checking for inclusion (so you
can
extend it past characters in a string which might not have an .include?
method). You probably don't want that .uniq in there though as that
excludes
you from matching 'hell' out of 'hello', for example.
Martin DeMello (Guest)
on 2007-01-11 16:27
(Received via mailing list)
On 1/11/07, Fedor L. <removed_email_address@domain.invalid> wrote:
> > reduced.split(//).each_with_index {|e, i|
> >   p = 1
> >   word.each_byte {|i| p *= factors[i]}
> >   puts word if target_num % p == 0
> > }
>
> That's neat, and is a good general way of checking for inclusion (so you can
> extend it past characters in a string which might not have an .include?
> method). You probably don't want that .uniq in there though as that excludes
> you from matching 'hell' out of 'hello', for example.

No, I do need the uniq since I want one prime per unique letter in the
target word. I then calculate its signature by a loop over the entire
word, not the reduced word, so "hell" is indeed matched for "hello"
but not for, say, "whelps".

martin
Matthew M. (Guest)
on 2007-01-19 17:30
(Received via mailing list)
I'd be interested in trying this, but I've avoided dictionary-type
quizzes in the past for lack of a good dictionary file.  Does anyone
have links to decent word/dictionary files?  Or perhaps does Mac OS X
come with one?
Jason M. (Guest)
on 2007-01-19 17:31
(Received via mailing list)
On 1/5/07, Ruby Q. <removed_email_address@domain.invalid> wrote:
>
>
> the
> Bonus points for building a completely functional game!
>
>        [1]: http://games.yahoo.com/games/texttwist.html (just one example,
> java)
>        [2]: http://www.gamehouse.com/



Is the goal to get the most points or to get to the highest round?  Do
you
get points based on the number of letters used (as in Upwords) or do you
get
points based on the obscurity of the letter to be used (as in scrabble)?
James G. (Guest)
on 2007-01-19 17:31
(Received via mailing list)
On Jan 5, 2007, at 9:53 AM, Matthew M. wrote:

> Or perhaps does Mac OS X come with one?

Sure:  /usr/share/dict/words

James Edward G. II
Jason M. (Guest)
on 2007-01-19 17:31
(Received via mailing list)
On 1/5/07, Matthew M. <removed_email_address@domain.invalid> wrote:

> I'd be interested in trying this, but I've avoided dictionary-type
> quizzes in the past for lack of a good dictionary file.  Does anyone
> have links to decent word/dictionary files?  Or perhaps does Mac OS X
> come with one?



 Official scrabble list of accepted 6 letter words:
http://www.math.utoronto.ca/jjchew/scrabble/lists/...
Oin M. (Guest)
on 2007-01-19 17:31
(Received via mailing list)
On Friday 05 January 2007 17:53, Matthew M. wrote:
> I'd be interested in trying this, but I've avoided dictionary-type
> quizzes in the past for lack of a good dictionary file.  Does anyone
> have links to decent word/dictionary files?  Or perhaps does Mac OS X
> come with one?

I'm using the ones here: http://wordlist.sourceforge.net/
I don't know how good they are, but they're ok for this particular quiz
IMHO
Jason M. (Guest)
on 2007-09-26 00:30
(Received via mailing list)
On 1/5/07, Jason M. <removed_email_address@domain.invalid> wrote:
> >
> >
> >  Official scrabble list of accepted 6 letter words:
> > http://www.math.utoronto.ca/jjchew/scrabble/lists/...
> >
>
> My mistake, that's the list of *obscure* accepted 6 letter words.  I'll
> find a better list this afternoon.
>

http://www.scrabulous.com/twl_dictionary.php
Complete list of accepted scrabble words.  You'll need to go through and
nuke any long words... I think learn Ruby in 21 days has a regex on day
8
that can assist people with this :)
Oin M. (Guest)
on 2007-09-26 00:34
(Received via mailing list)
did anyone find a unix-friendly text-twist?
the yahoo one doesn't like me: "Note: TextTwist is not compatible with
Unix or
Macintosh computers."
and every other site with texttwist seems to use the yahoo one.
James G. (Guest)
on 2007-09-26 00:36
(Received via mailing list)
On Jan 5, 2007, at 7:53 AM, Jason M. wrote:

> Is the goal to get the most points or to get to the highest round?
> Do you
> get points based on the number of letters used (as in Upwords) or
> do you get
> points based on the obscurity of the letter to be used (as in
> scrabble)?

My solution doesn't track "points" per say, just the rounds really.
So for my code, it's getting to the highest round.

Feel free to add some scoring though.  I would probably score based
on words found, with bigger words earning more points.

James Edward G. II
Jason M. (Guest)
on 2007-09-26 00:38
(Received via mailing list)
On 1/5/07, Jason M. <removed_email_address@domain.invalid> wrote:
>  Official scrabble list of accepted 6 letter words:
> http://www.math.utoronto.ca/jjchew/scrabble/lists/...
>

My mistake, that's the list of *obscure* accepted 6 letter words.  I'll
find
a better list this afternoon.
Ben B. (Guest)
on 2007-09-26 00:41
(Received via mailing list)
On Sat, Jan 06, 2007, Oin M. wrote:
> did anyone find a unix-friendly text-twist?
> the yahoo one doesn't like me: "Note: TextTwist is not compatible with Unix or
> Macintosh computers."
> and every other site with texttwist seems to use the yahoo one.

It's just a java game.  I'm on a mac and it works fine.  If you have a
current JRE and an appropriate browser plugin, it should work.

Ben
This topic is locked and can not be replied to.