# \[QUIZ\] DictionaryMatcher (#103)

**URL:** <https://rubytalk.org/t/quiz-dictionarymatcher-103/33192>\
**Category:** ruby-talk\
**Created:** [24 November 2006 23:21 UTC](https://rubytalk.org/t/quiz-dictionarymatcher-103/33192 "2006-11-24T23:21:24Z")\
**Posts on this page:** 18\
**Page:** 1

<div class="post-metadata">

**Author:** ![James\_Edward\_Gray\_II](https://avatars.discourse-cdn.com/v4/letter/j/ea5d25/32.png) [@James\_Edward\_Gray\_II](https://rubytalk.org/u/James_Edward_Gray_II)\
**Post date:** [24 November 2006 23:21 UTC](https://rubytalk.org/t/quiz-dictionarymatcher-103/33192/1 "2006-11-24T23:21:24Z")

</div>

The three rules of Ruby Quiz:

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

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

[http://www.rubyquiz.com/](http://www.rubyquiz.com/)

3. Enjoy!

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

> **···**
>
> -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
> 
> by Ken Bloom
> 
> From time to time someone asks on ruby-talk how they can write a regexp of the  
> form:
> 
> &nbsp;&nbsp;/alligator|crocodile|bear|dinosaur|...|seven-thousandth-word/
> 
> It's not hard to write such a regexp, but Ruby has in internal limit on how big  
> the regular expression can be, so users find they can't do this matching  
> function easily.
> 
> Implement a class DictionaryMatcher that determines whether any of the strings  
> added to it are substrings of a string S. This should function as almost a  
> drop-in replacement for a Regexp, therefore your implementation should support  
> the following operations:
> 
> &nbsp;&nbsp;# creates a new empty matcher  
> &nbsp;&nbsp;dm=DictionaryMatcher.new  
> &nbsp;&nbsp;  
> &nbsp;&nbsp;# adds strings to the matcher  
> &nbsp;&nbsp;dm \<\< "string"  
> &nbsp;&nbsp;dm \<\< "Ruby"  
> &nbsp;&nbsp;  
> &nbsp;&nbsp;# determines whether a given word was one of those added to the matcher  
> &nbsp;&nbsp;dm.include?("Ruby") # =\> true  
> &nbsp;&nbsp;dm.include?("missing") # =\> false  
> &nbsp;&nbsp;dm.include?("stringing you along") # =\> false  
> &nbsp;&nbsp;  
> &nbsp;&nbsp;# Regexp-like substing search  
> &nbsp;&nbsp;dm =~ "long string" # =\> 5  
> &nbsp;&nbsp;dm =~ "rub you the wrong way" # =\> nil  
> &nbsp;&nbsp;  
> &nbsp;&nbsp;# will automatically work as a result of implementing  
> &nbsp;&nbsp;# DictionaryMatcher#=~ (see String#=~)  
> &nbsp;&nbsp;"long string" =~ dm # =\> true  
> &nbsp;&nbsp;  
> &nbsp;&nbsp;# implement the rest of the interface implemented by Regexps (well, almost)  
> &nbsp;&nbsp;class DictionaryMatcher  
> &nbsp;&nbsp;&nbsp;&nbsp;alias\_method :===, :=~  
> &nbsp;&nbsp;&nbsp;&nbsp;alias\_method :match, :=~  
> &nbsp;&nbsp;end
> 
> If you can add additional features, like a case insensitivity option when  
> creating a new DictionaryMatcher this is also very useful.

---

<div class="post-metadata">

**Author:** ![David\_A\_Black3](https://avatars.discourse-cdn.com/v4/letter/d/6a8cbe/32.png) [@David\_A\_Black3](https://rubytalk.org/u/David_A_Black3)\
**Post date:** [24 November 2006 23:56 UTC](https://rubytalk.org/t/quiz-dictionarymatcher-103/33192/2 "2006-11-24T23:56:37Z")

</div>

Hi --

> by Ken Bloom
> 
> > From time to time someone asks on ruby-talk how they can write a regexp of the
> 
> form:
> 
> &nbsp;&nbsp;/alligator|crocodile|bear|dinosaur|...|seven-thousandth-word/
> 
> It's not hard to write such a regexp, but Ruby has in internal limit on how big  
> the regular expression can be, so users find they can't do this matching  
> function easily.
> 
> Implement a class DictionaryMatcher that determines whether any of the strings  
> added to it are substrings of a string S. This should function as almost a  
> drop-in replacement for a Regexp, therefore your implementation should support  
> the following operations:
> 
> &nbsp;&nbsp;# creates a new empty matcher  
> &nbsp;&nbsp;dm=DictionaryMatcher.new
> 
> &nbsp;&nbsp;# adds strings to the matcher  
> &nbsp;&nbsp;dm \<\< "string"  
> &nbsp;&nbsp;dm \<\< "Ruby"
> 
> &nbsp;&nbsp;# determines whether a given word was one of those added to the matcher  
> &nbsp;&nbsp;dm.include?("Ruby") # =\> true  
> &nbsp;&nbsp;dm.include?("missing") # =\> false  
> &nbsp;&nbsp;dm.include?("stringing you along") # =\> false
> 
> &nbsp;&nbsp;# Regexp-like substing search  
> &nbsp;&nbsp;dm =~ "long string" # =\> 5  
> &nbsp;&nbsp;dm =~ "rub you the wrong way" # =\> nil
> 
> &nbsp;&nbsp;# will automatically work as a result of implementing  
> &nbsp;&nbsp;# DictionaryMatcher#=~ (see String#=~)  
> &nbsp;&nbsp;"long string" =~ dm # =\> true
> 
> &nbsp;&nbsp;# implement the rest of the interface implemented by Regexps (well, almost)  
> &nbsp;&nbsp;class DictionaryMatcher  
> &nbsp;&nbsp;&nbsp;&nbsp;alias\_method :===, :=~  
> &nbsp;&nbsp;&nbsp;&nbsp;alias\_method :match, :=~  
> &nbsp;&nbsp;end
> 
> If you can add additional features, like a case insensitivity option when  
> creating a new DictionaryMatcher this is also very useful.

What's the ruling on priority and "greediness"? In other words,  
given:

&nbsp;&nbsp;&nbsp;dm \<\< "hi"  
&nbsp;&nbsp;&nbsp;dm \<\< "child"

what would:

&nbsp;&nbsp;&nbsp;dm =~ "children"

give? Would it be different if you added "child" first? Or is there  
a rule about finding the longest match?

David

> **···**
>
> On Sat, 25 Nov 2006, Ruby Quiz wrote:
> 
> --  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;David A. Black | dblack@wobblini.net  
> Author of "Ruby for Rails" [1] | Ruby/Rails training & consultancy [3]  
> DABlog (DAB's Weblog) [2] | Co-director, Ruby Central, Inc. [4]  
> [1] [Ruby for Rails](http://www.manning.com/black) | [3] [http://www.rubypowerandlight.com](http://www.rubypowerandlight.com)  
> [2] [http://dablog.rubypal.com](http://dablog.rubypal.com) | [4] [http://www.rubycentral.org](http://www.rubycentral.org)

---

<div class="post-metadata">

**Author:** ![Jamie\_Macey](https://avatars.discourse-cdn.com/v4/letter/j/9dc877/32.png) [@Jamie\_Macey](https://rubytalk.org/u/Jamie_Macey)\
**Post date:** [25 November 2006 00:19 UTC](https://rubytalk.org/t/quiz-dictionarymatcher-103/33192/3 "2006-11-25T00:19:23Z")

</div>

On my machine (ruby 1.8.4), I don't get this result. For me, 'long  
string' =~ /string/ returns the same as /string/ =~ 'long string',  
which is 5, not true.

Just a heads-up for anyone else using the provided code as the basis  
for a test case.

- Jamie

> **···**
>
> On 11/24/06, Ruby Quiz \<james@grayproductions.net\> wrote:
> 
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# will automatically work as a result of implementing  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# DictionaryMatcher#=~ (see String#=~)  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"long string" =~ dm # =\> true

---

<div class="post-metadata">

**Author:** ![Ken\_Bloom](https://avatars.discourse-cdn.com/v4/letter/k/f08c70/32.png) [@Ken\_Bloom](https://rubytalk.org/u/Ken_Bloom)\
**Post date:** [27 November 2006 00:10 UTC](https://rubytalk.org/t/quiz-dictionarymatcher-103/33192/4 "2006-11-27T00:10:08Z")

</div>

This DictionaryMatcher implementation implements a trie (prefix tree)  
combined with Knuth-Morris-Pratt string matching on O(n) time. The funny  
thing is I gave this solution as an answer to the same question on my  
algorithms final last semester, and it was marked wrong. The professor  
just didn't understand what I was doing.

I violated the interface a little since knowing which words matched can  
also be pretty important, and added the #scan method, because the  
questioner who inspired me to write this quiz actually told me that he  
wanted to count how many matches there were in the string.

require 'enumerator'

# The DictionaryMatcher class holds a collection of strings. It allows lookups to  
# determine which strings are included, and it can be used similarly  
# to a +Regexp+ in substring matching.  
class DictionaryMatcher  
&nbsp;&nbsp;&nbsp;#Create a DictionaryMatcher with no words in it  
&nbsp;&nbsp;&nbsp;def initialize  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@internal=Node.new  
&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;#Add a word to the DictionaryMatcher  
&nbsp;&nbsp;&nbsp;def add string  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;array=@internal.add string  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;parent\_indexes=compute\_failure\_function string  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;array.zip(parent\_indexes).each do |node,parentindex|  
&nbsp;&nbsp;&nbsp;node.failure=array[parentindex] if parentindex  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;nil  
&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;alias\_method :\<\<, :add

&nbsp;&nbsp;&nbsp;#Determine whether +string+ was previously \<tt\>add\</tt\>ed to the  
&nbsp;&nbsp;&nbsp;#DictionaryMatcher.  
&nbsp;&nbsp;&nbsp;def include? string  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@internal.include? string  
&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;#Determine whether one of the words in the DictionaryMatcher is a substring of  
&nbsp;&nbsp;&nbsp;#+string+. Returns a DictionaryMatcher::MatchData object if found, +nil+ if not  
&nbsp;&nbsp;&nbsp;#found.  
&nbsp;&nbsp;&nbsp;def match string  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;internal\_match(string){|md| return md}  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return nil  
&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;#Scans +string+ for all occurrances of strings in the DictionaryMatcher.  
&nbsp;&nbsp;&nbsp;#Overlapping matches are skipped (only the first one is yielded), and  
&nbsp;&nbsp;&nbsp;#when some strings in the  
&nbsp;&nbsp;&nbsp;#DictionaryMatcher are substrings of others, only the shortest match at a given  
&nbsp;&nbsp;&nbsp;#position is found.  
&nbsp;&nbsp;&nbsp;def scan string  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;internal\_match(string){|matchdata| yield matchdata}  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;nil  
&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;#Case equality. Similar to =~, but returns true or false.  
&nbsp;&nbsp;&nbsp;def === string  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;not match(string).nil?  
&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;#Determines whether one of the words in the DictionaryMatcher is a substring of  
&nbsp;&nbsp;&nbsp;#+string+. Returns the index of the match if found, +nil+ if not  
&nbsp;&nbsp;&nbsp;#found.  
&nbsp;&nbsp;&nbsp;def =~ string  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;x=match(string)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;x && x.index  
&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;#Contains the index matched, and the word matched  
&nbsp;&nbsp;&nbsp;MatchData=Struct.new(:index,:match)

&nbsp;&nbsp;&nbsp;private  
&nbsp;&nbsp;&nbsp;#Doing this globally for the whole word feels kludgy, but I didn't  
&nbsp;&nbsp;&nbsp;#want to figure out how to do this as a per-node function.  
&nbsp;&nbsp;&nbsp;#Basically copied from Cormen, Leiserson, Rivest and Stein  
&nbsp;&nbsp;&nbsp;#"Introduction to Algorithms" 2nd ed.  
&nbsp;&nbsp;&nbsp;def compute\_failure\_function p  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m=p.size  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pi=[0,0]  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;k=0  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;2.upto m do |q|  
&nbsp;&nbsp;&nbsp;k=pi[k] while k\>0 and p[k] != p[q-1]  
&nbsp;&nbsp;&nbsp;k=k+1 if p[k]==p[q-1]  
&nbsp;&nbsp;&nbsp;pi[q]=k  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pi  
&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;def internal\_match string  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;node=@internal  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;e=Enumerable::Enumerator.new(string,:each\_byte)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;e.each\_with\_index do |b,index|  
&nbsp;&nbsp;&nbsp;advance=false  
&nbsp;&nbsp;&nbsp;until advance  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;nextnode=node.transitions[b]  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if not nextnode  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;advance=true if node==node.failure #loops happen at the root  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;node=node.failure  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;elsif nextnode.endword?  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;yield MatchData.new(index-node.depth,string[index-node.depth..index])  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;advance=true  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;node=@internal  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;advance=true  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;node=nextnode  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;class Node #:nodoc:  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;attr\_accessor :transitions, :failure, :endword, :depth  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;alias\_method :endword?, :endword  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;def initialize depth=0  
&nbsp;&nbsp;&nbsp;@depth=depth  
&nbsp;&nbsp;&nbsp;@transitions={}  
&nbsp;&nbsp;&nbsp;@endword=false  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;def add string,offset=0, array=  
&nbsp;&nbsp;&nbsp;first=string[offset]  
&nbsp;&nbsp;&nbsp;if offset==string.size  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@endword=true  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;array \<\< self  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return array  
&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;array \<\< self  
&nbsp;&nbsp;&nbsp;node=(@transitions[first] ||= Node.new(@depth+1))  
&nbsp;&nbsp;&nbsp;return node.add(string,offset+1,array)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;def include? string, offset=0  
&nbsp;&nbsp;&nbsp;first=string[offset]  
&nbsp;&nbsp;&nbsp;if offset==string.size  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return @endword  
&nbsp;&nbsp;&nbsp;elsif not @transitions.include?(first)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return false  
&nbsp;&nbsp;&nbsp;else  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return @transitions[first].include?(string,offset+1)  
&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;def inspect; "x"; end  
&nbsp;&nbsp;&nbsp;end

end

> **···**
>
> On Sat, 25 Nov 2006 08:21:24 +0900, Ruby Quiz wrote:
> 
> > by Ken Bloom
> > 
> > From time to time someone asks on ruby-talk how they can write a regexp of the  
> > form:
> > 
> > &nbsp;&nbsp;/alligator|crocodile|bear|dinosaur|...|seven-thousandth-word/
> > 
> > It's not hard to write such a regexp, but Ruby has in internal limit on how big  
> > the regular expression can be, so users find they can't do this matching  
> > function easily.
> > 
> > Implement a class DictionaryMatcher that determines whether any of the strings  
> > added to it are substrings of a string S. This should function as almost a  
> > drop-in replacement for a Regexp, therefore your implementation should support  
> > the following operations:
> > 
> > &nbsp;&nbsp;# creates a new empty matcher  
> > &nbsp;&nbsp;dm=DictionaryMatcher.new  
> > &nbsp;&nbsp;  
> > &nbsp;&nbsp;# adds strings to the matcher  
> > &nbsp;&nbsp;dm \<\< "string"  
> > &nbsp;&nbsp;dm \<\< "Ruby"  
> > &nbsp;&nbsp;  
> > &nbsp;&nbsp;# determines whether a given word was one of those added to the matcher  
> > &nbsp;&nbsp;dm.include?("Ruby") # =\> true  
> > &nbsp;&nbsp;dm.include?("missing") # =\> false  
> > &nbsp;&nbsp;dm.include?("stringing you along") # =\> false  
> > &nbsp;&nbsp;  
> > &nbsp;&nbsp;# Regexp-like substing search  
> > &nbsp;&nbsp;dm =~ "long string" # =\> 5  
> > &nbsp;&nbsp;dm =~ "rub you the wrong way" # =\> nil  
> > &nbsp;&nbsp;  
> > &nbsp;&nbsp;# will automatically work as a result of implementing  
> > &nbsp;&nbsp;# DictionaryMatcher#=~ (see String#=~)  
> > &nbsp;&nbsp;"long string" =~ dm # =\> true  
> > &nbsp;&nbsp;  
> > &nbsp;&nbsp;# implement the rest of the interface implemented by Regexps (well, almost)  
> > &nbsp;&nbsp;class DictionaryMatcher  
> > &nbsp;&nbsp;&nbsp;&nbsp;alias\_method :===, :=~  
> > &nbsp;&nbsp;&nbsp;&nbsp;alias\_method :match, :=~  
> > &nbsp;&nbsp;end
> > 
> > If you can add additional features, like a case insensitivity option when  
> > creating a new DictionaryMatcher this is also very useful.
> 
> --  
> Ken Bloom. PhD candidate. Linguistic Cognition Laboratory.  
> Department of Computer Science. Illinois Institute of Technology.  
> [http://www.iit.edu/~kbloom1/](http://www.iit.edu/~kbloom1/)

---

<div class="post-metadata">

**Author:** ![Lou\_Scoras](https://avatars.discourse-cdn.com/v4/letter/l/ee59a6/32.png) [@Lou\_Scoras](https://rubytalk.org/u/Lou_Scoras)\
**Post date:** [27 November 2006 04:24 UTC](https://rubytalk.org/t/quiz-dictionarymatcher-103/33192/5 "2006-11-27T04:24:22Z")

</div>

# Author: Lou Scoras \<[louis.j.scoras@gmail.com](mailto:louis.j.scoras@gmail.com)\>  
# Date: Sun Nov 26 10:43:34 EST 2006

> **···**
>
> #  
> # q103.rb - Solution to Rubyquiz 103 (DictionaryMatcher)  
> #  
> # Implements DictionaryMatcher using a Trie. This version of  
> # DictionaryMatcher only matches complete words, but it wouldn't  
> # be too hard to modify to match any substring.
> 
> class Trie  
> &nbsp;&nbsp;def initialize  
> &nbsp;&nbsp;&nbsp;&nbsp;@children = Hash.new {|h,k| h[k] = Trie.new}  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;attr\_accessor :value
> 
> &nbsp;&nbsp;def []=(key, value)  
> &nbsp;&nbsp;&nbsp;&nbsp;insert(key, 0, value)  
> &nbsp;&nbsp;&nbsp;&nbsp;key  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;def [](key)  
> &nbsp;&nbsp;&nbsp;&nbsp;get(key,0)  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;def each &blk  
> &nbsp;&nbsp;&nbsp;&nbsp;\_each &blk  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;include Enumerable
> 
> &nbsp;&nbsp;def keys  
> &nbsp;&nbsp;&nbsp;&nbsp;inject([]) {|keys,(k,v)| keys \<\< k; keys}  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;def values  
> &nbsp;&nbsp;&nbsp;&nbsp;inject([]) {|vals,(k,v)| vals \<\< v; vals}  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;def each\_key &blk  
> &nbsp;&nbsp;&nbsp;&nbsp;keys.each &blk  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;def each\_value &blk  
> &nbsp;&nbsp;&nbsp;&nbsp;values.each &blk  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;def inspect(indent=0)  
> &nbsp;&nbsp;&nbsp;&nbsp;buff = ''  
> &nbsp;&nbsp;&nbsp;&nbsp;i = ' ' \* indent  
> &nbsp;&nbsp;&nbsp;&nbsp;buff \<\< i + "value: #{value}\n" if value  
> &nbsp;&nbsp;&nbsp;&nbsp;return buff unless @children.size \> 0  
> &nbsp;&nbsp;&nbsp;&nbsp;@children.each {|k,c|  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;buff \<\< "#{i}#{k} =\>\n"  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;buff \<\< c.inspect(indent+2)  
> &nbsp;&nbsp;&nbsp;&nbsp;}  
> &nbsp;&nbsp;&nbsp;&nbsp;buff  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;protected
> 
> &nbsp;&nbsp;def \_each(key='', &blk)  
> &nbsp;&nbsp;&nbsp;&nbsp;blk.call(key,value) if key != '' and value  
> &nbsp;&nbsp;&nbsp;&nbsp;@children.keys.sort.each do |k|  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@children[k].\_each(key + k,&blk)  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;def insert(key,offset,value)  
> &nbsp;&nbsp;&nbsp;&nbsp;if offset == key.length - 1  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@children[key[offset,1]].value = value  
> &nbsp;&nbsp;&nbsp;&nbsp;else  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@children[key[offset,1]].insert(key,offset+1,value)  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;def get(key,offset)  
> &nbsp;&nbsp;&nbsp;&nbsp;if offset == key.length - 1  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@children[key[offset,1]].value  
> &nbsp;&nbsp;&nbsp;&nbsp;else  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return nil unless @children.has\_key?(key[offset,1])  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@children[key[offset,1]].get(key,offset+1)  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;end  
> end
> 
> class DictionaryMatcher  
> &nbsp;&nbsp;def initialize(opts={})  
> &nbsp;&nbsp;&nbsp;&nbsp;@ignore\_case = opts[:ignore\_case]  
> &nbsp;&nbsp;&nbsp;&nbsp;@trie = Trie.new  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;def ignores\_case?  
> &nbsp;&nbsp;&nbsp;&nbsp;@ignore\_case  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;def \<\<(word)  
> &nbsp;&nbsp;&nbsp;&nbsp;word = word.downcase if ignores\_case?  
> &nbsp;&nbsp;&nbsp;&nbsp;@trie[word] = true  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;def words  
> &nbsp;&nbsp;&nbsp;&nbsp;@trie.keys  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;def include?(word)  
> &nbsp;&nbsp;&nbsp;&nbsp;!@trie[(ignores\_case?? word.downcase : word)].nil?  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;def =~(string)  
> &nbsp;&nbsp;&nbsp;&nbsp;words = string.split  
> &nbsp;&nbsp;&nbsp;&nbsp;positions = words.inject({}) { |h,w|  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;h[w] = string.index(w) unless h[w]; h  
> &nbsp;&nbsp;&nbsp;&nbsp;}  
> &nbsp;&nbsp;&nbsp;&nbsp;words.each do |word|  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return positions[word] if  
> &nbsp;&nbsp;&nbsp;include?(ignores\_case?? word.downcase : word)  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;return nil  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;alias\_method :===, :=~  
> &nbsp;&nbsp;alias\_method :match, :=~  
> end

---

<div class="post-metadata">

**Author:** ![Jamie\_Macey](https://avatars.discourse-cdn.com/v4/letter/j/9dc877/32.png) [@Jamie\_Macey](https://rubytalk.org/u/Jamie_Macey)\
**Post date:** [27 November 2006 04:40 UTC](https://rubytalk.org/t/quiz-dictionarymatcher-103/33192/6 "2006-11-27T04:40:31Z")

</div>

Well, I'm not sure I can add anything not covered by Adam's solution  
(forwarded in by James), but here goes.

I started with the naive solution:

class DictionaryMatcher \< Array  
&nbsp;&nbsp;def =~(string)  
&nbsp;&nbsp;&nbsp;&nbsp;self.map{|e| Regexp.new(e) =~ string }.compact.min  
&nbsp;&nbsp;end  
end

I then fleshed it out with a case insensitive option, and more tests.

- Jamie

class DictionaryMatcher \< Array  
&nbsp;&nbsp;alias\_method :===, :=~  
&nbsp;&nbsp;alias\_method :match, :=~

&nbsp;&nbsp;def initialize(default = [], options = nil)  
&nbsp;&nbsp;&nbsp;&nbsp;super(default)

&nbsp;&nbsp;&nbsp;&nbsp;unless options.nil? or options.is\_a? Fixnum  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;options = Regexp::IGNORECASE  
&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;@regexp\_options = options  
&nbsp;&nbsp;end

&nbsp;&nbsp;def =~(string)  
&nbsp;&nbsp;&nbsp;&nbsp;self.map{|e| Regexp.new(e, @regexp\_options) =~ string }.compact.min  
&nbsp;&nbsp;end  
end

class DictionaryMatcherTest \< Test::Unit::TestCase  
&nbsp;&nbsp;def test\_acceptance  
&nbsp;&nbsp;&nbsp;&nbsp;# creates a new empty matcher  
&nbsp;&nbsp;&nbsp;&nbsp;dm=DictionaryMatcher.new

&nbsp;&nbsp;&nbsp;&nbsp;# adds strings to the matcher  
&nbsp;&nbsp;&nbsp;&nbsp;dm \<\< "string"  
&nbsp;&nbsp;&nbsp;&nbsp;dm \<\< "Ruby"

&nbsp;&nbsp;&nbsp;&nbsp;# determines whether a given word was one of those added to the matcher  
&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal true, dm.include?("Ruby") # =\> true  
&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal false, dm.include?("missing") # =\> false  
&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal false, dm.include?("stringing you along") # =\> false

&nbsp;&nbsp;&nbsp;&nbsp;# Regexp-like substing search  
&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal 5, dm =~ "long string" # =\> 5  
&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal nil, dm =~ "rub you the wrong way" # =\> nil

&nbsp;&nbsp;&nbsp;&nbsp;# will automatically work as a result of implementing  
&nbsp;&nbsp;&nbsp;&nbsp;# DictionaryMatcher#=~ (see String#=~)  
&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal 5, "long string" =~ dm # =\> true  
&nbsp;&nbsp;end

&nbsp;&nbsp;def test\_include\_eh  
&nbsp;&nbsp;&nbsp;&nbsp;dm = DictionaryMatcher.new(['string', 'ruby', 'foo'])  
&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal true, dm.include?('string' )  
&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal true, dm.include?('ruby' )  
&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal true, dm.include?('foo' )  
&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal false, dm.include?('stringa')  
&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal false, dm.include?('astring')  
&nbsp;&nbsp;end

&nbsp;&nbsp;def test\_equals\_tilde  
&nbsp;&nbsp;&nbsp;&nbsp;dm = DictionaryMatcher.new(['string', 'ruby', 'foo'])  
&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal 0, dm =~ 'string'  
&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal 0, dm =~ 'string two'  
&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal 6, dm =~ 'three string'  
&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal 5, dm =~ 'four string five'  
&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal nil, dm =~ 'strng'

&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal 0, 'string' =~ dm  
&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal 2, 'a string b' =~ dm  
&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal nil, 'strng' =~ dm  
&nbsp;&nbsp;end

&nbsp;&nbsp;def test\_case\_sensitivity  
&nbsp;&nbsp;&nbsp;&nbsp;dm = DictionaryMatcher.new(['Foo','bar'])  
&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal 0, dm =~ 'Foo'  
&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal 0, dm =~ 'bar'  
&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal nil, dm =~ 'foo'  
&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal nil, dm =~ 'Bar'  
&nbsp;&nbsp;end

&nbsp;&nbsp;def test\_case\_insensitivity  
&nbsp;&nbsp;&nbsp;&nbsp;dm = DictionaryMatcher.new(['Foo','bar'], true)  
&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal 0, dm =~ 'Foo'  
&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal 0, dm =~ 'bar'  
&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal 0, dm =~ 'foo'  
&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal 0, dm =~ 'Bar'  
&nbsp;&nbsp;end

&nbsp;&nbsp;def test\_greediness  
&nbsp;&nbsp;&nbsp;&nbsp;dm = DictionaryMatcher.new(['hi','child'])  
&nbsp;&nbsp;&nbsp;&nbsp;r = /hi|child/  
&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal r =~ 'children', dm =~ 'children'

&nbsp;&nbsp;&nbsp;&nbsp;dm = DictionaryMatcher.new(['child','hi'])  
&nbsp;&nbsp;&nbsp;&nbsp;r = /child|hi/  
&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal r =~ 'children', dm =~ 'children'  
&nbsp;&nbsp;end

end

---

<div class="post-metadata">

**Author:** ![Ross\_Bamford4](https://avatars.discourse-cdn.com/v4/letter/r/ea666f/32.png) [@Ross\_Bamford4](https://rubytalk.org/u/Ross_Bamford4)\
**Post date:** [27 November 2006 16:44 UTC](https://rubytalk.org/t/quiz-dictionarymatcher-103/33192/7 "2006-11-27T16:44:48Z")

</div>

Here's my solution. Theres a bit more speed to be had out of it, but  
I'll be in trouble if I spend any more time on it, so I'll just post it  
up instead 😉

It's slower than some of the other solutions, but does seem to work. I  
ran the attached benchmark to gauge performance against a regexp (using  
Oniguruma under 1.9) and it's slow to build, but fast to match (I'm  
lazy, using hashes for my tree):

$ ruby9 bench.rb  
### CREATION (x10) ###  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;user system total real  
regexp 1.430000 0.020000 1.450000 ( 1.518385)  
tree matcher 15.290000 0.070000 15.360000 ( 15.727852)  
### MATCHING (x500) ###  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;user system total real  
regexp 0.830000 0.000000 0.830000 ( 0.865269)  
tree matcher 0.010000 0.000000 0.010000 ( 0.016855)

I also ran Ken's posted benchmark, under both 1.8 and (for fun) 1.9. My  
solution suffers slightly, but check out the speed increase in Ken's own  
solution:

$ ruby -v kbbench.rb  
ruby 1.8.5 (2006-08-25) [i686-linux]  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;user system total real  
Edwin Fine -- fill 0.480000 0.020000 0.500000 ( 0.536963)  
Edwin Fine -- test 3.310000 0.010000 3.320000 ( 3.383446)  
Ken Bloom -- fill 2.460000 0.070000 2.530000 ( 2.569271)  
Ken Bloom -- test 13.300000 0.030000 13.330000 ( 13.830023)  
Ross Bamford -- fill 1.570000 0.020000 1.590000 ( 1.635048)  
Ross Bamford -- test 5.470000 0.030000 5.500000 ( 5.595892)

$ ruby9 -v kbbench.rb  
ruby 1.9.0 (2006-11-26 patchlevel 0) [i686-linux]  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;user system total real  
Edwin Fine -- fill 0.460000 0.010000 0.470000 ( 0.530037)  
Edwin Fine -- test 2.990000 0.000000 2.990000 ( 3.947945)  
Ken Bloom -- fill 3.070000 0.060000 3.130000 ( 3.273754)  
Ken Bloom -- test 3.110000 0.010000 3.120000 ( 3.184959)  
Ross Bamford -- fill 1.270000 0.000000 1.270000 ( 1.314763)  
Ross Bamford -- test 6.070000 0.010000 6.080000 ( 6.164216)

Thanks for a fun quiz. Now I'd better get some work done... 🙂

[bench.rb](https://rubytalk.org/uploads/short-url/haPlZKWwJQsAEy5nsCMzybEED6N.rb) (763 Bytes)

[dm.rb](https://rubytalk.org/uploads/short-url/p8MhycfXqHn6NlQmczDRKsLrmOb.rb) (6.64 KB)

> **···**
>
> --  
> Ross Bamford - [rosco@roscopeco.REMOVE.co.uk](mailto:rosco@roscopeco.REMOVE.co.uk)

---

<div class="post-metadata">

**Author:** ![Jamie\_Macey](https://avatars.discourse-cdn.com/v4/letter/j/9dc877/32.png) [@Jamie\_Macey](https://rubytalk.org/u/Jamie_Macey)\
**Post date:** [25 November 2006 00:35 UTC](https://rubytalk.org/t/quiz-dictionarymatcher-103/33192/8 "2006-11-25T00:35:04Z")

</div>

I'm doing the same thing a regexp does.

In this case, both /hi|child/ =~ 'children' and /child|hi/ =~  
'children' return 0, so I'm returning the index of the first character  
in the string that matches the uber-regexp we're standing in for.

- Jamie

> **···**
>
> On 11/24/06, dblack@wobblini.net \<dblack@wobblini.net\> wrote:
> 
> > What's the ruling on priority and "greediness"? In other words,  
> > given:
> > 
> > &nbsp;&nbsp;&nbsp;dm \<\< "hi"  
> > &nbsp;&nbsp;&nbsp;dm \<\< "child"
> > 
> > what would:
> > 
> > &nbsp;&nbsp;&nbsp;dm =~ "children"
> > 
> > give? Would it be different if you added "child" first? Or is there  
> > a rule about finding the longest match?

---

<div class="post-metadata">

**Author:** ![Gareth\_Adams](https://avatars.discourse-cdn.com/v4/letter/g/c68b51/32.png) [@Gareth\_Adams](https://rubytalk.org/u/Gareth_Adams)\
**Post date:** [25 November 2006 09:35 UTC](https://rubytalk.org/t/quiz-dictionarymatcher-103/33192/9 "2006-11-25T09:35:50Z")

</div>

\<dblack \<at\> wobblini.net\> writes:

Hi,

> What's the ruling on priority and "greediness"? In other words,  
> given:
> 
> &nbsp;&nbsp;&nbsp;dm \<\< "hi"  
> &nbsp;&nbsp;&nbsp;dm \<\< "child"
> 
> what would:
> 
> &nbsp;&nbsp;&nbsp;dm =~ "children"
> 
> give?

I'd say that given the example:

> \> dm \<\< "string"  
> \> dm.include?("stringing you along") # =\> false

your example would return nil, as the purpose of DictionaryMatcher seems to be  
to match whole words, and not derivatives

---

<div class="post-metadata">

**Author:** ![Ken\_Bloom](https://avatars.discourse-cdn.com/v4/letter/k/f08c70/32.png) [@Ken\_Bloom](https://rubytalk.org/u/Ken_Bloom)\
**Post date:** [26 November 2006 00:20 UTC](https://rubytalk.org/t/quiz-dictionarymatcher-103/33192/10 "2006-11-26T00:20:07Z")

</div>

Jamie Macey wrote:

> \> # will automatically work as a result of implementing  
> \> # DictionaryMatcher#=~ (see String#=~)  
> \> "long string" =~ dm # =\> true
> 
> On my machine (ruby 1.8.4), I don't get this result. For me, 'long  
> string' =~ /string/ returns the same as /string/ =~ 'long string',  
> which is 5, not true.
> 
> Just a heads-up for anyone else using the provided code as the basis  
> for a test case.

One of the unwritten rules of Ruby Quiz is to do whatever seems  
appropriate with the interface, given the real interface.

You'll notice, for example, that a Regexp returns the offset from =~, a  
boolean from === (despite the documentation's claims to the contrary),  
and a MatchData object from #match.  
The alias\_method part of the interface simplifies this aspect of the  
interface to DictionaryMatcher, although it defintiely makes more sense  
to invent a MatchData object of some sort that can tell you what the  
matched word was, and use that as the return value to match.

--Ken

> **···**
>
> > On 11/24/06, Ruby Quiz \<james@grayproductions.net\> wrote:

---

<div class="post-metadata">

**Author:** ![Edwin\_Fine](https://avatars.discourse-cdn.com/v4/letter/e/b3f665/32.png) [@Edwin\_Fine](https://rubytalk.org/u/Edwin_Fine)\
**Post date:** [27 November 2006 02:47 UTC](https://rubytalk.org/t/quiz-dictionarymatcher-103/33192/11 "2006-11-27T02:47:38Z")

</div>

This solution uses a digital trie to encode the strings to search.  
A good background to this data structure can be found here:  
[http://www.ddj.com/184410528](http://www.ddj.com/184410528),

which also discusses a better solution that I did not explore (ternary  
search trees).

A trie has a search speed that is O(m), where m is the number of  
characters in the string to find in the dictionary. A hash table is  
O(1), which is theoretically faster than a trie, but in practice the  
trie is faster because the has table has to hash all characters in the  
string to create the hash key, whereas the trie can reject a string by  
matching as little as 1 character.

The solution trades memory usage for speed. Every string in the  
dictionary is organized into a hierarchy of character codes.  
Essentially, the dictionary encodes a DFA (deterministic finite  
automaton) where each character code is a transition from one node to  
the next one. This provides very fast search speeds, especially when a  
non-matching string is encountered. In other words, the trie can reject  
incorrect strings very quickly (as soon as a non-matching prefix to a  
valid string is seen).

To store a string in the dictionary, we start at the root (which is just  
a hash  
or array of the character codes of the first character of every string  
in the  
dictionary).

trie = root  
for each character code in the string to store  
&nbsp;&nbsp;trie[character code] ||= {} # or [], if arrays used  
&nbsp;&nbsp;trie = trie[character code] # drop down 1 level  
end  
trie[0] = true # Mark end of word (code 0 will never be used)

It is necessary to mark the end of a word because you can get words that  
are prefixes of other words (for example, can, canna, and cannabis).  
This allows you to decide if you want to use a least-prefix or greedy  
search. This code uses a least-prefix search that returns the shortest  
matching string in the dictionary. This is due to the requirements of  
the quiz. However, it should be easy enough to code a greedy search.

The search algorithm is surprisingly simple. The search space is the  
text that  
we want to search for matching strings. The code starts at the first  
character of the search space and tries to find a match in the  
dictionary anchored at that position. If it does not detect a match, it  
moves the anchor to the next character and starts again.

trie = root  
for each character code in the search space  
&nbsp;&nbsp;break unless character code is in trie  
&nbsp;&nbsp;trie = trie[character code]  
end  
found a valid string if trie[0] exists

Each character in the dictionary to be searched is an index into a hash.

Informal benchmarks on my system (Intel Core 2 Duo E6600 on Win XP 32  
bit)  
shows a memory usage of about 15MB when using a hash as the basic trie  
storage, and 30+Mb using an array, when storing a dictionary of 24,001  
words.  
The speed seems to be about the same either way, so the hash solution is  
the best bet.

The test code finds all words in the 24,001 word dictionary in a text of  
about  
600KB. On my system, this takes around 2 seconds. This does include a  
possibly  
incorrect optimization: once a word is matched, the anchor point is  
moved to  
after the word so that substrings within the word are not matched. This  
may  
or may not be what is desired, but removing this optimization almost  
doubles  
the run time.

The code itself is fairly short:

class DictionaryMatcher  
&nbsp;&nbsp;attr\_reader :word\_count

&nbsp;&nbsp;def initialize  
&nbsp;&nbsp;&nbsp;&nbsp;@trie = {}  
&nbsp;&nbsp;&nbsp;&nbsp;@word\_count = 0  
&nbsp;&nbsp;end

&nbsp;&nbsp;def add\_word(word)  
&nbsp;&nbsp;&nbsp;&nbsp;@word\_count += 1  
&nbsp;&nbsp;&nbsp;&nbsp;container = @trie

&nbsp;&nbsp;&nbsp;&nbsp;word.each\_byte do |b|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;container[b] = {} unless container.has\_key? b  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;container = container[b]  
&nbsp;&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;&nbsp;container[0] = true # Mark end of word  
&nbsp;&nbsp;end

&nbsp;&nbsp;def include?(word)  
&nbsp;&nbsp;&nbsp;&nbsp;container = @trie  
&nbsp;&nbsp;&nbsp;&nbsp;word.each\_byte do |b|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break unless container.has\_key? b  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;container = container[b]  
&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;container[0]  
&nbsp;&nbsp;end

&nbsp;&nbsp;def =~(text)  
&nbsp;&nbsp;&nbsp;&nbsp;text\_end = text.length - 1  
&nbsp;&nbsp;&nbsp;&nbsp;pos = 0

&nbsp;&nbsp;&nbsp;&nbsp;while pos \<= text\_end do  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;container = @trie

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pos.upto(text\_end) do |i|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;b = text[i]  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break unless container.has\_key? b  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;container = container[b]  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return pos if container[0] # Match  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pos += 1  
&nbsp;&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;&nbsp;nil  
&nbsp;&nbsp;end

&nbsp;&nbsp;# Return container of matches in text [[pos, len], ...]  
&nbsp;&nbsp;# or call block if provided (returns [])  
&nbsp;&nbsp;def find\_all\_matching(text, &block)  
&nbsp;&nbsp;&nbsp;&nbsp;matches = []  
&nbsp;&nbsp;&nbsp;&nbsp;block = lambda { |pos, len| matches \<\< [pos, len] } unless block  
&nbsp;&nbsp;&nbsp;&nbsp;pos = 0  
&nbsp;&nbsp;&nbsp;&nbsp;text\_end = text.length - 1

&nbsp;&nbsp;&nbsp;&nbsp;while pos \<= text\_end do  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;container = @trie  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;len = 0

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pos.upto(text\_end) do |i|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;b = text[i]  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break unless container.has\_key?(b)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;container = container[b]  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;len += 1  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if container[0] # Match  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;block.call(pos, len)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pos += len # Skip over word  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pos += 1  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;&nbsp;matches  
&nbsp;&nbsp;end

&nbsp;&nbsp;# implement much of the rest of the interface implemented by Regexps  
&nbsp;&nbsp;alias\_method :===, :=~  
&nbsp;&nbsp;alias\_method :match, :=~  
&nbsp;&nbsp;alias\_method :\<\<, :add\_word

&nbsp;&nbsp;# Add words from a file  
&nbsp;&nbsp;def add\_words(words\_file)  
&nbsp;&nbsp;&nbsp;&nbsp;IO.foreach(words\_file) do |line|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;add\_word line.chomp  
&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;end  
end

I have posted the code, Test::Unit tests, the test file, and dictionary  
file on my web site here:

[http://finecomputerconsultants.com/ruby\_quiz/dictionary\_matcher-0.1.tgz](http://finecomputerconsultants.com/ruby_quiz/dictionary_matcher-0.1.tgz)

I hope I have not violated any copyrights/lefts by supplying the text  
files; if so, let me know and I will remedy this.

> **···**
>
> --  
> Posted via [http://www.ruby-forum.com/](http://www.ruby-forum.com/).

---

<div class="post-metadata">

**Author:** ![David\_A\_Black3](https://avatars.discourse-cdn.com/v4/letter/d/6a8cbe/32.png) [@David\_A\_Black3](https://rubytalk.org/u/David_A_Black3)\
**Post date:** [25 November 2006 01:07 UTC](https://rubytalk.org/t/quiz-dictionarymatcher-103/33192/12 "2006-11-25T01:07:50Z")

</div>

Hi --

> > What's the ruling on priority and "greediness"? In other words,  
> > given:
> > 
> > &nbsp;&nbsp;&nbsp;dm \<\< "hi"  
> > &nbsp;&nbsp;&nbsp;dm \<\< "child"
> > 
> > what would:
> > 
> > &nbsp;&nbsp;&nbsp;dm =~ "children"
> > 
> > give? Would it be different if you added "child" first? Or is there  
> > a rule about finding the longest match?
> 
> I'm doing the same thing a regexp does.
> 
> In this case, both /hi|child/ =~ 'children' and /child|hi/ =~  
> 'children' return 0, so I'm returning the index of the first character  
> in the string that matches the uber-regexp we're standing in for.

Actually, a better example of what I was pondering would be:

&nbsp;&nbsp;&nbsp;dm \<\< "t"  
&nbsp;&nbsp;&nbsp;dm \<\< "ten"

&nbsp;&nbsp;&nbsp;dm =~ "tenth"

However, I guess as long as it's just the starting offset that's  
needed, and not the ending offset (i.e., we don't have to know how  
long the match is), it won't be an issue. My instinct was to want to  
know which string did the matching, but for starting offset purposes  
it doesn't matter.

David

> **···**
>
> On Sat, 25 Nov 2006, Jamie Macey wrote:
> 
> > On 11/24/06, dblack@wobblini.net \<dblack@wobblini.net\> wrote:
> 
> --  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;David A. Black | dblack@wobblini.net  
> Author of "Ruby for Rails" [1] | Ruby/Rails training & consultancy [3]  
> DABlog (DAB's Weblog) [2] | Co-director, Ruby Central, Inc. [4]  
> [1] [Ruby for Rails](http://www.manning.com/black) | [3] [http://www.rubypowerandlight.com](http://www.rubypowerandlight.com)  
> [2] [http://dablog.rubypal.com](http://dablog.rubypal.com) | [4] [http://www.rubycentral.org](http://www.rubycentral.org)

---

<div class="post-metadata">

**Author:** ![Ken\_Bloom](https://avatars.discourse-cdn.com/v4/letter/k/f08c70/32.png) [@Ken\_Bloom](https://rubytalk.org/u/Ken_Bloom)\
**Post date:** [27 November 2006 05:30 UTC](https://rubytalk.org/t/quiz-dictionarymatcher-103/33192/13 "2006-11-27T05:30:13Z")

</div>

> This solution uses a digital trie to encode the strings to search.  
> A good background to this data structure can be found here:  
> [Ternary Search Trees | Dr Dobb's](http://www.ddj.com/184410528),
> 
> which also discusses a better solution that I did not explore (ternary  
> search trees).
> 
> A trie has a search speed that is O(m), where m is the number of  
> characters in the string to find in the dictionary. A hash table is  
> O(1), which is theoretically faster than a trie, but in practice the  
> trie is faster because the has table has to hash all characters in the  
> string to create the hash key, whereas the trie can reject a string by  
> matching as little as 1 character.
> 
> The solution trades memory usage for speed. Every string in the  
> dictionary is organized into a hierarchy of character codes.  
> Essentially, the dictionary encodes a DFA (deterministic finite  
> automaton) where each character code is a transition from one node to  
> the next one. This provides very fast search speeds, especially when a  
> non-matching string is encountered. In other words, the trie can reject  
> incorrect strings very quickly (as soon as a non-matching prefix to a  
> valid string is seen).
> 
> To store a string in the dictionary, we start at the root (which is just  
> a hash  
> or array of the character codes of the first character of every string  
> in the  
> dictionary).
> 
> trie = root  
> for each character code in the string to store  
> &nbsp;&nbsp;trie[character code] ||= {} # or , if arrays used  
> &nbsp;&nbsp;trie = trie[character code] # drop down 1 level  
> end  
> trie[0] = true # Mark end of word (code 0 will never be used)
> 
> It is necessary to mark the end of a word because you can get words that  
> are prefixes of other words (for example, can, canna, and cannabis).  
> This allows you to decide if you want to use a least-prefix or greedy  
> search. This code uses a least-prefix search that returns the shortest  
> matching string in the dictionary. This is due to the requirements of  
> the quiz. However, it should be easy enough to code a greedy search.
> 
> The search algorithm is surprisingly simple. The search space is the  
> text that  
> we want to search for matching strings. The code starts at the first  
> character of the search space and tries to find a match in the  
> dictionary anchored at that position. If it does not detect a match, it  
> moves the anchor to the next character and starts again.
> 
> trie = root  
> for each character code in the search space  
> &nbsp;&nbsp;break unless character code is in trie  
> &nbsp;&nbsp;trie = trie[character code]  
> end  
> found a valid string if trie[0] exists
> 
> Each character in the dictionary to be searched is an index into a hash.
> 
> Informal benchmarks on my system (Intel Core 2 Duo E6600 on Win XP 32  
> bit)  
> shows a memory usage of about 15MB when using a hash as the basic trie  
> storage, and 30+Mb using an array, when storing a dictionary of 24,001  
> words.  
> The speed seems to be about the same either way, so the hash solution is  
> the best bet.
> 
> The test code finds all words in the 24,001 word dictionary in a text of  
> about  
> 600KB. On my system, this takes around 2 seconds. This does include a  
> possibly  
> incorrect optimization: once a word is matched, the anchor point is  
> moved to  
> after the word so that substrings within the word are not matched. This  
> may  
> or may not be what is desired, but removing this optimization almost  
> doubles  
> the run time.

[CODE SNIPPED]

> I have posted the code, Test::Unit tests, the test file, and dictionary  
> file on my web site here:
> 
> [http://finecomputerconsultants.com/ruby\_quiz/dictionary\_matcher-0.1.tgz](http://finecomputerconsultants.com/ruby_quiz/dictionary_matcher-0.1.tgz)
> 
> I hope I have not violated any copyrights/lefts by supplying the text  
> files; if so, let me know and I will remedy this.

Wow. My solution's a real slowpoke compared to yours, even thought mine's  
asymptotically faster: O(string.length) versus  
O(string.length\* max(keys.length))

I wonder what's slowing mine down so much, since these are in many ways  
the same.

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;user system total real  
Edwin Fine -- fill 0.810000 0.130000 0.940000 ( 0.939537)  
Edwin Fine -- test 5.580000 0.630000 6.210000 ( 6.244736)  
Ken Bloom -- fill 4.550000 0.460000 5.010000 ( 5.010708)  
Ken Bloom -- test 25.210000 0.960000 26.170000 ( 27.519233)

(tested using the files you provided -- I modified your interface just a  
little to alias your #find\_all\_matches method to #scan)

I'd be happy to benchmark anybody else's solution who implements #scan.

#!/usr/bin/env ruby  
open("practical-file-system-design.txt") do |f|  
&nbsp;&nbsp;&nbsp;FILEDATA=f.read  
end

open("words\_en.txt") do |f|  
&nbsp;&nbsp;&nbsp;DICTIONARY=f.readlines.map{|x| x.chomp}  
end

require 'benchmark'  
include Benchmark  
#the following files contain various implementations, renamed so as not to  
#conflict with each other  
require 'trie'  
require 'finedm'

TESTCLASSES={"Ken Bloom" =\> KenDictionaryMatcher,  
&nbsp;&nbsp;"Edwin Fine" =\> FineDictionaryMatcher}

bm(TESTCLASSES.keys.collect{|x| x.length}.max + 8) do |benchmarker|  
&nbsp;&nbsp;&nbsp;matcher=nil  
&nbsp;&nbsp;&nbsp;TESTCLASSES.each do |name,klass|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;benchmarker.report("#{name} -- fill") do  
&nbsp;&nbsp;&nbsp;matcher=klass.new  
&nbsp;&nbsp;&nbsp;DICTIONARY.each {|x| matcher \<\< x}  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;benchmarker.report("#{name} -- test") do  
&nbsp;&nbsp;&nbsp;matcher.scan(FILEDATA){}  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;end  
end  
\_\_END\_\_

> **···**
>
> On Mon, 27 Nov 2006 11:47:38 +0900, Edwin Fine wrote:
> 
> --  
> Ken Bloom. PhD candidate. Linguistic Cognition Laboratory.  
> Department of Computer Science. Illinois Institute of Technology.  
> [http://www.iit.edu/~kbloom1/](http://www.iit.edu/~kbloom1/)

---

<div class="post-metadata">

**Author:** ![Ken\_Bloom](https://avatars.discourse-cdn.com/v4/letter/k/f08c70/32.png) [@Ken\_Bloom](https://rubytalk.org/u/Ken_Bloom)\
**Post date:** [27 November 2006 05:45 UTC](https://rubytalk.org/t/quiz-dictionarymatcher-103/33192/14 "2006-11-27T05:45:09Z")

</div>

> This solution uses a digital trie to encode the strings to search.  
> A good background to this data structure can be found here:  
> [Ternary Search Trees | Dr Dobb's](http://www.ddj.com/184410528),
> 
> which also discusses a better solution that I did not explore (ternary  
> search trees).
> 
> A trie has a search speed that is O(m), where m is the number of  
> characters in the string to find in the dictionary. A hash table is  
> O(1), which is theoretically faster than a trie, but in practice the  
> trie is faster because the has table has to hash all characters in the  
> string to create the hash key, whereas the trie can reject a string by  
> matching as little as 1 character.
> 
> The solution trades memory usage for speed. Every string in the  
> dictionary is organized into a hierarchy of character codes.  
> Essentially, the dictionary encodes a DFA (deterministic finite  
> automaton) where each character code is a transition from one node to  
> the next one. This provides very fast search speeds, especially when a  
> non-matching string is encountered. In other words, the trie can reject  
> incorrect strings very quickly (as soon as a non-matching prefix to a  
> valid string is seen).
> 
> To store a string in the dictionary, we start at the root (which is just  
> a hash  
> or array of the character codes of the first character of every string  
> in the  
> dictionary).
> 
> trie = root  
> for each character code in the string to store  
> &nbsp;&nbsp;trie[character code] ||= {} # or , if arrays used  
> &nbsp;&nbsp;trie = trie[character code] # drop down 1 level  
> end  
> trie[0] = true # Mark end of word (code 0 will never be used)
> 
> It is necessary to mark the end of a word because you can get words that  
> are prefixes of other words (for example, can, canna, and cannabis).  
> This allows you to decide if you want to use a least-prefix or greedy  
> search. This code uses a least-prefix search that returns the shortest  
> matching string in the dictionary. This is due to the requirements of  
> the quiz. However, it should be easy enough to code a greedy search.
> 
> The search algorithm is surprisingly simple. The search space is the  
> text that  
> we want to search for matching strings. The code starts at the first  
> character of the search space and tries to find a match in the  
> dictionary anchored at that position. If it does not detect a match, it  
> moves the anchor to the next character and starts again.
> 
> trie = root  
> for each character code in the search space  
> &nbsp;&nbsp;break unless character code is in trie  
> &nbsp;&nbsp;trie = trie[character code]  
> end  
> found a valid string if trie[0] exists
> 
> Each character in the dictionary to be searched is an index into a hash.
> 
> Informal benchmarks on my system (Intel Core 2 Duo E6600 on Win XP 32  
> bit)  
> shows a memory usage of about 15MB when using a hash as the basic trie  
> storage, and 30+Mb using an array, when storing a dictionary of 24,001  
> words.  
> The speed seems to be about the same either way, so the hash solution is  
> the best bet.
> 
> The test code finds all words in the 24,001 word dictionary in a text of  
> about  
> 600KB. On my system, this takes around 2 seconds. This does include a  
> possibly  
> incorrect optimization: once a word is matched, the anchor point is  
> moved to  
> after the word so that substrings within the word are not matched. This  
> may  
> or may not be what is desired, but removing this optimization almost  
> doubles  
> the run time.

[CODE SNIPPED]

> I have posted the code, Test::Unit tests, the test file, and dictionary  
> file on my web site here:
> 
> [http://finecomputerconsultants.com/ruby\_quiz/dictionary\_matcher-0.1.tgz](http://finecomputerconsultants.com/ruby_quiz/dictionary_matcher-0.1.tgz)
> 
> I hope I have not violated any copyrights/lefts by supplying the text  
> files; if so, let me know and I will remedy this.

Wow. My solution's a real slowpoke compared to yours, even thought mine's  
asymptotically faster: O(string.length) versus  
O(string.length\* max(keys.length))

I wonder what's slowing mine down so much, since these are in many ways  
the same.

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;user system total real  
Edwin Fine -- fill 0.810000 0.130000 0.940000 ( 0.939537)  
Edwin Fine -- test 5.580000 0.630000 6.210000 ( 6.244736)  
Ken Bloom -- fill 4.550000 0.460000 5.010000 ( 5.010708)  
Ken Bloom -- test 25.210000 0.960000 26.170000 ( 27.519233)

(tested using the files you provided -- I modified your interface just a  
little to alias your #find\_all\_matches method to #scan)

I'd be happy to benchmark anybody else's solution who implements #scan.

#!/usr/bin/env ruby  
open("practical-file-system-design.txt") do |f|  
&nbsp;&nbsp;&nbsp;FILEDATA=f.read  
end

open("words\_en.txt") do |f|  
&nbsp;&nbsp;&nbsp;DICTIONARY=f.readlines.map{|x| x.chomp}  
end

require 'benchmark'  
include Benchmark  
#the following files contain various implementations, renamed so as not to  
#conflict with each other  
require 'trie'  
require 'finedm'

TESTCLASSES={"Ken Bloom" =\> KenDictionaryMatcher,  
&nbsp;&nbsp;"Edwin Fine" =\> FineDictionaryMatcher}

bm(TESTCLASSES.keys.collect{|x| x.length}.max + 8) do |benchmarker|  
&nbsp;&nbsp;&nbsp;matcher=nil  
&nbsp;&nbsp;&nbsp;TESTCLASSES.each do |name,klass|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;benchmarker.report("#{name} -- fill") do  
&nbsp;&nbsp;&nbsp;matcher=klass.new  
&nbsp;&nbsp;&nbsp;DICTIONARY.each {|x| matcher \<\< x}  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;benchmarker.report("#{name} -- test") do  
&nbsp;&nbsp;&nbsp;matcher.scan(FILEDATA){}  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;end  
end  
\_\_END\_\_

> **···**
>
> On Mon, 27 Nov 2006 11:47:38 +0900, Edwin Fine wrote:
> 
> --  
> Ken Bloom. PhD candidate. Linguistic Cognition Laboratory.  
> Department of Computer Science. Illinois Institute of Technology.  
> [http://www.iit.edu/~kbloom1/](http://www.iit.edu/~kbloom1/)

---

<div class="post-metadata">

**Author:** ![Edwin\_Fine](https://avatars.discourse-cdn.com/v4/letter/e/b3f665/32.png) [@Edwin\_Fine](https://rubytalk.org/u/Edwin_Fine)\
**Post date:** [27 November 2006 06:20 UTC](https://rubytalk.org/t/quiz-dictionarymatcher-103/33192/15 "2006-11-27T06:20:32Z")

</div>

> Wow. My solution's a real slowpoke compared to yours, even thought  
> mine's  
> asymptotically faster: O(string.length) versus  
> O(string.length\* max(keys.length))
> 
> I wonder what's slowing mine down so much, since these are in many ways  
> the same.

Well, do we get the same results from the scan of the big file? Recall  
that in the scan I skip over strings once I see they are in the  
dictionary, which cuts out a lot of work and may yield far fewer  
results.

For example, if the text to be scanned is

abacusqwertyark

and the dictionary contains the words abacus and ark, the index into the  
string will move as follows:

abacusqwertyark # Begin  
^

abacusqwertyark # Found abacus, skip it  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;^  
abacusqwertyark # Found ark  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;^

A solution that did not skip the words would find additional substrings,  
for example:

abacusqwertyark # Begin  
^

abacusqwertyark # Found abacus, start again on next character  
^  
... omit some steps ...

abacusqwertyark # Found "us"  
&nbsp;&nbsp;&nbsp;&nbsp;^

Maybe I'm cheating or there's some other bug in my code that omits  
results that you find 🙂

> **···**
>
> --  
> Posted via [http://www.ruby-forum.com/\](http://www.ruby-forum.com/%5C).

---

<div class="post-metadata">

**Author:** ![Ken\_Bloom](https://avatars.discourse-cdn.com/v4/letter/k/f08c70/32.png) [@Ken\_Bloom](https://rubytalk.org/u/Ken_Bloom)\
**Post date:** [27 November 2006 14:40 UTC](https://rubytalk.org/t/quiz-dictionarymatcher-103/33192/16 "2006-11-27T14:40:07Z")

</div>

Yes, I also skip over the remainder of a word, so we should get the same  
results. I can check that later though.

--Ken

> **···**
>
> On Mon, 27 Nov 2006 15:20:32 +0900, Edwin Fine wrote:
> 
> > Well, do we get the same results from the scan of the big file? Recall  
> > that in the scan I skip over strings once I see they are in the  
> > dictionary, which cuts out a lot of work and may yield far fewer  
> > results.
> 
> --  
> Ken Bloom. PhD candidate. Linguistic Cognition Laboratory.  
> Department of Computer Science. Illinois Institute of Technology.  
> [http://www.iit.edu/~kbloom1/](http://www.iit.edu/~kbloom1/)

---

<div class="post-metadata">

**Author:** ![Ken\_Bloom](https://avatars.discourse-cdn.com/v4/letter/k/f08c70/32.png) [@Ken\_Bloom](https://rubytalk.org/u/Ken_Bloom)\
**Post date:** [29 November 2006 15:10 UTC](https://rubytalk.org/t/quiz-dictionarymatcher-103/33192/17 "2006-11-29T15:10:18Z")

</div>

Apparently, part of my problem was the use of the Enumerator object.  
Eliminating that and keeping track of the index manually saves about 5  
seconds off the benchmark.

I also took my solution and reimplemented it in your code --  
instead of having Node objects, I just use your hashes now and for the  
special fields I need, I use hash[:failure] and hash[:depth]. This saved  
about 15 seconds, so my new solution (based on your code) is only about a  
second slower than your solution, which is probably acceptable given the  
length of the words in the dictionary file. If the entries in the  
dictionary were longer, then my new solution should start to win. And by  
the way, both of these solutions win out over fairly large regular  
expressions pretty easily, since regular expressions have to backtrack to  
try each branch.

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;user system total real  
Edwin Fine -- fill 0.840000 0.090000 0.930000 ( 0.933338)  
Edwin Fine -- test 5.650000 0.540000 6.190000 ( 6.194274)  
Ken Bloom -- fill 3.800000 0.300000 4.100000 ( 4.104002)  
Ken Bloom -- test 6.800000 0.370000 7.170000 ( 7.167073)

#based on Edwin Fine's solution, this reimplements my solution with  
#less overhead all around.  
class DictionaryMatcher  
&nbsp;&nbsp;attr\_reader :word\_count

&nbsp;&nbsp;def initialize  
&nbsp;&nbsp;&nbsp;&nbsp;@trie = {}  
&nbsp;&nbsp;&nbsp;&nbsp;@word\_count = 0  
&nbsp;&nbsp;end

&nbsp;&nbsp;def add\_word(word)  
&nbsp;&nbsp;&nbsp;&nbsp;@word\_count += 1  
&nbsp;&nbsp;&nbsp;&nbsp;container = @trie  
&nbsp;&nbsp;&nbsp;&nbsp;containers=

&nbsp;&nbsp;&nbsp;&nbsp;i=0  
&nbsp;&nbsp;&nbsp;&nbsp;word.each\_byte do |b|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;container[b] = {} unless container.has\_key? b  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;container[:depth]=i  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;containers \<\< container  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;container = container[b]  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;i+=1  
&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;containers \<\< container

&nbsp;&nbsp;&nbsp;&nbsp;container[0] = true # Mark end of word  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;  
&nbsp;&nbsp;&nbsp;&nbsp;ff=compute\_failure\_function word  
&nbsp;&nbsp;&nbsp;&nbsp;ff.zip(containers).each do |pointto,container|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;container[:failure]=containers[pointto] if pointto  
&nbsp;&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;end

&nbsp;&nbsp;def compute\_failure\_function p  
&nbsp;&nbsp;&nbsp;&nbsp;m=p.size  
&nbsp;&nbsp;&nbsp;&nbsp;pi=[nil,0]  
&nbsp;&nbsp;&nbsp;&nbsp;k=0  
&nbsp;&nbsp;&nbsp;&nbsp;2.upto m do |q|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;k=pi[k] while k\>0 and p[k] != p[q-1]  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;k=k+1 if p[k]==p[q-1]  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pi[q]=k  
&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;pi  
&nbsp;&nbsp;end  
&nbsp;&nbsp;private :compute\_failure\_function

&nbsp;&nbsp;def include?(word)  
&nbsp;&nbsp;&nbsp;&nbsp;container = @trie  
&nbsp;&nbsp;&nbsp;&nbsp;word.each\_byte do |b|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break unless container.has\_key? b  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;container = container[b]  
&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;container[0]  
&nbsp;&nbsp;end

&nbsp;&nbsp;def =~ text  
&nbsp;&nbsp;&nbsp;&nbsp;internal\_match text {|pos,len| return pos}  
&nbsp;&nbsp;&nbsp;&nbsp;nil  
&nbsp;&nbsp;end

&nbsp;&nbsp;def internal\_match string  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;node=@trie  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pos=0  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;string.each\_byte do |b|  
&nbsp;&nbsp;&nbsp;advance=false  
&nbsp;&nbsp;&nbsp;until advance  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;nextnode=node[b]  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if not nextnode  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if node[:failure]  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;node=node[:failure]  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;advance=true  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;elsif nextnode[0]  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;yield pos, nextnode[:depth]  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;advance=true  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;node=@trie  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;advance=true  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;node=nextnode  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pos+=1  
&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;end  
&nbsp;&nbsp;private :internal\_match

&nbsp;&nbsp;def find\_all\_matching(text, &block)  
&nbsp;&nbsp;&nbsp;&nbsp;matches=  
&nbsp;&nbsp;&nbsp;&nbsp;block= lambda{ |pos,len| matches \<\< [pos,len] } unless block  
&nbsp;&nbsp;&nbsp;&nbsp;internal\_match(text,&block)  
&nbsp;&nbsp;&nbsp;&nbsp;matches  
&nbsp;&nbsp;end

&nbsp;&nbsp;alias\_method :scan, :find\_all\_matching

&nbsp;&nbsp;# implement much of the rest of the interface implemented by Regexps  
&nbsp;&nbsp;alias\_method :===, :=~  
&nbsp;&nbsp;alias\_method :match, :=~  
&nbsp;&nbsp;alias\_method :\<\<, :add\_word

&nbsp;&nbsp;# Add words from a file  
&nbsp;&nbsp;def add\_words(words\_file)  
&nbsp;&nbsp;&nbsp;&nbsp;IO.foreach(words\_file) do |line|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;add\_word line.chomp  
&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;end  
end

> **···**
>
> On Mon, 27 Nov 2006 15:20:32 +0900, Edwin Fine wrote:
> 
> > > Wow. My solution's a real slowpoke compared to yours, even thought  
> > > mine's  
> > > asymptotically faster: O(string.length) versus  
> > > O(string.length\* max(keys.length))
> > > 
> > > I wonder what's slowing mine down so much, since these are in many ways  
> > > the same.
> > 
> > Well, do we get the same results from the scan of the big file? Recall  
> > that in the scan I skip over strings once I see they are in the  
> > dictionary, which cuts out a lot of work and may yield far fewer  
> > results.
> > 
> > For example, if the text to be scanned is
> > 
> > abacusqwertyark
> > 
> > and the dictionary contains the words abacus and ark, the index into the  
> > string will move as follows:
> > 
> > abacusqwertyark # Begin  
> > ^
> > 
> > abacusqwertyark # Found abacus, skip it  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;^  
> > abacusqwertyark # Found ark  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;^
> > 
> > A solution that did not skip the words would find additional substrings,  
> > for example:
> > 
> > abacusqwertyark # Begin  
> > ^
> > 
> > abacusqwertyark # Found abacus, start again on next character  
> > ^  
> > .. omit some steps ...
> > 
> > abacusqwertyark # Found "us"  
> > &nbsp;&nbsp;&nbsp;&nbsp;^
> > 
> > Maybe I'm cheating or there's some other bug in my code that omits  
> > results that you find 🙂
> 
> --  
> Ken Bloom. PhD candidate. Linguistic Cognition Laboratory.  
> Department of Computer Science. Illinois Institute of Technology.  
> [http://www.iit.edu/~kbloom1/](http://www.iit.edu/~kbloom1/)

---

<div class="post-metadata">

**Author:** ![Ken\_Bloom](https://avatars.discourse-cdn.com/v4/letter/k/f08c70/32.png) [@Ken\_Bloom](https://rubytalk.org/u/Ken_Bloom)\
**Post date:** [29 November 2006 15:25 UTC](https://rubytalk.org/t/quiz-dictionarymatcher-103/33192/18 "2006-11-29T15:25:06Z")

</div>

Apparently, part of my problem was the use of the Enumerator object.  
Eliminating that and keeping track of the index itself saves about 5  
seconds off the benchmark.

I also took my solution and reimplemented it in your code --  
instead of having Node objects, I just use hashes now and for the special  
fields I need, I use hash[:failure] and hash[:depth]. This saved about 15  
seconds, so my new solution (based on your code) is only about a second  
slower than your solution, which is probably acceptable given the length  
of the words in the dictionary file. If the entries in the dictionary  
were longer, then my new solution should start to win. And by the way, both  
of these solutions win out over fairly large regular expressions pretty  
easily, since regular expressions have to backtrack to try each branch.

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;user system total real  
Edwin Fine -- fill 0.840000 0.090000 0.930000 ( 0.933338)  
Edwin Fine -- test 5.650000 0.540000 6.190000 ( 6.194274)  
Ken Bloom -- fill 3.800000 0.300000 4.100000 ( 4.104002)  
Ken Bloom -- test 6.800000 0.370000 7.170000 ( 7.167073)

#based on Edwin Fine's solution, this reimplements my solution with  
#less overhead all around.  
class DictionaryMatcher  
&nbsp;&nbsp;attr\_reader :word\_count

&nbsp;&nbsp;def initialize  
&nbsp;&nbsp;&nbsp;&nbsp;@trie = {}  
&nbsp;&nbsp;&nbsp;&nbsp;@word\_count = 0  
&nbsp;&nbsp;end

&nbsp;&nbsp;def add\_word(word)  
&nbsp;&nbsp;&nbsp;&nbsp;@word\_count += 1  
&nbsp;&nbsp;&nbsp;&nbsp;container = @trie  
&nbsp;&nbsp;&nbsp;&nbsp;containers=

&nbsp;&nbsp;&nbsp;&nbsp;i=0  
&nbsp;&nbsp;&nbsp;&nbsp;word.each\_byte do |b|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;container[b] = {} unless container.has\_key? b  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;container[:depth]=i  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;containers \<\< container  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;container = container[b]  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;i+=1  
&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;containers \<\< container

&nbsp;&nbsp;&nbsp;&nbsp;container[0] = true # Mark end of word  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;  
&nbsp;&nbsp;&nbsp;&nbsp;ff=compute\_failure\_function word  
&nbsp;&nbsp;&nbsp;&nbsp;ff.zip(containers).each do |pointto,container|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;container[:failure]=containers[pointto] if pointto  
&nbsp;&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;end

&nbsp;&nbsp;def compute\_failure\_function p  
&nbsp;&nbsp;&nbsp;&nbsp;m=p.size  
&nbsp;&nbsp;&nbsp;&nbsp;pi=[nil,0]  
&nbsp;&nbsp;&nbsp;&nbsp;k=0  
&nbsp;&nbsp;&nbsp;&nbsp;2.upto m do |q|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;k=pi[k] while k\>0 and p[k] != p[q-1]  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;k=k+1 if p[k]==p[q-1]  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pi[q]=k  
&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;pi  
&nbsp;&nbsp;end  
&nbsp;&nbsp;private :compute\_failure\_function

&nbsp;&nbsp;def include?(word)  
&nbsp;&nbsp;&nbsp;&nbsp;container = @trie  
&nbsp;&nbsp;&nbsp;&nbsp;word.each\_byte do |b|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break unless container.has\_key? b  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;container = container[b]  
&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;container[0]  
&nbsp;&nbsp;end

&nbsp;&nbsp;def =~ text  
&nbsp;&nbsp;&nbsp;&nbsp;internal\_match text {|pos,len| return pos}  
&nbsp;&nbsp;&nbsp;&nbsp;nil  
&nbsp;&nbsp;end

&nbsp;&nbsp;def internal\_match string  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;node=@trie  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pos=0  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;string.each\_byte do |b|  
&nbsp;&nbsp;&nbsp;advance=false  
&nbsp;&nbsp;&nbsp;until advance  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;nextnode=node[b]  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if not nextnode  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if node[:failure]  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;node=node[:failure]  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;advance=true  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;elsif nextnode[0]  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;yield pos, nextnode[:depth]  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;advance=true  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;node=@trie  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;advance=true  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;node=nextnode  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pos+=1  
&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;end  
&nbsp;&nbsp;private :internal\_match

&nbsp;&nbsp;def find\_all\_matching(text, &block)  
&nbsp;&nbsp;&nbsp;&nbsp;matches=  
&nbsp;&nbsp;&nbsp;&nbsp;block= lambda{ |pos,len| matches \<\< [pos,len] } unless block  
&nbsp;&nbsp;&nbsp;&nbsp;internal\_match(text,&block)  
&nbsp;&nbsp;&nbsp;&nbsp;matches  
&nbsp;&nbsp;end

&nbsp;&nbsp;alias\_method :scan, :find\_all\_matching

&nbsp;&nbsp;# implement much of the rest of the interface implemented by Regexps  
&nbsp;&nbsp;alias\_method :===, :=~  
&nbsp;&nbsp;alias\_method :match, :=~  
&nbsp;&nbsp;alias\_method :\<\<, :add\_word

&nbsp;&nbsp;# Add words from a file  
&nbsp;&nbsp;def add\_words(words\_file)  
&nbsp;&nbsp;&nbsp;&nbsp;IO.foreach(words\_file) do |line|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;add\_word line.chomp  
&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;end  
end

> **···**
>
> On Mon, 27 Nov 2006 15:20:32 +0900, Edwin Fine wrote:
> 
> > > Wow. My solution's a real slowpoke compared to yours, even thought  
> > > mine's  
> > > asymptotically faster: O(string.length) versus  
> > > O(string.length\* max(keys.length))
> > > 
> > > I wonder what's slowing mine down so much, since these are in many ways  
> > > the same.
> > 
> > Well, do we get the same results from the scan of the big file? Recall  
> > that in the scan I skip over strings once I see they are in the  
> > dictionary, which cuts out a lot of work and may yield far fewer  
> > results.
> > 
> > For example, if the text to be scanned is
> > 
> > abacusqwertyark
> > 
> > and the dictionary contains the words abacus and ark, the index into the  
> > string will move as follows:
> > 
> > abacusqwertyark # Begin  
> > ^
> > 
> > abacusqwertyark # Found abacus, skip it  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;^  
> > abacusqwertyark # Found ark  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;^
> > 
> > A solution that did not skip the words would find additional substrings,  
> > for example:
> > 
> > abacusqwertyark # Begin  
> > ^
> > 
> > abacusqwertyark # Found abacus, start again on next character  
> > ^  
> > .. omit some steps ...
> > 
> > abacusqwertyark # Found "us"  
> > &nbsp;&nbsp;&nbsp;&nbsp;^
> > 
> > Maybe I'm cheating or there's some other bug in my code that omits  
> > results that you find 🙂
> 
> --  
> Ken Bloom. PhD candidate. Linguistic Cognition Laboratory.  
> Department of Computer Science. Illinois Institute of Technology.  
> [http://www.iit.edu/~kbloom1/](http://www.iit.edu/~kbloom1/)
