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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +19 -0
- data/Rakefile +32 -0
- data/app/assets/javascripts/anagram_solver/application.js +17 -0
- data/app/assets/javascripts/anagram_solver/index.js +65 -0
- data/app/assets/stylesheets/anagram_solver/application.css +13 -0
- data/app/assets/stylesheets/anagram_solver/index.css +38 -0
- data/app/controllers/anagram_solver/anagram_solver_controller.rb +16 -0
- data/app/controllers/anagram_solver/application_controller.rb +4 -0
- data/app/helpers/anagram_solver/application_helper.rb +4 -0
- data/app/models/anagram_solver/anagram.rb +9 -0
- data/app/views/anagram_solver/anagram_solver/_form.html.erb +16 -0
- data/app/views/anagram_solver/anagram_solver/index.html.erb +1 -0
- data/app/views/layouts/anagram_solver/application.html.erb +12 -0
- data/config/routes.rb +5 -0
- data/lib/anagram_solver.rb +4 -0
- data/lib/anagram_solver/async_consumer.rb +96 -0
- data/lib/anagram_solver/engine.rb +10 -0
- data/lib/anagram_solver/file_manager.rb +97 -0
- data/lib/anagram_solver/finder.rb +95 -0
- data/lib/anagram_solver/loader.rb +7 -0
- data/lib/anagram_solver/middleware.rb +110 -0
- data/lib/anagram_solver/permutator.rb +130 -0
- data/lib/anagram_solver/routing.rb +18 -0
- data/lib/anagram_solver/version.rb +3 -0
- data/lib/tasks/anagram_solver_tasks.rake +4 -0
- metadata +140 -0
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,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,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,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,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)
|
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: []
|