levenstein_with_path 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2ac652def9cc19e91d929d8564d8c149cf5a7d76bfffa64a5a1c33f4916c2da2
4
+ data.tar.gz: 8ef661698b9ac965abf7ecac94db5eb10e383660ec6404a586a966bf06b67995
5
+ SHA512:
6
+ metadata.gz: 45450cf22c284ccae072c7c6ac87318497072f15f62a113749ffc2e624b9d8bbd8c96f61955ada897ae999564aef8d00c77201a5e0288fa5925e8e4a14335f91
7
+ data.tar.gz: 32f95bf400659442b39c5ef626ed7465e1d09e3536a8190650adfc9a7e3f8c285eba63af1b5e4b21004f9fc98534f42745088ef811f4df0dddba563cfbb2ac5c
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ .*.swp
2
+ .*.swo
3
+ /.ruby-version
4
+ /.ruby-gemset
5
+ /pkg
6
+ /tmp
7
+ /README.html
8
+ /coverage
9
+ /Gemfile.lock
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,81 @@
1
+ Metrics/BlockLength:
2
+ Enabled: false
3
+
4
+ Metrics/LineLength:
5
+ Exclude:
6
+ - "Rakefile"
7
+ Max: 120
8
+
9
+ Metrics/MethodLength:
10
+ Exclude:
11
+ - "spec/**/*"
12
+ Max: 35
13
+
14
+ Metrics/AbcSize:
15
+ Max: 60
16
+
17
+ Metrics/CyclomaticComplexity:
18
+ Max: 12
19
+
20
+ Metrics/PerceivedComplexity:
21
+ Max: 15
22
+
23
+ Style/BlockDelimiters:
24
+ EnforcedStyle: line_count_based
25
+ Exclude:
26
+ - "spec/**/*"
27
+
28
+ Style/Documentation:
29
+ Exclude:
30
+ - "spec/**/*"
31
+ Style/PercentLiteralDelimiters:
32
+ PreferredDelimiters:
33
+ "%w": "[]"
34
+
35
+ Style/StringLiterals:
36
+ Enabled: false
37
+
38
+ Style/ExpandPathArguments:
39
+ Enabled: false
40
+
41
+ Gemspec/OrderedDependencies:
42
+ Enabled: false
43
+
44
+ Naming/HeredocDelimiterNaming:
45
+ Enabled: false
46
+
47
+ Layout/EmptyLinesAroundBlockBody:
48
+ Enabled: false
49
+
50
+ Layout/IndentHeredoc:
51
+ Enabled: false
52
+
53
+ Style/Encoding:
54
+ Enabled: false
55
+
56
+ Lint/AmbiguousBlockAssociation:
57
+ Enabled: false
58
+
59
+ Layout/SpaceBeforeBlockBraces:
60
+ Enabled: false
61
+
62
+ Layout/SpaceInsideBlockBraces:
63
+ Enabled: false
64
+
65
+ Style/TrailingCommaInArrayLiteral:
66
+ Enabled: false
67
+
68
+ Style/AndOr:
69
+ Enabled: false
70
+
71
+ Layout/EmptyLinesAroundModuleBody:
72
+ Enabled: false
73
+
74
+ Layout/EmptyLinesAroundClassBody:
75
+ Enabled: false
76
+
77
+ Style/ConditionalAssignment:
78
+ Enabled: false
79
+
80
+ Style/TernaryParentheses:
81
+ Enabled: false
data/.travis.yml ADDED
@@ -0,0 +1,16 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9
4
+ - 2.0
5
+ - 2.1
6
+ - 2.2
7
+ - 2.3
8
+ - 2.4
9
+ - 2.5
10
+ - jruby
11
+
12
+ matrix:
13
+ include:
14
+ # Only run rubocop on the latest Ruby:
15
+ - rvm: 2.5
16
+ env: SUITE="rubocop"
data/.yardopts ADDED
@@ -0,0 +1 @@
1
+ --markup markdown
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2018 Paul A. Jungwirth
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.md ADDED
@@ -0,0 +1,79 @@
1
+ levenstein_with_path
2
+ ====================
3
+
4
+ [![Build Status](https://travis-ci.org/pjungwir/levenstein_with_path.svg?branch=master)](https://travis-ci.org/pjungwir/levenstein_with_path)
5
+
6
+ Ruby gem to find the Levenstein distance *and its sequence of edits*
7
+ between two strings or arrays of tokens (like words, lines, etc.).
8
+
9
+ Based on the [Wagner-Fischer algorithm](https://en.wikipedia.org/wiki/Wagner%E2%80%93Fischer_algorithm),
10
+ which is `O(n^2)` but lets you retain the history of edits.
11
+
12
+
13
+ Installation
14
+ ------------
15
+
16
+ Add this line to your application's Gemfile:
17
+
18
+ ```ruby
19
+ gem 'levenstein_with_path'
20
+ ```
21
+
22
+ And then execute:
23
+
24
+ $ bundle
25
+
26
+ Or install it yourself as:
27
+
28
+ $ gem install levenstein_with_path
29
+
30
+
31
+ Usage
32
+ -----
33
+
34
+ ```ruby
35
+ p = LevensteinWithPath::Path.new('kitten', 'sitting')
36
+ p.score
37
+ # => 3
38
+ p.edits
39
+ # => [
40
+ # LevensteinWithPath::Swap.new('k', 's'),
41
+ # LevensteinWithPath::Keep.new('i'),
42
+ # LevensteinWithPath::Keep.new('t'),
43
+ # LevensteinWithPath::Keep.new('t'),
44
+ # LevensteinWithPath::Swap.new('e', 'i'),
45
+ # LevensteinWithPath::Keep.new('n'),
46
+ # LevensteinWithPath::Insert.new('g'),
47
+ # ]
48
+ ```
49
+
50
+
51
+ Tests
52
+ -----
53
+
54
+ Say `rake` to run rspec tests plus Rubocop,
55
+ or `rspec spec` to run just the tests.
56
+
57
+
58
+ Contributing
59
+ ------------
60
+
61
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
62
+ * Check out the issue tracker to make sure someone hasn't already requested and/or contributed it.
63
+ * Fork the project.
64
+ * Start a feature/bugfix branch.
65
+ * Commit and push until you are happy with your contribution.
66
+ * Make be sure to add tests for it. This is important so I don't break it in a future version unintentionally.
67
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, that is fine, but please isolate that change to its own commit so I can cherry-pick around it.
68
+
69
+ Commands for building/releasing/installing:
70
+
71
+ * `rake build`
72
+ * `rake install`
73
+ * `rake release`
74
+
75
+ Copyright
76
+ ---------
77
+
78
+ Copyright (c) 2018 Paul A. Jungwirth.
79
+ See LICENSE.txt for further details.
data/Rakefile ADDED
@@ -0,0 +1,19 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+ require 'rubocop/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
6
+ RuboCop::RakeTask.new
7
+
8
+ task readme: [] do
9
+ system %q{jq --slurp --raw-input '{"text": "\(.)", "mode": "gfm", "context": "pjungwir/levenstein_with_path"}' < README.md | curl --data @- https://api.github.com/markdown > README.html}
10
+ end
11
+
12
+ if ENV['CI'].nil?
13
+ task default: %w[spec rubocop]
14
+ else
15
+ case ENV['SUITE']
16
+ when 'rubocop' then task default: :rubocop
17
+ else task default: :spec
18
+ end
19
+ end
@@ -0,0 +1,28 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'levenstein_with_path/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'levenstein_with_path'
7
+ s.version = LevensteinWithPath::VERSION
8
+
9
+ s.summary = 'Computes Levenstein distance between arrays of tokens, and gives the list of edits'
10
+ s.description = 'Computes Levenstein distance between arrays of tokens, and gives the list of edits'
11
+
12
+ s.authors = ['Paul A. Jungwirth']
13
+ s.homepage = 'https://github.com/pjungwir/levenstein_with_path'
14
+ s.email = ['pj@illuminatedcomputing.com']
15
+
16
+ s.licenses = ['MIT']
17
+
18
+ s.require_paths = ['lib']
19
+ s.executables = []
20
+ s.files = `git ls-files`.split("\n")
21
+ s.test_files = `git ls-files -- {test,spec,fixtures}/*`.split("\n")
22
+
23
+ s.add_development_dependency 'rspec', '>= 3.2.0'
24
+ s.add_development_dependency 'rake'
25
+ s.add_development_dependency 'bundler'
26
+ s.add_development_dependency 'simplecov'
27
+ s.add_development_dependency 'rubocop'
28
+ end
@@ -0,0 +1,139 @@
1
+ module LevensteinWithPath
2
+
3
+ # Computes the levenstein distance between two sequences.
4
+ #
5
+ # Unlike other Ruby libraries, this one:
6
+ #
7
+ # - Can tell you not just the optimal score, but the sequence of operations to get it.
8
+ # This is useful if you want to present a nice diff.
9
+ # - Lets you pass either strings or array of some other atomic tokens
10
+ # (like words, lines, nucleobases, etc.).
11
+ #
12
+ # It is based on the [Wagner-Fischer algorithm](https://en.wikipedia.org/wiki/Wagner%E2%80%93Fischer_algorithm),
13
+ # which is `O(n^2)` but lets you retain the history of edits.
14
+ # See also [that algorithm applied to Levenstein distance](https://en.wikipedia.org/wiki/Levenshtein_distance#Computing_Levenshtein_distance).
15
+ class Path
16
+ attr_reader :tokens1, :tokens2
17
+ attr_reader :scores, :score
18
+
19
+ def initialize(tokens1, tokens2)
20
+ @tokens1 = if tokens1.is_a?(String)
21
+ tokens1.chars
22
+ else
23
+ tokens1
24
+ end.to_a
25
+ @tokens2 = if tokens2.is_a?(String)
26
+ tokens2.chars
27
+ else
28
+ tokens2
29
+ end.to_a
30
+
31
+ s = @tokens1
32
+ t = @tokens2
33
+ m = s.size
34
+ n = t.size
35
+ d = 0.upto(m).map { [0] * (n + 1)}
36
+ 1.upto(m) {|i| d[i][0] = i}
37
+ 1.upto(n) {|j| d[0][j] = j}
38
+
39
+ # rubocop:disable Layout/SpaceInsideReferenceBrackets
40
+ 1.upto(n) do |j|
41
+ 1.upto(m) do |i|
42
+ cost = s[i - 1] == t[j - 1] ? 0 : 1
43
+ d[i][j] = [
44
+ d[i - 1][j ] + 1, # delete
45
+ d[i ][j - 1] + 1, # insert
46
+ d[i - 1][j - 1] + cost # keep/swap
47
+ ].min
48
+ end
49
+ end
50
+ # rubocop:enable Layout/SpaceInsideReferenceBrackets
51
+
52
+ @scores = d
53
+ @score = d[m][n]
54
+ end
55
+
56
+ def edits
57
+ return @edits if @edits
58
+ @edits = []
59
+ s = @tokens1
60
+ t = @tokens2
61
+ m = s.size
62
+ n = t.size
63
+ i = m
64
+ j = n
65
+ d = @scores
66
+ while i > 0 or j > 0
67
+ cur_score = d[i][j]
68
+ insert_score = j > 0 ? d[i][j - 1] : cur_score + 1
69
+ delete_score = i > 0 ? d[i - 1][j] : cur_score + 1
70
+ keep_or_swap_score = (i > 0 and j > 0) ? d[i - 1][j - 1] : cur_score + 1
71
+ best_score = [insert_score, delete_score, keep_or_swap_score].min
72
+ if best_score == keep_or_swap_score
73
+ if cur_score == keep_or_swap_score
74
+ @edits << Keep.new(s[i - 1])
75
+ else
76
+ @edits << Swap.new(s[i - 1], t[j - 1])
77
+ end
78
+ i -= 1
79
+ j -= 1
80
+ elsif best_score == insert_score
81
+ @edits << Insert.new(t[j - 1])
82
+ j -= 1
83
+ else
84
+ @edits << Delete.new(s[i - 1])
85
+ i -= 1
86
+ end
87
+ end
88
+ @edits.reverse!
89
+ end
90
+
91
+ end
92
+
93
+ # rubocop:disable Layout/EmptyLineBetweenDefs
94
+ class Edit
95
+ end
96
+
97
+ class Keep < Edit #:nodoc:
98
+ attr_reader :token
99
+ def initialize(token)
100
+ @token = token
101
+ end
102
+ def ==(other)
103
+ other.is_a?(Keep) and other.token == token
104
+ end
105
+ end
106
+
107
+ class Delete < Edit #:nodoc:
108
+ attr_reader :token
109
+ def initialize(token)
110
+ @token = token
111
+ end
112
+ def ==(other)
113
+ other.is_a?(Delete) and other.token == token
114
+ end
115
+ end
116
+
117
+ class Insert < Edit #:nodoc:
118
+ attr_reader :token
119
+ def initialize(token)
120
+ @token = token
121
+ end
122
+ def ==(other)
123
+ other.is_a?(Edit) and other.token == token
124
+ end
125
+ end
126
+
127
+ class Swap < Edit #:nodoc:
128
+ attr_reader :token1, :token2
129
+ def initialize(token1, token2)
130
+ @token1 = token1
131
+ @token2 = token2
132
+ end
133
+ def ==(other)
134
+ other.is_a?(Swap) and other.token1 == token1 and other.token2 == token2
135
+ end
136
+ end
137
+ # rubocop:enable Layout/EmptyLineBetweenDefs
138
+
139
+ end
@@ -0,0 +1,3 @@
1
+ module LevensteinWithPath
2
+ VERSION = '1.0.0'.freeze
3
+ end
@@ -0,0 +1,80 @@
1
+ # encoding: UTF-8
2
+
3
+ describe 'LevensteinWithPath' do
4
+ # Some real tests:
5
+ let(:kitten_to_sitting) { LevensteinWithPath::Path.new('kitten', 'sitting') }
6
+ let(:saturday_to_sunday) { LevensteinWithPath::Path.new('Saturday', 'Sunday') }
7
+
8
+ # Non-ascii:
9
+ let(:eye_to_navel) { LevensteinWithPath::Path.new("ὀφθαλμός", "ὀμφαλός") }
10
+
11
+ # Some degenerate cases:
12
+ let(:cat_to_empty) { LevensteinWithPath::Path.new('cat', '') }
13
+ let(:empty_to_cat) { LevensteinWithPath::Path.new('', 'cat') }
14
+ let(:empty_to_empty) { LevensteinWithPath::Path.new('', '') }
15
+ let(:a_to_a) { LevensteinWithPath::Path.new('a', 'a') }
16
+ let(:cat_to_cat) { LevensteinWithPath::Path.new('cat', 'cat') }
17
+
18
+ # Arrays of words:
19
+ let(:vita_ad_spes) { LevensteinWithPath::Path.new(%w[Dum vita est], %w[spes est]) }
20
+
21
+ it "computes the score" do
22
+ expect(kitten_to_sitting.score).to eq 3
23
+ expect(saturday_to_sunday.score).to eq 3
24
+
25
+ expect(eye_to_navel.score).to eq 3
26
+
27
+ expect(cat_to_empty.score).to eq 3
28
+ expect(empty_to_cat.score).to eq 3
29
+ expect(empty_to_empty.score).to eq 0
30
+ expect(a_to_a.score).to eq 0
31
+ expect(cat_to_cat.score).to eq 0
32
+
33
+ expect(vita_ad_spes.score).to eq 2
34
+ end
35
+
36
+ it "lists the edits" do
37
+ expect(kitten_to_sitting.edits).to eq [
38
+ LevensteinWithPath::Swap.new('k', 's'),
39
+ LevensteinWithPath::Keep.new('i'),
40
+ LevensteinWithPath::Keep.new('t'),
41
+ LevensteinWithPath::Keep.new('t'),
42
+ LevensteinWithPath::Swap.new('e', 'i'),
43
+ LevensteinWithPath::Keep.new('n'),
44
+ LevensteinWithPath::Insert.new('g'),
45
+ ]
46
+ expect(saturday_to_sunday.edits).to eq [
47
+ LevensteinWithPath::Keep.new('S'),
48
+ LevensteinWithPath::Delete.new('a'),
49
+ LevensteinWithPath::Delete.new('t'),
50
+ LevensteinWithPath::Keep.new('u'),
51
+ LevensteinWithPath::Swap.new('r', 'n'),
52
+ LevensteinWithPath::Keep.new('d'),
53
+ LevensteinWithPath::Keep.new('a'),
54
+ LevensteinWithPath::Keep.new('y'),
55
+ ]
56
+
57
+ expect(eye_to_navel.edits).to eq [
58
+ LevensteinWithPath::Keep.new('ὀ'),
59
+ LevensteinWithPath::Swap.new('φ', 'μ'),
60
+ LevensteinWithPath::Swap.new('θ', 'φ'),
61
+ LevensteinWithPath::Keep.new('α'),
62
+ LevensteinWithPath::Keep.new('λ'),
63
+ LevensteinWithPath::Delete.new('μ'),
64
+ LevensteinWithPath::Keep.new('ό'),
65
+ LevensteinWithPath::Keep.new('ς'),
66
+ ]
67
+
68
+ expect(cat_to_empty.edits).to eq %w[c a t].map{|x| LevensteinWithPath::Delete.new(x)}
69
+ expect(empty_to_cat.edits).to eq %w[c a t].map{|x| LevensteinWithPath::Insert.new(x)}
70
+ expect(empty_to_empty.edits).to eq []
71
+ expect(a_to_a.edits).to eq [LevensteinWithPath::Keep.new('a')]
72
+ expect(cat_to_cat.edits).to eq %w[c a t].map{|x| LevensteinWithPath::Keep.new(x)}
73
+
74
+ expect(vita_ad_spes.edits).to eq [
75
+ LevensteinWithPath::Delete.new('Dum'),
76
+ LevensteinWithPath::Swap.new('vita', 'spes'),
77
+ LevensteinWithPath::Keep.new('est'),
78
+ ]
79
+ end
80
+ end
@@ -0,0 +1,11 @@
1
+ require 'simplecov'
2
+ SimpleCov.start do
3
+ add_filter '/spec/'
4
+ end
5
+
6
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
7
+ require 'levenstein_with_path'
8
+
9
+ RSpec.configure do |config|
10
+
11
+ end
metadata ADDED
@@ -0,0 +1,130 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: levenstein_with_path
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Paul A. Jungwirth
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-07-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 3.2.0
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 3.2.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
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: bundler
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: simplecov
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: rubocop
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: Computes Levenstein distance between arrays of tokens, and gives the
84
+ list of edits
85
+ email:
86
+ - pj@illuminatedcomputing.com
87
+ executables: []
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - ".gitignore"
92
+ - ".rspec"
93
+ - ".rubocop.yml"
94
+ - ".travis.yml"
95
+ - ".yardopts"
96
+ - Gemfile
97
+ - LICENSE.txt
98
+ - README.md
99
+ - Rakefile
100
+ - levenstein_with_path.gemspec
101
+ - lib/levenstein_with_path.rb
102
+ - lib/levenstein_with_path/version.rb
103
+ - spec/levenstein_with_path_spec.rb
104
+ - spec/spec_helper.rb
105
+ homepage: https://github.com/pjungwir/levenstein_with_path
106
+ licenses:
107
+ - MIT
108
+ metadata: {}
109
+ post_install_message:
110
+ rdoc_options: []
111
+ require_paths:
112
+ - lib
113
+ required_ruby_version: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ required_rubygems_version: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ requirements: []
124
+ rubyforge_project:
125
+ rubygems_version: 2.7.6
126
+ signing_key:
127
+ specification_version: 4
128
+ summary: Computes Levenstein distance between arrays of tokens, and gives the list
129
+ of edits
130
+ test_files: []