Skip to content

Commit

Permalink
Merge 233b55f into a9252ee
Browse files Browse the repository at this point in the history
  • Loading branch information
toddthomas committed Apr 18, 2017
2 parents a9252ee + 233b55f commit 5a3f563
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 4 deletions.
9 changes: 9 additions & 0 deletions README.markdown
Expand Up @@ -41,6 +41,15 @@ an object that responds to `#===` to the exclude list.
This will prevent the return of the exact string `"bar"` and any word
which matches the regex `/fo+/`.

Constraining word length
----

You can constrain the length of words provided by the `nouns` and `adjs` iterators like so:
```ruby
RandomWord.nouns(not_longer_than: 56).next
RandomWord.adjs(not_shorter_than: 3).next
RandomWord.adjs(not_shorter_than: 16, not_longer_than: 672).next
```

Contributing to random-word
----
Expand Down
27 changes: 24 additions & 3 deletions lib/random_word.rb
Expand Up @@ -9,22 +9,35 @@
module RandomWord
module EachRandomly
attr_accessor :random_word_exclude_list
attr_accessor :min_length
attr_accessor :max_length

def each_randomly(&blk)
used = Set.new
skipped = Set.new
exclude_list = Array(@random_word_exclude_list)
while true
idx = next_unused_idx(used)
used << idx
word = at(idx)
next if exclude_list.any?{|r| r === word }
if exclude_list.any? { |r| r === word } || word.length < min_length || (!(max_length.nil?) && word.length > max_length)
skipped << idx
next
end
yield word
used.subtract(skipped)
skipped.clear
end

rescue OutOfWords
# we are done.
end

def set_constraints(opts)
@min_length = opts[:not_shorter_than] || 1
@max_length = opts[:not_longer_than] || nil
end

private
def next_unused_idx(used)
idx = rand(length)
Expand All @@ -41,18 +54,24 @@ class OutOfWords < Exception; end
end

class << self
attr_accessor :word_list

def exclude_list
@exclude_list ||= []
end

# @return [Enumerator] Random noun enumerator
def nouns
def nouns(opts = {})
@nouns ||= enumerator(load_word_list("nouns.dat"), exclude_list)
word_list.set_constraints(opts)
@nouns
end

# @return [Enumerator] Random adjective enumerator
def adjs
def adjs(opts = {})
@adjs ||= enumerator(load_word_list("adjs.dat"), exclude_list)
word_list.set_constraints(opts)
@adjs
end

# @return [Enumerator] Random phrase enumerator
Expand All @@ -69,8 +88,10 @@ def each()
# Create a random, non-repeating enumerator for a list of words
# (or anything really).
def enumerator(word_list, list_of_regexs_or_strings_to_exclude = [])
@word_list = word_list
word_list.extend EachRandomly
word_list.random_word_exclude_list = list_of_regexs_or_strings_to_exclude
word_list.set_constraints(not_shorter_than: 1, not_longer_than: nil)
word_list.enum_for(:each_randomly)
end

Expand Down
111 changes: 110 additions & 1 deletion spec/random_word_spec.rb
Expand Up @@ -13,7 +13,7 @@
expect{subject.next}.to raise_error(StopIteration)
end

it "make sure each word is only returned once" do
it "make sure each word is only returned once" do
already_received = []
3.times do
expect(new_word = subject.next).not_to be_one_of(already_received)
Expand Down Expand Up @@ -90,3 +90,112 @@

end

shared_examples 'allows constraints on word length' do |method|
context 'when constraining' do
let(:word_list) { %w(aa bbb cccc ddddd) }

before(:each) do
expect(RandomWord).to receive(:load_word_list).and_return(word_list)
end

after(:each) do
RandomWord.instance_eval{ @nouns, @adjs = nil, nil }
end

let(:next_words) do
[].tap do |next_words|
loop do
begin
next_words << RandomWord.send(method, length_constraints).next
rescue StopIteration
# We've tried all the words in the short test list we're using.
break
end
end
end
end

context 'by maximum length' do
let(:length_constraints) { {not_longer_than: 2} }

it 'excludes the correct words' do
expect(next_words).to match_array %w(aa)
end
end

context 'by minimum length' do
let(:length_constraints) { {not_shorter_than: 4} }

it 'excludes the correct words' do
expect(next_words).to match_array %w(cccc ddddd)
end

end

context 'by both minimum and maximum length' do
let(:length_constraints) { {not_shorter_than: 3, not_longer_than: 4} }

it 'excludes the correct words' do
expect(next_words).to match_array %w(bbb cccc)
end
end

context 'by a perverse minimum length' do
let(:length_constraints) { {not_shorter_than: -1234} }

it 'includes all words' do
expect(next_words).to match_array word_list
end
end

context 'by a perverse maximum length' do
let(:length_constraints) { {not_longer_than: -34234} }

it 'excludes all words' do
expect(next_words).to be_empty
end
end

context 'and all words are within the constraints' do
let(:length_constraints) { {not_shorter_than: 2, not_longer_than: 5} }

it 'includes all words' do
expect(next_words).to match_array word_list
end
end
end
end

shared_examples 'changing constraints in subsequent calls' do |method|
context 'when changing constraints in subsequent calls' do
let(:word_list) { %w(defenestrate as can jubilant orangutan hat) }

before(:each) do
expect(RandomWord).to receive(:load_word_list).and_return(word_list)
end

after(:each) do
RandomWord.instance_eval{ @nouns, @adjs = nil, nil }
end

it 'applies the new constraints' do
short_words = %w(as can hat)
long_words = %w(defenestrate jubilant orangutan)
3.times { expect(short_words).to include RandomWord.send(method, not_longer_than: 3).next }
3.times { expect(long_words).to include RandomWord.send(method, not_longer_than: 150).next }
expect { RandomWord.send(method).next }.to raise_exception StopIteration
end
end
end

describe RandomWord do
context '#nouns' do
include_examples 'allows constraints on word length', :nouns
include_examples 'changing constraints in subsequent calls', :nouns
end

context '#adjs' do
include_examples 'allows constraints on word length', :adjs
include_examples 'changing constraints in subsequent calls', :adjs
end
end

0 comments on commit 5a3f563

Please sign in to comment.