anagram_solver 0.0.2

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1b09baa79e607ae059995e5c52012c5dfe875bbe
4
+ data.tar.gz: 52002f88f8736f79bf36e1f027739575b7d84bfe
5
+ SHA512:
6
+ metadata.gz: 6ec1715650407f74c4c6efb772b3b05a698c78d6b8c9d2fa5d0f8c9ab5c450dd1ba87e8bc4e942215a53020da1e7205c7fb2a7f5dabc24c3df89e2196a4afd62
7
+ data.tar.gz: 0cd752f03c338f83ae92df5cd5203239a435b2aca2a13908cabdeac493fa320e77c5fa5de2adfd91f99128fa21a31850872b4df021e8a33cc33720e7071072c2
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2013 YOURNAME
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,19 @@
1
+ = AnagramSolver
2
+
3
+ A Ruby gem for that plugs into Rails.
4
+ The gem allows for the upload of a text file which will be
5
+ the dictionary file. The dictionary file consists of a
6
+ list of words. Each word is on separate line.
7
+ The gem provides an input where a user can input a word of their
8
+ choice. On hitting enter (or clicking a button) the webapp should find all
9
+ anagrams, if any exist, of the word and display them. If no anagrams are
10
+ found it should display “No anagrams found for <word>”.
11
+
12
+ = TODO
13
+
14
+ Create an Instrumentation to save searched anagrams.
15
+ Save tempfile into root_path/public/uploads, and keep
16
+ track of them, so that it can be displayed as a list
17
+ and a user can select that particular word list to use it.
18
+
19
+ This project rocks and uses MIT-LICENSE.
data/Rakefile ADDED
@@ -0,0 +1,32 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'AnagramSolver'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+ Bundler::GemHelper.install_tasks
21
+
22
+ require 'rake/testtask'
23
+
24
+ Rake::TestTask.new(:test) do |t|
25
+ t.libs << 'lib'
26
+ t.libs << 'spec'
27
+ t.pattern = 'spec/**/*_spec.rb'
28
+ t.verbose = false
29
+ end
30
+
31
+
32
+ task default: :test
@@ -0,0 +1,17 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // compiled file.
9
+ //
10
+ // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
11
+ // about supported directives.
12
+ //
13
+
14
+ //= require jquery
15
+ //= require jquery_ujs
16
+ //= require turbolinks
17
+ //= require_tree .
@@ -0,0 +1,65 @@
1
+ $(function(){
2
+
3
+ $("#search_area").hide();
4
+ $('#word').attr('autocomplete','off');
5
+
6
+ $.extend({
7
+ ajaxPostRequest: function(url, formData, callBack){
8
+ $.ajax({
9
+ url: url,
10
+ type: "POST",
11
+ data: formData,
12
+ async: false,
13
+ success: callBack,
14
+ contentType: false,
15
+ cache: false,
16
+ processData: false
17
+ });
18
+ },
19
+ ajaxGetRequest: function(url, jsonData, callBack){
20
+ $.ajax({
21
+ url: url,
22
+ type: "POST",
23
+ data: JSON.stringify(jsonData),
24
+ async: false,
25
+ success: callBack,
26
+ contentType: "application/json; charset=utf-8",
27
+ traditional: true,
28
+ cache: false,
29
+ processData: false
30
+ });
31
+ }
32
+
33
+ });
34
+
35
+ $("body").on("submit","form#new_anagram", function(e){
36
+ var formData = new FormData($(this)[0]);
37
+ var url = $(this).attr("action");
38
+
39
+ $.ajaxPostRequest(url, formData, function(data){
40
+ $(".loaded_in").html(data + "ms");
41
+ $("#search_area").fadeIn();
42
+ $("#word").focus();
43
+ });
44
+
45
+ $("#results").html("");
46
+ e.preventDefault();
47
+ });
48
+
49
+
50
+ $("body").on("submit","form#search", function(e){
51
+
52
+ var url = "/search";
53
+ var word = { word: $("#word").val() }
54
+ $("#word").val("");
55
+
56
+ $.ajaxGetRequest(url, word, function(d){
57
+ var result = "<div class='results'><span>"+d.started_at+"</span>"+
58
+ "<span>"+d.notice+"</span>"+
59
+ "<span>>"+d.anagrams+"</span></div>"
60
+ $("#results").prepend(result);
61
+ });
62
+ e.preventDefault();
63
+ });
64
+
65
+ });
@@ -0,0 +1,13 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the top of the
9
+ * compiled file, but it's generally better to create a new file per style scope.
10
+ *
11
+ *= require_self
12
+ *= require_tree .
13
+ */
@@ -0,0 +1,38 @@
1
+ body{
2
+ margin: 0;
3
+ }
4
+
5
+ .results {
6
+ width: 500px;
7
+ margin: 5px;
8
+ display: table;
9
+ background: black;
10
+ margin-bottom: 10px;
11
+ border: white dotted 2px;
12
+ }
13
+
14
+ span {
15
+ float: left;
16
+ width: 100%;
17
+ margin-bottom: 2px;
18
+ background: white;
19
+ }
20
+
21
+ #word {
22
+ border: solid 1px black;
23
+ outline: 0;
24
+ width: 20%;
25
+ margin: 7px;
26
+ padding-left: 5px;
27
+ }
28
+
29
+ #dict {
30
+ float: left;
31
+ }
32
+
33
+ .loaded_in {
34
+ float: left;
35
+ width: 100px;
36
+ height: 20px;
37
+ line-height: 20px;
38
+ }
@@ -0,0 +1,16 @@
1
+ module AnagramSolver
2
+ class AnagramSolverController < ApplicationController
3
+
4
+ respond_to :html, :json
5
+
6
+ def index
7
+ end
8
+
9
+ def create
10
+ respond_to do |f|
11
+ f.json { render json: Time.now.strftime("%2N").to_json, status: 200 }
12
+ end
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,4 @@
1
+ module AnagramSolver
2
+ class ApplicationController < ActionController::Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module AnagramSolver
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,9 @@
1
+ module AnagramSolver
2
+ class Anagram
3
+ include Mongoid::Document
4
+
5
+ field :searched_at, type: Date
6
+ field :finished_at, type: Date
7
+ field :dictionary_name, type: String
8
+ end
9
+ end
@@ -0,0 +1,16 @@
1
+ <%= form_tag "/anagram_solver", id: :new_anagram, method: :post, multipart: true do %>
2
+ <%= file_field_tag :dict %><span class="loaded_in"></span>
3
+ <div class="actions">
4
+ <%= submit_tag "Load new dictionary" %>
5
+ </div>
6
+ <% end %>
7
+
8
+ <div id="search_area">
9
+ <%= form_tag "/search", method: :get, id: :search do %>
10
+ <%= text_field_tag :word %>
11
+ <%= submit_tag :find %>
12
+ <% end %>
13
+ </div>
14
+
15
+ <div id="results">
16
+ </div>
@@ -0,0 +1 @@
1
+ <%= render("anagram_solver/anagram_solver/form") %>
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>AnagramSolver</title>
5
+ <%= stylesheet_link_tag "anagram_solver/application", media: "all" %>
6
+ <%= javascript_include_tag "anagram_solver/application" %>
7
+ <%= csrf_meta_tags %>
8
+ </head>
9
+ <body>
10
+ <%= yield %>
11
+ </body>
12
+ </html>
data/config/routes.rb ADDED
@@ -0,0 +1,5 @@
1
+ AnagramSolver::Engine.routes.draw do
2
+ root "anagram_solver#index"
3
+
4
+ resource :anagram_solver, controller: :anagram_solver
5
+ end
@@ -0,0 +1,4 @@
1
+ require "anagram_solver/loader"
2
+
3
+ module AnagramSolver
4
+ end
@@ -0,0 +1,96 @@
1
+ module AnagramSolver
2
+ class AsyncConsumer
3
+
4
+ ##
5
+ # TODO: remove this
6
+ #
7
+ class << self
8
+ attr_reader :thread
9
+
10
+ def bg_process(to_be_processed, &block)
11
+ @thread = Thread.new(to_be_processed) { |p|
12
+ block.call(p)
13
+ }
14
+ end
15
+
16
+ def wait
17
+ thread.join
18
+ end
19
+ end
20
+
21
+ attr_reader :queue, :thread
22
+ attr_reader :block, :mutex
23
+
24
+ def initialize(queue=Queue.new, &block)
25
+ @queue = queue
26
+ @thread = Thread.new { consume }
27
+ @mutex = Mutex.new
28
+ @block = block
29
+ @lock = true
30
+ end
31
+
32
+ ##
33
+ # Consumes data from a queue.
34
+ # NOTE: If I was to use a while loop
35
+ # here, I'd have to find a way of stoping
36
+ # the loop. A common way would be pushing
37
+ # nil to queue and then it would exit.
38
+ # However I'd have to be passing nil ATFT.
39
+ # If I didn't pass nil it would raise:
40
+ # Failure/Error:
41
+ # (anonymous error class);
42
+ # No live threads left. Deadlock?
43
+ # As there's nothing in queue.
44
+ #
45
+ # In Ruby you have a lot of different ways
46
+ # to make the same thing.
47
+ #
48
+ # This implentation simply asks queue what it's
49
+ # size is and then loops through it (i.e times)
50
+ # and then I call queue.deq which stands for
51
+ # deqeuing.
52
+ #
53
+ def consume
54
+ (queue.size).times do
55
+ mutex.synchronize {
56
+ block.call(queue.deq)
57
+ @lock = true
58
+ }
59
+ end
60
+ end
61
+
62
+ ##
63
+ # Pushes args in to a queue.
64
+ #
65
+ def push(*args)
66
+ queue.push(*args)
67
+ mutex.synchronize { @lock = false }
68
+ end
69
+
70
+ ##
71
+ # Waits for a thread to finish
72
+ # returns nil if limit seconds have past.
73
+ # or returns thread itself if thread was
74
+ # finished whithing limit seconds.
75
+ #
76
+ # If you call it without a limit second,
77
+ # it will use 0 as default, therefore it
78
+ # will not wait thread to finish, and it
79
+ # will return nil.
80
+ # (i.e Stops main thread ( current one )
81
+ # and waits until the other thread is finished,
82
+ # then passes execution to main thread again. )
83
+ #
84
+ def wait_thread_to_finish!(ttw=0)
85
+ thread.join(ttw)
86
+ end
87
+
88
+ def finished?
89
+ lock
90
+ end
91
+
92
+ private
93
+ attr_reader :lock
94
+
95
+ end
96
+ end
@@ -0,0 +1,10 @@
1
+ module AnagramSolver
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace AnagramSolver
4
+
5
+ initializer "anagram_solver.add_middleware" do |app|
6
+ app.middleware.insert_after ::Rack::Runtime, AnagramSolver::Middleware
7
+ end
8
+
9
+ end
10
+ end
@@ -0,0 +1,97 @@
1
+ ##
2
+ # Responsability
3
+ # Read lines from a file
4
+ # Create a new file, absolute path should be passed in.
5
+ # Check file exists?
6
+ # Manage a File object
7
+ #
8
+ # TODO:
9
+ # Make it compartible with File object
10
+ # As it is only compartible with Tempfile
11
+ #
12
+ module AnagramSolver
13
+ class FileManager
14
+ attr_reader :current_file, :root_path
15
+
16
+ ##
17
+ # current_file is a temp_file to read
18
+ # content from it and be destroyed later on when
19
+ # everything gets garbaged.
20
+ #
21
+ # root_path is the path to save new files.
22
+ #
23
+ # TODO:
24
+ # Make it work with instances of File,
25
+ # as it only works with Tempfile.
26
+ #
27
+ def initialize(current_file)
28
+ @current_file = current_file
29
+ @root_path = AnagramSolver::RootPath
30
+ end
31
+
32
+ def read_lines
33
+ open_safely! { |f| f.rewind; f.read }
34
+ end
35
+
36
+ ##
37
+ # Returns file's root path
38
+ #
39
+ def current_file_path
40
+ current_file.to_path
41
+ end
42
+
43
+ ##
44
+ # Creates a new file, saves it based on
45
+ # root_path provided.
46
+ # NOTE: Ruby File#open when called with a block closes
47
+ # file stream for us.
48
+ # This is not compartible with Ruby 1.8.7.
49
+ #
50
+ def new_file(new_name=false, body)
51
+ f_name = new_name || file_name_with_path
52
+ File.open(f_name, "w") { |line|
53
+ line.write(body)
54
+ line
55
+ }
56
+ end
57
+
58
+ ##
59
+ # Checks file existence using current_file_path.
60
+ # However if absolute path is passed in, uses this
61
+ # over current_file_path.
62
+ #
63
+ def file_exist?(_file_name_=false)
64
+ File.exists?(_file_name_ || current_file_path)
65
+ end
66
+
67
+
68
+ private
69
+
70
+ ##
71
+ # Keeps a tmp_file opened for the
72
+ # duration of a block call.
73
+ #
74
+ def open_safely!
75
+ yield current_file.open
76
+ ensure
77
+ current_file.close
78
+ end
79
+
80
+ ##
81
+ # Creates a root_path with file name at the end.
82
+ #
83
+ def file_name_with_path
84
+ root_path.dup + file_name
85
+ end
86
+
87
+ ##
88
+ # Returns its random file name that TempFile
89
+ # assings when new object is created.
90
+ # test_file_name434302324843493
91
+ #
92
+ def file_name
93
+ "/" + current_file.path.split('/').pop
94
+ end
95
+
96
+ end
97
+ end
@@ -0,0 +1,95 @@
1
+ require 'json'
2
+ ##
3
+ # TODO:
4
+ # This object is doing too much
5
+ # Perhaps we need to break it into
6
+ # smaller objects.
7
+ #
8
+ module AnagramSolver
9
+ class Finder
10
+
11
+ class << self
12
+
13
+ include ActionView::Helpers::TextHelper
14
+
15
+ attr_accessor :finished_at, :started_at
16
+ attr_accessor :time, :word, :anagrams
17
+ @finished_at = ""
18
+ @started_at = ""
19
+ @time = ""
20
+ @word = ""
21
+ @anagrams = ""
22
+
23
+ ##
24
+ # Finds anagrams for a given key ( i.e word )
25
+ # and a list, which should be already precomputed.
26
+ # It returns a JSON object, see anagrams_found!.
27
+ #
28
+ def find_for(key, list)
29
+ timer { look_up(list, key) }
30
+ anagrams_found!
31
+ end
32
+
33
+ ##
34
+ # Look for anagrams
35
+ #
36
+ def look_up(list, _word_)
37
+ @word = _word_
38
+ key = list.sort!(word)
39
+ @anagrams = list.precomputed_list[key]
40
+ end
41
+
42
+ private
43
+
44
+ ##
45
+ # Generates a JSON object
46
+ # with results of finished_at, started_at,
47
+ # milliseconds, and anagrams found.
48
+ def anagrams_found!
49
+ {
50
+ notice: "#{pluralize(size, "anagram")} found for '#{word}' in #{time}",
51
+ anagrams: join_anagrams_found!,
52
+ finished_at: normalize_time!(finished_at),
53
+ started_at: normalize_time!(started_at)
54
+ }.to_json
55
+ end
56
+
57
+ def size
58
+ anagrams.size
59
+ end
60
+
61
+ def join_anagrams_found!
62
+ anagrams.join(", ")
63
+ end
64
+
65
+ ##
66
+ # Simple timer to get started/finished time/date
67
+ # and to generate milliseconds
68
+ #
69
+ def timer
70
+ self.started_at = Time.now
71
+ result = yield
72
+ self.finished_at = Time.now
73
+ result
74
+ self.time = (self.finished_at - self.started_at) * 1000
75
+ end
76
+
77
+ ##
78
+ # Normalizes time as 10/25/2013 at 04:15 PM
79
+ #
80
+ def normalize_time!(_time_)
81
+ _time_.strftime("%m/%d/%Y at %I:%M %p")
82
+ end
83
+
84
+ ##
85
+ # Get milliseconds.
86
+ #
87
+ def milliseconds
88
+ finished_at.strftime("%N").to_i - started_at.strftime("%N").to_i
89
+ end
90
+
91
+ end
92
+
93
+
94
+ end
95
+ end
@@ -0,0 +1,7 @@
1
+ require 'anagram_solver/routing'
2
+ require 'anagram_solver/finder'
3
+ require 'anagram_solver/permutator'
4
+ require 'anagram_solver/file_manager'
5
+ require 'anagram_solver/async_consumer'
6
+ require 'anagram_solver/middleware'
7
+ require 'anagram_solver/engine'
@@ -0,0 +1,110 @@
1
+ require 'rack/request'
2
+
3
+ ##
4
+ # TODO:
5
+ # Break this down, as it is doing
6
+ # alot.
7
+ module AnagramSolver
8
+ class Middleware
9
+ attr_reader :app, :precomputed
10
+
11
+ def initialize(app)
12
+ @app = app
13
+ @precomputed = ""
14
+ @env = ""
15
+ end
16
+
17
+ ##
18
+ # Assigns env to @env to be used later on.
19
+ # If PATH_INFO matches search, it then tries
20
+ # to find anagrams. In not it goes down the stack.
21
+ #
22
+ def call(env)
23
+ @env = env
24
+ uploaded_tempfile?
25
+ search? || app.call(env)
26
+ end
27
+
28
+ ##
29
+ # Returns a response which is a JSON response
30
+ # if search_path?
31
+ #
32
+ # When trying to find an anagram AnagramSolver::Finder
33
+ # returns either anagrams found for a particular word or
34
+ # it returns a notice (i.e a message ) saying that No anagrams
35
+ # was found for that particular word.
36
+ #
37
+ def search?
38
+ return response if search_path?
39
+ end
40
+
41
+ private
42
+
43
+ attr_reader :env
44
+
45
+ ##
46
+ # True is PATH_INFO matches search,
47
+ # false otherwise.
48
+ #
49
+ def search_path?
50
+ env["PATH_INFO"] == "/search"
51
+ end
52
+
53
+ ##
54
+ # Sets a 200 HTTP response code,
55
+ # Sets Content-Type to JSON, and
56
+ # returns anagrams_result.
57
+ #
58
+ def response
59
+ response = anagrams_result
60
+ [200, { "Content-Type" => "application/json" }, [response]]
61
+ end
62
+
63
+
64
+ ##
65
+ # Returns results of anagrams.
66
+ # If none is found it returns a notice
67
+ # saying that no anagram was found.
68
+ #
69
+ def anagrams_result
70
+ Finder.find_for(word, @precomputed)
71
+ end
72
+
73
+
74
+ ##
75
+ # Gets the json request sent to server
76
+ # then parse it and get the raw word.
77
+ #
78
+ def word
79
+ word = JSON.parse(env["rack.input"].string)
80
+ word["word"]
81
+ end
82
+
83
+
84
+ ##
85
+ # Rack::Request rocks!!
86
+ #
87
+ def params
88
+ Rack::Request.new(env)
89
+ end
90
+
91
+ ##
92
+ # Gets the tempfile uploaded.
93
+ # Rack::Request parses and extract params
94
+ # from StringIO for us.
95
+ #
96
+ def tempfile
97
+ params["dict"][:tempfile]
98
+ end
99
+
100
+ def uploaded_tempfile?
101
+ precompute! if env["PATH_INFO"] == "/anagram_solver"
102
+ end
103
+
104
+
105
+ def precompute!
106
+ @precomputed = Permutator.new(tempfile)
107
+ end
108
+
109
+ end
110
+ end
@@ -0,0 +1,130 @@
1
+ # encoding: utf-8
2
+ ##
3
+ # Responsability
4
+ # Create a hash of precomputed anagrams
5
+ # to facilitate when searching for one.
6
+ # Rather than doing all the possible combinations
7
+ # at run time.
8
+ #
9
+ # Here's a benchmark with, unpack and chars
10
+ #
11
+ # require 'benchmark'
12
+ #
13
+ # Benchmark.bm do |bm|
14
+ # bm.report("Unpack") do
15
+ # 1000_000.times do
16
+ # "aabcjksjdsodoiio32j3k2jkjdksjkdsj,, ,fd,f,d, f,d,,---".gsub(/[,\t\r\n\f]*/, "").unpack("c*").sort.pack("c*")
17
+ # end
18
+ # end
19
+ #
20
+ # bm.report("Chars") do
21
+ # 1000_000.times do
22
+ # "aabcjksjdsodoiio32j3k2jkjdksjkdsj,, ,fd,f,d, f,d,,---".gsub(/[,\t\r\n\f]*/, "").chars.sort.join
23
+ # end
24
+ # end
25
+ # end
26
+ #
27
+ # user system total real
28
+ # Unpack 37.620000 0.080000 37.700000 ( 37.781407)
29
+ # Chars 65.020000 0.200000 65.220000 ( 65.300449)
30
+ #
31
+
32
+ module AnagramSolver
33
+ require 'anagram_solver/async_consumer'
34
+ class Permutator
35
+
36
+ attr_reader :word_list, :precomputed_list
37
+ attr_reader :precomputed_list_old, :bg_process
38
+
39
+ ##
40
+ # Initializes word_list instance variable, precomputed_list
41
+ # assigning it a hash whose default values is an empty array.
42
+ #
43
+ # Precomputes word_list in underneath the hood. (i.e A in-process
44
+ # thread that AsyncConsumer offers. )
45
+ #
46
+ def initialize(word_list)
47
+ @word_list = word_list
48
+ @precomputed_list = Hash.new([])
49
+ @bg_process = AsyncConsumer.bg_process(self) { |s| s.precompute }
50
+ @precomputed_list_old = Hash.new([])
51
+ end
52
+
53
+ ##
54
+ # Used for benchmarking
55
+ #
56
+ def precompute_old
57
+ warn "This method should not be used, please use precompute instead."
58
+ word_list.rewind.each do |line|
59
+ word = line.chomp
60
+ precomputed_list_old[word.split('').sort.join.freeze] += [word]
61
+ end
62
+ end
63
+
64
+ ##
65
+ # Generates a hash whose keys are sorted
66
+ # alphabetically and values (i.e rhd ) are words
67
+ # in its normal order.
68
+ #
69
+ # Given a list pots, stop, opts
70
+ # When I precompute it
71
+ # Then I should have a hash like:
72
+ # { "opst" => [ "pots", "stop", "opts" ] }
73
+ #
74
+ # NOTE:
75
+ # precompute only gets the first word it 'slices' off
76
+ # a line. Therefore if a line was:
77
+ # "pots, opts"
78
+ # It would get the first word, which is +pots+.
79
+ #
80
+ # However if it was:
81
+ # ", Deviation's, pots";
82
+ # It would slice Deviation's
83
+ #
84
+ # It is also slices words with accents, such as:
85
+ # émigré's
86
+ # Ångström
87
+ #
88
+ # If you're on UNIX-like there might exist a dict
89
+ # word in see /usr/share/dict/words
90
+ #
91
+ # Want to learn more about Regexp?
92
+ # Fear not:
93
+ # open-std.org/jtc1/sc22/wg21/docs/papers/2003/n1500.html
94
+ #
95
+ def precompute
96
+ word_list.each do |line|
97
+ # word = line.chomp.slice(/\b['\wÀ-ÖÙ-Üà-öù-ü]+/i)
98
+ word = line.chomp
99
+ precomputed_list[sort!(word)] += [word]
100
+ end
101
+ end
102
+
103
+ ##
104
+ # Orders any given word in alphabetical order by
105
+ # unpacking them (i.e spliting ) sort and packing
106
+ # them again ( i.e joining ).
107
+ # This facilitates when trying to find combinations
108
+ # for words, as we only have to sort once
109
+ # and return results by calling a hash with a sorted key.
110
+ #
111
+ # ruby-doc.org/core-2.0.0/Array.html#method-i-pack
112
+ # See: en.wikipedia.org/wiki/UTF-8
113
+ # Unpacks a string using:
114
+ # Integer UTF-8 character, as it can represent
115
+ # every character in the Unicode char set.
116
+ # Such as émigré's
117
+ # * means all remaining arrays elements will be
118
+ # converted.
119
+ #
120
+ # Freezes string, otherwise Ruby would generate
121
+ # another String object instead of keeping
122
+ # the one already supplied. So that we would have
123
+ # two instances of the same thing in memory.
124
+ #
125
+ def sort!(word)
126
+ word.unpack("U*").sort.pack("U*").freeze
127
+ end
128
+
129
+ end
130
+ end
@@ -0,0 +1,18 @@
1
+ ##
2
+ # Responsability
3
+ # Add anagram_solver as root_path
4
+ #
5
+ # TODO:
6
+ # Allow anagram_solver to be mounted
7
+ # at a different path.
8
+ module AnagramSolver
9
+ module Routing
10
+ extend ActiveSupport::Concern
11
+ def anagram_to_root_path!
12
+ mount(AnagramSolver::Engine, at: "/")
13
+ end
14
+
15
+ end
16
+ end
17
+
18
+ ActionDispatch::Routing::Mapper.send(:include, AnagramSolver::Routing)
@@ -0,0 +1,3 @@
1
+ module AnagramSolver
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :anagram_solver do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,140 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: anagram_solver
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Antonio C Nalesso Moreira
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-10-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: 4.0.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: 4.0.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: bson_ext
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: mongoid
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-rails
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: Reads a dictionary file and allows a user to find anagrams.
84
+ email:
85
+ - acnalesso@yahoo.co.uk
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - app/views/anagram_solver/anagram_solver/index.html.erb
91
+ - app/views/anagram_solver/anagram_solver/_form.html.erb
92
+ - app/views/layouts/anagram_solver/application.html.erb
93
+ - app/helpers/anagram_solver/application_helper.rb
94
+ - app/assets/javascripts/anagram_solver/index.js
95
+ - app/assets/javascripts/anagram_solver/application.js
96
+ - app/assets/stylesheets/anagram_solver/index.css
97
+ - app/assets/stylesheets/anagram_solver/application.css
98
+ - app/controllers/anagram_solver/application_controller.rb
99
+ - app/controllers/anagram_solver/anagram_solver_controller.rb
100
+ - app/models/anagram_solver/anagram.rb
101
+ - config/routes.rb
102
+ - lib/tasks/anagram_solver_tasks.rake
103
+ - lib/anagram_solver/routing.rb
104
+ - lib/anagram_solver/version.rb
105
+ - lib/anagram_solver/permutator.rb
106
+ - lib/anagram_solver/file_manager.rb
107
+ - lib/anagram_solver/loader.rb
108
+ - lib/anagram_solver/finder.rb
109
+ - lib/anagram_solver/middleware.rb
110
+ - lib/anagram_solver/engine.rb
111
+ - lib/anagram_solver/async_consumer.rb
112
+ - lib/anagram_solver.rb
113
+ - MIT-LICENSE
114
+ - Rakefile
115
+ - README.rdoc
116
+ homepage: https://www.github.com/acnalesso/anagram_solver
117
+ licenses:
118
+ - MIT
119
+ metadata: {}
120
+ post_install_message:
121
+ rdoc_options: []
122
+ require_paths:
123
+ - lib
124
+ required_ruby_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - '>='
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
129
+ required_rubygems_version: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - '>='
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ requirements: []
135
+ rubyforge_project:
136
+ rubygems_version: 2.0.5
137
+ signing_key:
138
+ specification_version: 4
139
+ summary: Finds anagrams for a given word.
140
+ test_files: []