scrabbler 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README CHANGED
@@ -6,7 +6,16 @@ Solve scrabble puzzles by the means of dictionary lookups.
6
6
  :en - English/US dictonary based as provided in Firefox
7
7
 
8
8
  == CLI
9
- A commandline tool is provided
9
+ A commandline tool is provided, use like this:
10
+
11
+ scrabble [options] 'letters'
12
+ -l en Set language [en, nl]
13
+ -m max Limit the number of results
14
+ -t thresh Set the score threshold for the results
15
+ -e Show the score explanation
16
+ -h Show help
17
+
18
+ it is possible to give multiple sets of letters.
10
19
 
11
20
  == Author
12
21
  Peter Maas (pfmmaas [at] gmail [dot] com)
@@ -1,10 +1,12 @@
1
1
  require 'optparse'
2
+
2
3
  require File.dirname(__FILE__) + '/../lib/scrabbler.rb'
4
+ include SCRABBLR
3
5
 
4
6
  options = {:language => :en, :max => 10, :thresh => 10, :explanation => false}
5
7
 
6
8
  optionParser = OptionParser.new do |opts|
7
- opts.banner = "Usage: scrabblit [options, -h for help] 'letters'"
9
+ opts.banner = "Usage: scrabblit [options] 'letters'"
8
10
 
9
11
  opts.on("-l en", "Set language [en, nl]") do |l|
10
12
  options[:language] = l.to_sym
@@ -23,19 +25,20 @@ optionParser = OptionParser.new do |opts|
23
25
  puts opts
24
26
  exit
25
27
  end
26
- end.parse!
27
28
 
28
- if ARGV && ARGV.length > 0
29
- s = Scrabbler.new(options[:language])
30
- ARGV.each do |set|
31
- r = s.scrabble(set, options[:thresh], options[:language])
32
- puts '==> ' + set
33
- r[0...options[:max]].each do |score|
34
- puts "#{score.word} (#{score.score})"
35
- puts "-- #{score.explanation.join(", ")}" if options[:explanation]
36
- end
37
- puts ''
29
+ if !ARGV || ARGV.length <= 0
30
+ puts opts
31
+ exit
38
32
  end
39
- else
40
- puts optionParser.options.banner
41
- end
33
+ end.parse!
34
+
35
+ s = Scrabbler.new(options[:language])
36
+ ARGV.each do |set|
37
+ r = s.scrabble(set, options[:thresh], options[:language])
38
+ puts '==> ' + set
39
+ r[0...options[:max]].each do |score|
40
+ puts "#{score.word} (#{score.score})"
41
+ puts "-- #{score.explanation.join(", ")}" if options[:explanation]
42
+ end
43
+ puts ''
44
+ end
@@ -4,6 +4,8 @@
4
4
 
5
5
  # This module contains some extensions for the build-in array
6
6
  # type. Will not change existing methods
7
+ require 'enumerator'
8
+
7
9
  module ArrayExtensions
8
10
  # removes the first entry in the array for which the test returns true
9
11
  def remove_first(&test)
@@ -13,15 +15,6 @@ module ArrayExtensions
13
15
 
14
16
  nil # nothing removed, nothing to return
15
17
  end
16
-
17
- def chunk(chunk_size = 100)
18
- chunked = []
19
- (0...self.length).step(chunk_size) do |i|
20
- chunked[i/chunk_size] = self[i...(i+chunk_size)]
21
- end
22
-
23
- chunked
24
- end
25
18
  end
26
19
 
27
20
  class Array
@@ -1,22 +1,23 @@
1
1
  # Author:: Peter Maas (pfmmaas [at] gmail [dot] com)
2
2
  # Copyright:: Copyright (c) 2008
3
3
  # License:: Distributes under the same terms as Ruby
4
- #
5
-
6
- class Score
7
- attr_accessor(:word, :score, :explanation)
4
+ module SCRABBLR
5
+ # simple container for scrabble scores
6
+ class Score
7
+ attr_accessor(:word, :score, :explanation)
8
8
 
9
- def initialize(word, score, explanation)
10
- @word = word
11
- @score = score
12
- @explanation = explanation
13
- end
9
+ def initialize(word, score, explanation)
10
+ @word = word
11
+ @score = score
12
+ @explanation = explanation
13
+ end
14
14
 
15
- def inspect
16
- "Scrabble score [word=#{@word}, score=#{@score}]"
17
- end
15
+ def inspect
16
+ "Scrabble score [word=#{@word}, score=#{@score}]"
17
+ end
18
18
 
19
- def <=> other
20
- other.score <=> @score
21
- end
19
+ def <=> other
20
+ other.score <=> @score
21
+ end
22
+ end
22
23
  end
@@ -10,104 +10,104 @@ require File.dirname(__FILE__) +'/array_extensions'
10
10
  require File.dirname(__FILE__) +'/score'
11
11
  require File.dirname(__FILE__) +'/vocabulary'
12
12
 
13
-
14
- # Class for solving scrabble puzzles
15
- class Scrabbler
16
- MIN_WORD_SIZE = 3
13
+ module SCRABBLR
14
+ # Class for solving scrabble puzzles
15
+ class Scrabbler
16
+ MIN_WORD_SIZE = 3
17
17
 
18
- SCRABBLER_DICTIONARIES = {
19
- :nl => File.dirname(__FILE__) +"/data/nederlands.dict",
20
- :en => File.dirname(__FILE__) +"/data/en-US.dict"
21
- }
18
+ SCRABBLER_DICTIONARIES = {
19
+ :nl => File.dirname(__FILE__) +"/data/nederlands.dict",
20
+ :en => File.dirname(__FILE__) +"/data/en-US.dict"
21
+ }
22
22
 
23
- SCORES = YAML::load(File.new(File.dirname(__FILE__) + "/data/scores.yml").read)
24
- MAX_THREADS = 10;
23
+ SCORES = YAML::load(File.new(File.dirname(__FILE__) + "/data/scores.yml").read)
24
+ MAX_THREADS = 10;
25
25
 
26
- # TODO: rates in constant
26
+ # TODO: rates in constant
27
27
 
28
- # initializes the Scrabbler with:
29
- # - the array if the given object is of type array
30
- # - the dictonary file specified by the given symbos (:nl, :en)
31
- def initialize(_dictionary)
32
- case _dictionary
33
- when Symbol
34
- @dictionary = Vocabulary.new(File.read(SCRABBLER_DICTIONARIES[_dictionary]))
35
- @dictionary.info = _dictionary
36
- when Array
37
- @dictionary = Vocabulary.new(_dictionary)
38
- @dictionary.info = "based on supplied array"
28
+ # initializes the Scrabbler with:
29
+ # - the array if the given object is of type array
30
+ # - the dictonary file specified by the given symbos (:nl, :en)
31
+ def initialize(_dictionary)
32
+ case _dictionary
33
+ when Symbol
34
+ @dictionary = Vocabulary.new(File.read(SCRABBLER_DICTIONARIES[_dictionary]))
35
+ @dictionary.info = _dictionary
36
+ when Array
37
+ @dictionary = Vocabulary.new(_dictionary)
38
+ @dictionary.info = "based on supplied array"
39
+ end
39
40
  end
40
- end
41
41
 
42
- # find possible solutions of the given array of letters
43
- def scrabble(letters, min_score = 10, rating = :nl, wildcard = '$')
44
- raise "Wildcard can not be a word character" if wildcard =~ /[\w]/
42
+ # find possible solutions of the given array of letters
43
+ def scrabble(letters, min_score = 10, rating = :nl, wildcard = '$')
44
+ raise "Wildcard can not be a word character" if wildcard =~ /[\w]/
45
45
 
46
- letters = letters.scan(/[a-z#{wildcard}]/)
47
- num_wildcards = letters.select{|c| c == wildcard}.length # count the number of wildcards
46
+ letters = letters.scan(/[a-z#{wildcard}]/)
47
+ num_wildcards = letters.select{|c| c == wildcard}.length # count the number of wildcards
48
48
 
49
- # create a subset based on the word letters
50
- dictonary_subset = @dictionary.calculate_subset(letters, wildcard, MIN_WORD_SIZE)
49
+ # create a subset based on the word letters
50
+ dictonary_subset = @dictionary.calculate_subset(letters, wildcard, MIN_WORD_SIZE)
51
51
 
52
- scores, threads = [], []
53
- chunks = dictonary_subset.chunk(@dictionary.words.length / (MAX_THREADS - 1) + 1)
54
-
55
- chunks.each do |chunk|
56
- threads << Thread.new(chunk) do |c|
57
- chunk_scores = []
58
- chunk.each do |word|
59
- score = calculate_score(letters, word, num_wildcards, rating)
60
- chunk_scores << score if score && score.score > min_score
61
- end
62
- chunk_scores
63
- end
64
- end
52
+ scores, threads = [], []
53
+
54
+ dictonary_subset.each_slice(@dictionary.words.length / (MAX_THREADS - 1) + 1) do |slice|
55
+ threads << Thread.new(slice) do |s|
56
+ slice_scores = []
57
+ s.each do |word|
58
+ score = calculate_score(letters, word, num_wildcards, rating)
59
+ slice_scores << score if score && score.score > min_score
60
+ end
61
+ slice_scores
62
+ end
63
+ end
65
64
 
66
- threads.each {|t| scores = scores + t.value} # gather values from all threads
65
+ threads.each {|t| scores = scores + t.value} # gather values from all threads
67
66
 
68
- scores.sort
69
- end
67
+ scores.sort
68
+ end
70
69
 
71
- # calculate the score of the test_word. Doesn't return anything
72
- # if it is impossible to create the word from the given letters
73
- def calculate_score(letters, test_word, num_wild = 0, rating = :nl)
74
- return if letters.length < test_word.length
70
+ # calculate the score of the test_word. Doesn't return anything
71
+ # if it is impossible to create the word from the given letters
72
+ def calculate_score(letters, test_word, num_wild = 0, rating = :nl)
73
+ return if letters.length < test_word.length
75
74
 
76
- letters = letters.dup # make a copy, we'll use the array desctructively
77
- score_sum, score_explanation = 0, []
75
+ letters = letters.dup # make a copy, we'll use the array desctructively
76
+ score_sum, score_explanation = 0, []
78
77
 
79
- test_word.scan(/[a-z]/).each do |c|
80
- if letters.include? c
81
- letters.remove_first{|v| v == c} # we've used this letter, remove it
82
- curr_rate = SCORES[rating][c.to_sym];
83
- score_sum = score_sum + curr_rate
84
- score_explanation << "#{c} (#{curr_rate})"
85
- elsif num_wild > 0 # see if we've got any wildcards left
86
- num_wild = num_wild - 1
87
- score_explanation << "#{c} (wildcard)"
88
- else
89
- return
78
+ test_word.scan(/[a-z]/).each do |c|
79
+ if letters.include? c
80
+ letters.remove_first{|v| v == c} # we've used this letter, remove it
81
+ curr_rate = SCORES[rating][c.to_sym];
82
+ score_sum = score_sum + curr_rate
83
+ score_explanation << "#{c} (#{curr_rate})"
84
+ elsif num_wild > 0 # see if we've got any wildcards left
85
+ num_wild = num_wild - 1
86
+ score_explanation << "#{c} (wildcard)"
87
+ else
88
+ return
89
+ end
90
90
  end
91
- end
92
91
 
93
- # return the result
94
- Score.new(test_word, score_sum , score_explanation)
95
- end
92
+ # return the result
93
+ Score.new(test_word, score_sum , score_explanation)
94
+ end
96
95
 
97
- def inspect
98
- "scrabbler, dictionary: #{@dictionary.info}"
99
- end
96
+ def inspect
97
+ "scrabbler, dictionary: #{@dictionary.info}"
98
+ end
100
99
 
101
- def self.def_singletons(*args)
102
- args.each do |arg|
103
- module_eval <<-EOS
104
- def self.get_#{arg}
105
- @@instance_#{arg} ||= Scrabbler.new(:#{arg})
106
- end
107
- EOS
100
+ def self.def_singletons(*args)
101
+ args.each do |arg|
102
+ module_eval <<-EOS
103
+ def self.get_#{arg}
104
+ @@instance_#{arg} ||= Scrabbler.new(:#{arg})
105
+ end
106
+ EOS
107
+ end
108
108
  end
109
- end
110
109
 
111
- def_singletons *SCRABBLER_DICTIONARIES.keys
110
+ def_singletons *SCRABBLER_DICTIONARIES.keys
112
111
 
113
- end
112
+ end
113
+ end
@@ -2,50 +2,52 @@
2
2
  # Copyright:: Copyright (c) 2008
3
3
  # License:: Distributes under the same terms as Ruby
4
4
 
5
- # Vocabulary is a simple dictionary with options for creating subsets
6
- class Vocabulary
7
- attr_accessor(:words, :info, :index)
5
+ module SCRABBLR
6
+ # Vocabulary is a simple dictionary with options for creating subsets
7
+ class Vocabulary
8
+ attr_accessor(:words, :info, :index)
8
9
 
9
- # removes non-word characters
10
- SANITIZE_FILTER = proc{|v| v.chomp.downcase.gsub(/\W/,'')}
10
+ # removes non-word characters
11
+ SANITIZE_FILTER = proc{|v| v.chomp.downcase.gsub(/\W/,'')}
11
12
 
12
- def initialize(words)
13
- @words = words.map(&SANITIZE_FILTER)
14
- build_index
15
- end
13
+ def initialize(words)
14
+ @words = words.map(&SANITIZE_FILTER)
15
+ build_index
16
+ end
16
17
 
17
- def build_index
18
- @index = Hash.new { |hash, key| hash[key] = [] }
19
- @words.each do |w|
20
- w.scan(/[a-z]/).uniq.each do |c|
21
- @index[c.to_sym] << w
18
+ def build_index
19
+ @index = Hash.new { |hash, key| hash[key] = [] }
20
+ @words.each do |w|
21
+ w.scan(/[a-z]/).uniq.each do |c|
22
+ @index[c.to_sym] << w
23
+ end
22
24
  end
23
25
  end
24
- end
25
26
 
26
- def calculate_subset(letters, wildcard, min_word_size = 3)
27
- # create a subset based on the word letters
28
- lf = length_filter(min_word_size..letters.length)
27
+ def calculate_subset(letters, wildcard, min_word_size = 3)
28
+ # create a subset based on the word letters
29
+ lf = length_filter(min_word_size..letters.length)
29
30
 
30
- if letters.include? wildcard
31
- subset = @words.select(&lf)
32
- else
33
- subset = []
34
- letters.uniq.each do |c|
35
- subset = subset + index[c.to_sym]
31
+ if letters.include? wildcard
32
+ subset = @words.select(&lf)
33
+ else
34
+ subset = []
35
+ letters.uniq.each do |c|
36
+ subset = subset + index[c.to_sym]
37
+ end
38
+ subset = subset.select(&lf)
39
+ subset = subset.uniq.grep(create_pattern(letters, min_word_size))
36
40
  end
37
- subset = subset.select(&lf)
38
- subset = subset.uniq.grep(create_pattern(letters, min_word_size))
39
- end
40
41
 
41
- subset
42
- end
42
+ subset
43
+ end
43
44
 
44
- def length_filter(range)
45
- proc{|s| range.include? s.length}
46
- end
45
+ def length_filter(range)
46
+ proc{|s| range.include? s.length}
47
+ end
47
48
 
48
- def create_pattern(letters, min_word_size = 3)
49
- /^[#{letters.join}]{#{min_word_size},#{letters.length}}$/
49
+ def create_pattern(letters, min_word_size = 3)
50
+ /^[#{letters.join}]{#{min_word_size},#{letters.length}}$/
51
+ end
50
52
  end
51
53
  end
@@ -20,12 +20,5 @@ class TestScrabbler < Test::Unit::TestCase
20
20
  r.remove_first{|i| raise BlockCalled}
21
21
  end
22
22
  end
23
-
24
- def test_chunk
25
- r = %w(the quick brown fox jumps over the lazy dog)
26
- assert_equal([%w(the quick brown),%w(fox jumps over),%w(the lazy dog)], r.chunk(3))
27
-
28
- r = %w(the quick brown fox jumps over the lazy dog but didn't jump over the fast bunny)
29
- assert_equal([%w(the quick brown),%w(fox jumps over),%w(the lazy dog), %w(but didn't jump), %w(over the fast), %w(bunny)], r.chunk(3))
30
- end
23
+
31
24
  end
@@ -1,6 +1,8 @@
1
1
  require 'test/unit'
2
2
  require File.dirname(__FILE__) + '/../lib/scrabbler.rb'
3
3
 
4
+ include SCRABBLR
5
+
4
6
  # class to use in
5
7
  class BlockCalled < RuntimeError
6
8
  end
metadata CHANGED
@@ -3,7 +3,7 @@ rubygems_version: 0.9.4
3
3
  specification_version: 1
4
4
  name: scrabbler
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.1.2
6
+ version: 0.1.3
7
7
  date: 2008-01-30 00:00:00 +01:00
8
8
  summary: A bruteforce word finder for scrabble.
9
9
  require_paths: