keyboard_distance 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *~
19
+ *.swp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in keyboard_distance.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 snukky
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,58 @@
1
+ # KeyboardDistance
2
+
3
+ Simple algorithm for measuring of distance-on-keyboard between two strings
4
+ for various keyboard layouts.
5
+
6
+ A diagonal distance between two keys equals the longer distance in a straight
7
+ line to the appropriate row/column, e.g.:
8
+
9
+ require 'keyboard_distance'
10
+ keyboard = KeyboardDistance.new
11
+
12
+ keyboard.character_distance('q', 'w') # => 1.0
13
+ keyboard.character_distance('q', 's') # => 1.0
14
+ keyboard.character_distance('q', 'd') # => 2.0
15
+
16
+ The similarity of the two strings is:
17
+
18
+ 1.0 - (D / (L * M))
19
+
20
+ Where `D` is the distance between the two strings, L is the length of the
21
+ longer string, and M is the maximum key distance for given layout.
22
+
23
+ ## Installation
24
+
25
+ Add this line to your application's Gemfile:
26
+
27
+ gem 'keyboard_distance'
28
+
29
+ And then execute:
30
+
31
+ $ bundle
32
+
33
+ Or install it yourself as:
34
+
35
+ $ gem install keyboard_distance
36
+
37
+ ## Usage
38
+
39
+ Require the gem:
40
+
41
+ require 'keyboard_distance'
42
+
43
+ Basic usage:
44
+
45
+ keyboard = KeyboardDistance.new
46
+ keyboard.distance("foo", "boo") # => 1.0
47
+ keyboard.similarity("foo", "boo") # => 0.9770114942528736
48
+
49
+ keyboard.distance("foo", "Boo") # => 2.0
50
+ keyboard.similarity("foo", "Boo") # => 0.9540229885057472
51
+
52
+ Extra parameters:
53
+
54
+ keyboard = KeyboardDistance.new(:layout => :qwerty,
55
+ :national_keys => :polish,
56
+ :alt_distance => 0.5)
57
+
58
+ keyboard.distance("między", "miedzy") # => 0.5
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env rake
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ desc "Run the specs under spec/"
7
+ RSpec::Core::RakeTask.new :test do |spec|
8
+ spec.pattern = 'spec/**/*_spec.rb'
9
+ spec.rspec_opts = ['--color']
10
+ end
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/keyboard_distance/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["snukky"]
6
+ gem.email = ["snk987@gmail.com"]
7
+ gem.description = %q{Simple algorithm for measuring of distance-on-keyboard
8
+ between two strings.}
9
+ gem.summary = %q{Distance-on-keyboard algorithm.}
10
+ gem.homepage = "https://github.com/snukky/keyboard_distance"
11
+
12
+ gem.files = `git ls-files`.split($\)
13
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
14
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
15
+ gem.name = "keyboard_distance"
16
+ gem.require_paths = ["lib"]
17
+ gem.version = KeyboardDistance::VERSION
18
+
19
+ gem.add_development_dependency 'rake'
20
+ gem.add_development_dependency 'rspec', '>= 2.0.0'
21
+ end
@@ -0,0 +1,119 @@
1
+ # encoding: utf-8
2
+
3
+ require "keyboard_distance/version"
4
+ require "keyboard_distance/keyboard_layout"
5
+
6
+ class KeyboardDistance
7
+
8
+ attr_reader :max_distance
9
+
10
+ SHIFT_DISTANCE = 1.0
11
+ ALT_DISTANCE = 0.5
12
+ SPACE_DISTANCE = 2.0
13
+ UNKNOWN_CHAR_DISTANCE = 3.0
14
+
15
+ def initialize(options={})
16
+ @layout = options[:layout] || :qwerty
17
+ raise "Unknown layout :#{@layout}" unless KeyboardLayout.layout_defined?(@layout)
18
+
19
+ @national_keys = options[:national_keys] || :polish
20
+ raise "Unknown national keys #{@national_keys}" unless !@national_keys ||
21
+ KeyboardLayout.national_defined?(@national_keys)
22
+
23
+ @shift_distance = options[:shift_distance] || SHIFT_DISTANCE
24
+ @alt_distance = options[:alt_distance] || ALT_DISTANCE
25
+ @space_distance = options[:space_distance] || SPACE_DISTANCE
26
+ @unknown_char_distance = options[:unknown_char_distance] || UNKNOWN_CHAR_DISTANCE
27
+
28
+ @distances = calculate_distances
29
+ @max_distance = calculate_max_distance
30
+
31
+ @shift_map = KeyboardLayout.build_shifted_keys_map(@layout)
32
+ @alt_map = KeyboardLayout.build_alted_keys_map(@national_keys) if @national_keys
33
+ end
34
+
35
+ def similarity(word_1, word_2)
36
+ return nil unless word_1.size == word_2.size
37
+
38
+ max_word_length = [word_1.size, word_2.size].max
39
+ 1.0 - distance(word_1, word_2) / (max_word_length * @max_distance)
40
+ end
41
+
42
+ def distance(word_1, word_2)
43
+ return nil unless word_1.size == word_2.size
44
+ return 0.0 if word_1 == word_2
45
+
46
+ dist = 0.0
47
+ word_1.each_char.with_index do |char_1, idx|
48
+ dist += character_distance(char_1, word_2[idx])
49
+ end
50
+
51
+ dist
52
+ end
53
+
54
+ def character_distance(char_1, char_2)
55
+ return 0.0 if char_1 == char_2
56
+ return @space_distance if char_1 == ' ' || char_2 == ' '
57
+ dist = 0.0
58
+
59
+ if @national_keys
60
+ key_1, key_2, difference = unalt_keys(char_1, char_2)
61
+ dist += @alt_distance if difference
62
+ end
63
+
64
+ key_1, key_2, difference = unshift_keys(key_1 || char_1, key_2 || char_2)
65
+ dist += @shift_distance if difference
66
+
67
+ return dist if key_1 == key_2
68
+
69
+ dist + (@distances[keys_signature(key_1, key_2)] || @unknown_char_distance)
70
+ end
71
+
72
+ private
73
+
74
+ def calculate_distances
75
+ distances = {}
76
+
77
+ KeyboardLayout.foreach_unique_key_pair(@layout) do |key_1, key_2, indexes|
78
+ keys = keys_signature(key_1, key_2)
79
+ next if distances.include?(keys)
80
+
81
+ distances[keys] = key_distance(*indexes)
82
+ end
83
+
84
+ distances
85
+ end
86
+
87
+ def key_distance(idx_11, idx_12, idx_21, idx_22)
88
+ [(idx_11 - idx_21).abs, (idx_12 - idx_22).abs].max
89
+ end
90
+
91
+ def keys_signature(key_1, key_2)
92
+ [key_1, key_2].sort.join
93
+ end
94
+
95
+ def unshift_keys(key_1, key_2)
96
+ unshifted_key_1 = @shift_map[key_1] || key_1
97
+ unshifted_key_2 = @shift_map[key_2] || key_2
98
+
99
+ difference = (unshifted_key_1 != key_1 && unshifted_key_2 == key_2) ||
100
+ (unshifted_key_1 == key_1 && unshifted_key_2 != key_2)
101
+
102
+ return [unshifted_key_1, unshifted_key_2, difference]
103
+ end
104
+
105
+ def unalt_keys(key_1, key_2)
106
+ unalted_key_1 = @alt_map[key_1] || key_1
107
+ unalted_key_2 = @alt_map[key_2] || key_2
108
+
109
+ difference = (unalted_key_1 != key_1 && unalted_key_2 == key_2) ||
110
+ (unalted_key_1 == key_1 && unalted_key_2 != key_2)
111
+
112
+ return [unalted_key_1, unalted_key_2, difference]
113
+ end
114
+
115
+ def calculate_max_distance
116
+ @distances.values.max + @alt_distance + @shift_distance
117
+ end
118
+
119
+ end
@@ -0,0 +1,112 @@
1
+ # encoding: utf-8
2
+
3
+ class KeyboardLayout
4
+
5
+ LAYOUTS = {
6
+ :qwerty => {
7
+ :normal => ['`1234567890-=', ' qwertyuiop[]\\', " asdfghjkl;'", ' zxcvbnm,./'],
8
+ :shift => ['~!@#$%^&*()_+', ' QWERTYUIOP{}|', ' ASDFGHJKL:"', ' ZXCVBNM<>?']
9
+ },
10
+ :qwertz => {
11
+ :normal => ['`1234567890-=', ' qwertzuiop[]\\', " asdfghjkl;'", ' yxcvbnm,./'],
12
+ :shift => ['~!@#$%^&*()_+', ' QWERTZUIOP{}|', ' ASDFGHJKL:"', ' YXCVBNM<>?']
13
+ }
14
+ }
15
+
16
+ NATIONAL_KEYS = {
17
+ :polish => ["ąćęłńóśżźĄĆĘŁŃÓŚŻŹ", "acelnoszzACELNOSZZ"]
18
+ }
19
+
20
+ EMPTY_KEY = ' '
21
+
22
+ class << self
23
+ def layout_defined?(layout)
24
+ LAYOUTS.keys.include?(layout)
25
+ end
26
+
27
+ def national_defined?(national)
28
+ NATIONAL_KEYS.keys.include?(national)
29
+ end
30
+
31
+ def build_shifted_keys_map(layout)
32
+ keys = layout_as_array(layout, :normal).flatten
33
+ shifted_keys = layout_as_array(layout, :shift).flatten
34
+
35
+ raise "Different number of shifted and unshifted keys for layout" \
36
+ " :#{layout}" unless keys.size == shifted_keys.size
37
+
38
+ shift_map = {}
39
+ shifted_keys.each_with_index do |shifted_key, idx|
40
+ shift_map[shifted_key] = keys[idx]
41
+ end
42
+
43
+ shift_map
44
+ end
45
+
46
+ def build_alted_keys_map(national)
47
+ national_keys = NATIONAL_KEYS[national]
48
+ raise "Undefined national keys #{}" if national_keys.nil?
49
+
50
+ raise "Different number of national and ASCII keys for " \
51
+ " :#{national}" unless national_keys[0].size == national_keys[1].size
52
+
53
+ alt_map = {}
54
+ national_keys[0].split('').each_with_index do |char, idx|
55
+ alt_map[char] = national_keys[1][idx]
56
+ end
57
+
58
+ alt_map
59
+ end
60
+
61
+ def foreach_unique_key_pair(layout, &block)
62
+ keyboard = layout_as_array(layout, :normal)
63
+
64
+ keyboard.each_with_index do |row_1, idx_11|
65
+ row_1.each_with_index do |key_1, idx_12|
66
+ keyboard.each_with_index do |row_2, idx_21|
67
+ row_2.each_with_index do |key_2, idx_22|
68
+ next if key_1 == key_2 || key_1 == EMPTY_KEY || key_2 == EMPTY_KEY
69
+ yield key_1, key_2, [idx_11, idx_12, idx_21, idx_22]
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+
76
+ def print_layout(layout, type=:normal)
77
+ keyboard = layout_as_array(layout, type)
78
+ max_row_size = keyboard.map(&:size).max
79
+
80
+ puts format_header(max_row_size), format_separator(max_row_size)
81
+ keyboard.each_with_index { |keys, idx| puts format_row(idx, keys) }
82
+ puts format_separator(max_row_size)
83
+ end
84
+
85
+ private
86
+
87
+ def layout_as_array(layout, type=:normal)
88
+ raise "Undefined layout :#{layout}" unless layout_defined?(layout)
89
+
90
+ LAYOUTS[layout][type].map{ |line| line.split('') }
91
+ end
92
+
93
+ def format_header(length)
94
+ header = " |"
95
+ length.times{ |i| header << sprintf(" %-3d", i) }
96
+ header
97
+ end
98
+
99
+ def format_separator(length)
100
+ separator = " --+"
101
+ length.times{ |i| separator << "---+"}
102
+ separator
103
+ end
104
+
105
+ def format_row(idx, keys)
106
+ row = " #{idx} |"
107
+ keys.each{ |k| row << " #{k} |" }
108
+ row
109
+ end
110
+ end
111
+
112
+ end
@@ -0,0 +1,3 @@
1
+ class KeyboardDistance
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,118 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe KeyboardDistance do
6
+ before :all do
7
+ @keyboard = KeyboardDistance.new(:layout => :qwerty,
8
+ :national_keys => :polish,
9
+ :shift_distance => 1,
10
+ :alt_distance => 0.5,
11
+ :space_distance => 2,
12
+ :unknown_char_distance => 1)
13
+ end
14
+
15
+ describe "character_distance" do
16
+ it "should return zero for equal characters" do
17
+ @keyboard.character_distance('a', 'a').should eql 0.0
18
+ @keyboard.character_distance('A', 'A').should eql 0.0
19
+ @keyboard.character_distance('ą', 'ą').should eql 0.0
20
+ end
21
+
22
+ it "should measure distances in a straight line" do
23
+ @keyboard.character_distance('a', 's').should eql 1.0
24
+ @keyboard.character_distance('a', 'l').should eql 8.0
25
+ @keyboard.character_distance('a', 'z').should eql 1.0
26
+ @keyboard.character_distance('a', '1').should eql 2.0
27
+ end
28
+
29
+ it "should measure diagonally distances" do
30
+ @keyboard.character_distance('s', 'q').should eql 1.0
31
+ @keyboard.character_distance('s', 'e').should eql 1.0
32
+ @keyboard.character_distance('s', 'c').should eql 1.0
33
+ @keyboard.character_distance('s', 'z').should eql 1.0
34
+
35
+ @keyboard.character_distance('q', 'd').should eql 2.0
36
+ @keyboard.character_distance('q', '3').should eql 2.0
37
+
38
+ @keyboard.character_distance('q', 'p').should eql 9.0
39
+ end
40
+
41
+ it "should increase distance when shift is pressed" do
42
+ @keyboard.character_distance('a', 'z').should be <
43
+ @keyboard.character_distance('A', 'z')
44
+ end
45
+
46
+ it "should return the same distance when shift is pressed twice" do
47
+ @keyboard.character_distance('a', 'y').should eql \
48
+ @keyboard.character_distance('A', 'Y')
49
+ end
50
+
51
+ it "should increase distance when alt is pressed" do
52
+ @keyboard.character_distance('a', 'z').should be <
53
+ @keyboard.character_distance('ą', 'z')
54
+ end
55
+
56
+ it "should return the same distance when alt is pressed twice" do
57
+ @keyboard.character_distance('a', 'z').should eql \
58
+ @keyboard.character_distance('ą', 'ż')
59
+ end
60
+
61
+ it "should work with a space character" do
62
+ expect{ @keyboard.character_distance('a', ' ') }.not_to raise_error
63
+ end
64
+
65
+ it "should work with unknown character" do
66
+ expect{ @keyboard.character_distance('a', '¶') }.not_to raise_error
67
+ end
68
+ end
69
+
70
+ describe "distance" do
71
+ it "should return zero for equals words" do
72
+ @keyboard.distance("foo", "foo").should eql 0.0
73
+ @keyboard.distance("FOO", "FOO").should eql 0.0
74
+ end
75
+
76
+ it "should measure distance as the sum of character distances" do
77
+ word_1 = "foo"
78
+ word_2 = "bar"
79
+
80
+ sum_of_char_distances = 0.0
81
+ word_1.each_char.with_index do |char, idx|
82
+ sum_of_char_distances += @keyboard.character_distance(char, word_2[idx])
83
+ end
84
+
85
+ @keyboard.distance(word_1, word_2).should eql sum_of_char_distances
86
+ end
87
+
88
+ it "should measure simple distances" do
89
+ @keyboard.distance("asd", "sdf").should eql 3.0
90
+ @keyboard.distance("1qaz", "2wsx").should eql 4.0
91
+ @keyboard.distance("qsc", "wdv").should eql 3.0
92
+
93
+ @keyboard.distance("foo", "Foo").should eql 1.0
94
+ @keyboard.distance("laka", "łąka").should eql 1.0
95
+ end
96
+
97
+ it "should work with different length strings" do
98
+ expect{ @keyboard.distance("foo", "bar baz") }.not_to raise_error
99
+ end
100
+ end
101
+
102
+ describe "similarity" do
103
+ it "should give 1 for the same strings" do
104
+ @keyboard.similarity("foo", "foo").should eql 1.0
105
+ end
106
+
107
+ it "should work for simple strings" do
108
+ expected_value = (1 - 1 / (3 * @keyboard.max_distance))
109
+ @keyboard.similarity("foo", "boo").should eql expected_value
110
+ end
111
+
112
+ it "should decrease similarity for longer string" do
113
+ @keyboard.similarity("foo", "boo").should be <
114
+ @keyboard.similarity("foooooo", "boooooo")
115
+ end
116
+ end
117
+
118
+ end
@@ -0,0 +1,31 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe KeyboardLayout do
6
+ before :all do
7
+ @layout = KeyboardLayout::LAYOUTS.keys.first
8
+ end
9
+
10
+ describe "build_shifted_keys_map" do
11
+ it "should return a Hash object" do
12
+ KeyboardLayout.build_shifted_keys_map(@layout).should be_kind_of Hash
13
+ end
14
+
15
+ it "should have capital characters as keys" do
16
+ KeyboardLayout.build_shifted_keys_map(@layout).keys
17
+ .all?{ |big| big.upcase == big }.should be_true
18
+ end
19
+
20
+ it "should have small characters as keys" do
21
+ KeyboardLayout.build_shifted_keys_map(@layout).values
22
+ .all?{ |small| small.downcase == small }.should be_true
23
+ end
24
+ end
25
+
26
+ describe "layout_defined?" do
27
+ it "should raise error when access to undefined layout" do
28
+ expect{ KeyboardLayout.print_layout(:unexisted_layout) }.to raise_error
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,3 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+
3
+ require 'keyboard_distance'
metadata ADDED
@@ -0,0 +1,83 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: keyboard_distance
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - snukky
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-07-07 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rake
16
+ requirement: &69387850 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *69387850
25
+ - !ruby/object:Gem::Dependency
26
+ name: rspec
27
+ requirement: &69387230 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: 2.0.0
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *69387230
36
+ description: ! "Simple algorithm for measuring of distance-on-keyboard \n between
37
+ two strings."
38
+ email:
39
+ - snk987@gmail.com
40
+ executables: []
41
+ extensions: []
42
+ extra_rdoc_files: []
43
+ files:
44
+ - .gitignore
45
+ - Gemfile
46
+ - LICENSE
47
+ - README.md
48
+ - Rakefile
49
+ - keyboard_distance.gemspec
50
+ - lib/keyboard_distance.rb
51
+ - lib/keyboard_distance/keyboard_layout.rb
52
+ - lib/keyboard_distance/version.rb
53
+ - spec/keyboard_distance_spec.rb
54
+ - spec/keyboard_layout_spec.rb
55
+ - spec/spec_helper.rb
56
+ homepage: https://github.com/snukky/keyboard_distance
57
+ licenses: []
58
+ post_install_message:
59
+ rdoc_options: []
60
+ require_paths:
61
+ - lib
62
+ required_ruby_version: !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ! '>='
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ! '>='
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ requirements: []
75
+ rubyforge_project:
76
+ rubygems_version: 1.8.10
77
+ signing_key:
78
+ specification_version: 3
79
+ summary: Distance-on-keyboard algorithm.
80
+ test_files:
81
+ - spec/keyboard_distance_spec.rb
82
+ - spec/keyboard_layout_spec.rb
83
+ - spec/spec_helper.rb