words_matrix 0.0.1

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.
@@ -0,0 +1,8 @@
1
+ module WordsMatrix
2
+ VERSION = "0.0.1"
3
+ end
4
+
5
+ require 'words_matrix/config'
6
+ require 'words_matrix/matrix'
7
+ require 'words_matrix/dictionary'
8
+ require 'words_matrix/service'
@@ -0,0 +1,7 @@
1
+ module WordsMatrix::Config
2
+ DEFAULTS = {
3
+ n: 10,
4
+ min_length: 3,
5
+ dict_path: "#{File.dirname(__FILE__)}/../../data/dict.txt"
6
+ }
7
+ end
@@ -0,0 +1,29 @@
1
+ class WordsMatrix::Dictionary
2
+ attr_reader :words
3
+
4
+ def initialize(min_length, max_length, dict_path)
5
+ @dict_path = dict_path
6
+ @words = read_content(min_length, max_length)
7
+ end
8
+
9
+ private
10
+ def read_content(min_length, max_length)
11
+ contents = File.read(@dict_path).split("\n")
12
+
13
+ result = {}
14
+
15
+ contents.each do |word_line|
16
+ word = word_line[/^\S+/].to_s.upcase
17
+
18
+ break if word.length > max_length
19
+ if word.length >= min_length
20
+ result[word[0]] ||= {}
21
+ result[word[0]][word] = word_line
22
+ end
23
+ end
24
+
25
+ result
26
+ rescue SystemCallError => e
27
+ raise IOError, "error while reading dictionary file: #{e.message}"
28
+ end
29
+ end
@@ -0,0 +1,82 @@
1
+ class WordsMatrix::Matrix
2
+ attr_reader :grid, :tokens
3
+
4
+ def initialize(n, min_length)
5
+ @n = n
6
+ @min_length = min_length
7
+ @grid = generate_grid
8
+ parse_to_tokens!
9
+ end
10
+
11
+ def to_s
12
+ grid.map do |line|
13
+ line.join(" ")
14
+ end.join("\n")
15
+ end
16
+
17
+ def words_from(x, y)
18
+ raise ArgumentError, "Incorrect range" unless (0..@n-1).include?(x) || (0..@n-1).include?(y)
19
+ [
20
+ horizontal_line((y...@n).to_a, x),
21
+ horizontal_line((0..y).to_a.reverse, x),
22
+ vertical_line((x...@n).to_a, y),
23
+ vertical_line((0..x).to_a.reverse, y),
24
+ diagonal((x...@n).to_a, y),
25
+ diagonal((0..x).to_a.reverse, y),
26
+ diagonal((x...@n).to_a, y, -1),
27
+ diagonal((0..x).to_a.reverse, y, -1)
28
+ ].flatten.compact
29
+ end
30
+
31
+ private
32
+ def generate_grid
33
+ vowels = %w[A E O U I]
34
+ consonants = ('B'..'Z').to_a - vowels
35
+
36
+ Array.new(@n) do
37
+ Array.new(@n) do
38
+ (rand(4).zero? ? consonants : vowels).sample
39
+ end
40
+ end
41
+ end
42
+
43
+ def parse_to_tokens!
44
+ @tokens = Hash.new([])
45
+
46
+ (0...@n).each do |x|
47
+ (0...@n).each do |y|
48
+ words = words_from(x, y)
49
+ @tokens[@grid[x][y]] += words if words.any?
50
+ end
51
+ end
52
+ end
53
+
54
+ def horizontal_line(y_range, x)
55
+ return nil if invalid_range?(y_range)
56
+
57
+ y_range.to_a.map do |yt|
58
+ @grid[x][yt]
59
+ end.join
60
+ end
61
+
62
+ def vertical_line(x_range, y)
63
+ return nil if invalid_range?(x_range)
64
+
65
+ x_range.to_a.map do |xt|
66
+ @grid[xt][y]
67
+ end.join
68
+ end
69
+
70
+ def diagonal(x_range, y, multiplier=1)
71
+ return nil if invalid_range?(x_range) || !(0...@n).include?(y + multiplier*@min_length)
72
+
73
+ x_range.to_a.map.with_index do |xt, i|
74
+ next unless (0...@n).include? y+i*multiplier
75
+ @grid[xt][y+i*multiplier]
76
+ end.join
77
+ end
78
+
79
+ def invalid_range?(range)
80
+ range.size < @min_length
81
+ end
82
+ end
@@ -0,0 +1,45 @@
1
+ class WordsMatrix::Service
2
+ attr_reader :matrix, :dictionary, :words
3
+
4
+ def initialize(config={})
5
+ config = WordsMatrix::Config::DEFAULTS.merge(config) { |key, default, local|
6
+ local || default
7
+ }
8
+
9
+ validate_options!(config)
10
+ @matrix = WordsMatrix::Matrix.new(config[:n].to_i, config[:min_length].to_i)
11
+ @dictionary = WordsMatrix::Dictionary.new(config[:min_length].to_i, config[:n].to_i, config[:dict_path])
12
+
13
+ @words = []
14
+ end
15
+
16
+ def find_words
17
+ @matrix.tokens.each_pair do |letter, tokens|
18
+ next unless @dictionary.words.has_key?(letter)
19
+ words = @dictionary.words[letter].keys
20
+
21
+ tokens.each do |token|
22
+ words_found = words.select { |word| token =~ /^#{word}.*/ }
23
+
24
+ @words += words_found.map { |word| @dictionary.words[letter][word] }
25
+ end
26
+ end
27
+ @words.uniq!
28
+ end
29
+
30
+ def to_s
31
+ words_found = @words.any? ? @words.join("\n") : "none :("
32
+ ["Matrix:",
33
+ matrix.to_s,
34
+ "",
35
+ "Words found:",
36
+ words_found].join("\n")
37
+ end
38
+
39
+ private
40
+ def validate_options!(config)
41
+ raise ArgumentError, "matrix size should be a positive integer" if config[:n].to_i < 1
42
+ raise ArgumentError, "min word length should be a positive integer and less than matrix size" unless (0..config[:n].to_i).include?(config[:min_length].to_i)
43
+ raise ArgumentError, "dictionary path is not a valid file path" unless File.readable?(config[:dict_path])
44
+ end
45
+ end
@@ -0,0 +1,13 @@
1
+
2
+ AA rough, cindery lava [n -S]
3
+ JAZZ to {enliven=v} [v -ED, -ING, -ES]
4
+ ALBUM a book for preserving photographs or stamps [n -S]
5
+ PHYLLOME a leaf of a plant [n -S]
6
+ PHYSICAL a medical examination of the body [n -S]
7
+ PHYSIQUE the form or structure of the body [n -S]
8
+ PHYTANES <phytane=n> [n]
9
+ PHYTONIC <phyton=n> [adj]
10
+ WITHDRAWNNESSES <withdrawnness=n> [n]
11
+ WOEBEGONENESSES <woebegoneness=n> [n]
12
+ WONDERFULNESSES <wonderfulness=n> [n]
13
+ WORRISOMENESSES <worrisomeness=n> [n]
@@ -0,0 +1,7 @@
1
+ require "words_matrix"
2
+ require "rspec/its"
3
+
4
+ RSpec.configure do |config|
5
+ config.color = true
6
+ config.formatter = :documentation
7
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ describe WordsMatrix::Dictionary do
4
+ let(:dict_path) {'spec/fixtures/test_dict.txt'}
5
+ let(:subject) {WordsMatrix::Dictionary.new(6, 9, dict_path)}
6
+
7
+ describe "#initialize" do
8
+ its(:words) { should_not be_nil }
9
+
10
+ it "should save only words of required length" do
11
+ expect(subject.words.keys).to eq ["P"]
12
+ end
13
+
14
+ context "file unreadable" do
15
+ it "should raise an error" do
16
+ expect{ WordsMatrix::Dictionary.new(15, 15, "inexisting path")}.to raise_error(IOError, /error while reading dictionary file: No such file or directory/)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,73 @@
1
+ require 'spec_helper'
2
+
3
+ describe WordsMatrix::Matrix do
4
+ let(:matrix) {WordsMatrix::Matrix.new(6, 4)}
5
+ let(:sample_grid) {
6
+ [
7
+ %w[A B C D E X],
8
+ %w[F G H I J X],
9
+ %w[K L M N O X],
10
+ %w[P Q R S T X],
11
+ %w[U V W X Y X],
12
+ %w[A B C D E X]
13
+ ]
14
+ }
15
+
16
+ describe "#initialize" do
17
+ it "should initialize grid" do
18
+ expect(matrix.grid).not_to be_nil
19
+ end
20
+
21
+ it "should parse grid to tokens" do
22
+ expect(matrix.tokens).not_to be_empty
23
+ end
24
+ end
25
+
26
+ describe "#grid" do
27
+ subject(:grid) { matrix.grid }
28
+
29
+ its(:size) { should eq 6 }
30
+ it "should have approximately 3 times more vowels" do
31
+ consonants_count = subject.flatten.count {|letter| !%w[A E I O U].include?(letter)}
32
+
33
+ expect(consonants_count).to be_within(10).of(subject.size**2 / 4)
34
+ end
35
+ end
36
+
37
+ describe "#tokens" do
38
+ before { allow_any_instance_of(WordsMatrix::Matrix).to receive(:generate_grid).and_return([["A", "B", "C"],["D", "E", "F"], ["G", "H", "I"]]) }
39
+ let(:matrix) { WordsMatrix::Matrix.new(3, 3) }
40
+
41
+ it "should exclude too short tokens" do
42
+ expect(matrix.tokens.keys).not_to include("E")
43
+ end
44
+ end
45
+
46
+ describe "#words_from" do
47
+ it "should skip all combinations shorter than min_length" do
48
+ expect(matrix.words_from(1, 0).any?{ |el| el.size < 4 }).to be false
49
+ end
50
+
51
+ context "letter check" do
52
+ before do
53
+ allow_any_instance_of(WordsMatrix::Matrix).to receive(:generate_grid).and_return(sample_grid)
54
+ end
55
+
56
+ it "should fetch appropriate horizontal letter combinations" do
57
+ expect(matrix.words_from(1,4)).to include("JIHGF")
58
+ end
59
+
60
+ it "should fetch appropriate vertical letter combinations" do
61
+ expect(matrix.words_from(1,4)).to include("JOTYE")
62
+ end
63
+
64
+ it "should fetch appropriate diagonal letter combinations" do
65
+ expect(matrix.words_from(1,4)).to include("JNRVA")
66
+ end
67
+
68
+ it "should not contain any additional letter combinations" do
69
+ expect(matrix.words_from(1,4).size).to eq 3
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,93 @@
1
+ require 'spec_helper'
2
+
3
+ describe WordsMatrix::Service do
4
+ describe "#initialize" do
5
+ context "no local options given" do
6
+ let(:service) { WordsMatrix::Service.new }
7
+
8
+ it "should use default options" do
9
+ expect(service.matrix.grid.size).to eq WordsMatrix::Config::DEFAULTS[:n]
10
+ expect(service.matrix.instance_variable_get(:"@min_length")).to eq WordsMatrix::Config::DEFAULTS[:min_length]
11
+ expect(service.dictionary.instance_variable_get(:"@dict_path")).to eq WordsMatrix::Config::DEFAULTS[:dict_path]
12
+ end
13
+ end
14
+
15
+ context "local options given" do
16
+ let(:local_options) { {n: 5, dict_path: 'spec/fixtures/test_dict.txt', min_length: 4} }
17
+ it "should use local options with higher priority" do
18
+ service = WordsMatrix::Service.new(local_options)
19
+
20
+ expect(service.matrix.grid.size).to eq local_options[:n]
21
+ expect(service.matrix.instance_variable_get(:"@min_length")).to eq local_options[:min_length]
22
+ expect(service.dictionary.instance_variable_get(:"@dict_path")).to eq local_options[:dict_path]
23
+ end
24
+
25
+ context "invalid options" do
26
+ it "should validate the matrix size" do
27
+ local_options[:n] = double("something weird", to_i: 0)
28
+ expect{ WordsMatrix::Service.new(local_options) }.to raise_error(ArgumentError, "matrix size should be a positive integer")
29
+ end
30
+
31
+ it "should validate the min word size" do
32
+ local_options[:min_length] = 35
33
+ expect{ WordsMatrix::Service.new(local_options) }.to raise_error(ArgumentError, "min word length should be a positive integer and less than matrix size")
34
+ end
35
+
36
+ it "should check if dictionary file exists" do
37
+ local_options[:dict_path] = 'no file here'
38
+ expect{ WordsMatrix::Service.new(local_options) }.to raise_error(ArgumentError, "dictionary path is not a valid file path")
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ describe "#find_words" do
45
+ let(:sample_grid) {
46
+ [
47
+ %w[A B C D E X],
48
+ %w[F B L I M P],
49
+ %w[K L M N O S],
50
+ %w[P Q R S T X],
51
+ %w[U V W A Y X],
52
+ %w[A B C D E X]
53
+ ]
54
+ }
55
+ let(:service) { WordsMatrix::Service.new(n: 6, min_length: 4) }
56
+
57
+ before { allow_any_instance_of(WordsMatrix::Matrix).to receive(:generate_grid).and_return(sample_grid) }
58
+
59
+ it "should find all words not shorter than 4 chars" do
60
+ expected_result = [
61
+ "BLIMP a nonrigid aircraft [n -S] : BLIMPISH [adj]",
62
+ "DINS <din=v> [v]",
63
+ "EARL a British nobleman [n -S]",
64
+ "LIMP lacking rigidity [adj LIMPER, LIMPEST] / to walk lamely [v -ED, -ING, -S]",
65
+ "TOME a large book [n -S]"]
66
+ service.find_words
67
+
68
+ expect(service.words).to eq(expected_result)
69
+ end
70
+ end
71
+
72
+ describe "#to_s" do
73
+ let(:service) { WordsMatrix::Service.new(n: 6, min_length: 4, dict_path: 'spec/fixtures/test_dict.txt') }
74
+
75
+ before { service.find_words }
76
+
77
+ it "should print the matrix content" do
78
+ expect( service.to_s ).to include(service.matrix.to_s)
79
+ end
80
+
81
+ it "should print the words found" do
82
+ expect(service.to_s).to include(service.words.join("\n"))
83
+ end
84
+
85
+ context "no words found" do
86
+ it "should print a related message" do
87
+ allow(service).to receive(:words).and_return([])
88
+
89
+ expect(service.to_s).to include("Words found:\nnone :(")
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,36 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'words_matrix'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "words_matrix"
8
+ spec.version = WordsMatrix::VERSION
9
+ spec.authors = ["Alona Mekhovova"]
10
+ spec.email = ["alona.tarasova@gmail.com"]
11
+ spec.summary = "Words search in a random letter matrix"
12
+ spec.description = %q{
13
+ A command line app able to generate a random word search grid n x n (n: number of rows/number of columns) and find all the words inside it.
14
+
15
+ - The grid letters must be uppercase.
16
+ - The valid words are defined inside the file dict.txt
17
+ - The minimum size of a valid word is 3 characters.
18
+ - The program must detect all possible directions: horizontal, vertical and diagonal.
19
+
20
+ (The words can be on reverse order)
21
+ }
22
+ spec.homepage = "https://github.com/alony/words_matrix"
23
+ spec.license = "MIT"
24
+
25
+ spec.files = `git ls-files -z`.split("\x0")
26
+ spec.executables = ["words_matrix"]
27
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
28
+ spec.require_paths = ["lib", "data"]
29
+
30
+ spec.add_runtime_dependency "thor"
31
+
32
+ spec.add_development_dependency "bundler", "~> 1.6"
33
+ spec.add_development_dependency "rake"
34
+ spec.add_development_dependency "rspec"
35
+ spec.add_development_dependency "rspec-its"
36
+ end
metadata ADDED
@@ -0,0 +1,144 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: words_matrix
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Alona Mekhovova
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-03-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: thor
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.6'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.6'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec-its
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: "\n A command line app able to generate a random word search grid
84
+ n x n (n: number of rows/number of columns) and find all the words inside it.\n\n
85
+ \ - The grid letters must be uppercase.\n - The valid words are defined inside
86
+ the file dict.txt\n - The minimum size of a valid word is 3 characters.\n -
87
+ The program must detect all possible directions: horizontal, vertical and diagonal.\n\n
88
+ \ (The words can be on reverse order)\n "
89
+ email:
90
+ - alona.tarasova@gmail.com
91
+ executables:
92
+ - words_matrix
93
+ extensions: []
94
+ extra_rdoc_files: []
95
+ files:
96
+ - ".gitignore"
97
+ - Gemfile
98
+ - LICENSE.txt
99
+ - README.md
100
+ - Rakefile
101
+ - bin/words_matrix
102
+ - data/dict.txt
103
+ - lib/words_matrix.rb
104
+ - lib/words_matrix/config.rb
105
+ - lib/words_matrix/dictionary.rb
106
+ - lib/words_matrix/matrix.rb
107
+ - lib/words_matrix/service.rb
108
+ - spec/fixtures/test_dict.txt
109
+ - spec/spec_helper.rb
110
+ - spec/words_matrix/dictionary_spec.rb
111
+ - spec/words_matrix/matrix_spec.rb
112
+ - spec/words_matrix/service_spec.rb
113
+ - words_matrix.gemspec
114
+ homepage: https://github.com/alony/words_matrix
115
+ licenses:
116
+ - MIT
117
+ metadata: {}
118
+ post_install_message:
119
+ rdoc_options: []
120
+ require_paths:
121
+ - lib
122
+ - data
123
+ required_ruby_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ version: '0'
128
+ required_rubygems_version: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ requirements: []
134
+ rubyforge_project:
135
+ rubygems_version: 2.2.2
136
+ signing_key:
137
+ specification_version: 4
138
+ summary: Words search in a random letter matrix
139
+ test_files:
140
+ - spec/fixtures/test_dict.txt
141
+ - spec/spec_helper.rb
142
+ - spec/words_matrix/dictionary_spec.rb
143
+ - spec/words_matrix/matrix_spec.rb
144
+ - spec/words_matrix/service_spec.rb