obfs 0.1.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,41 @@
1
+ module OBFS
2
+ module StringSimilarity
3
+ extend self
4
+
5
+ # Dice overlap of character-bigram multisets. Repeated pairs count separately.
6
+ # Case, path separators and whitespace are normalized before comparison.
7
+ # Empty text has no evidence of similarity; equal nonempty text scores 1.0.
8
+ def similarity(str1, str2)
9
+ left = normalize(str1)
10
+ right = normalize(str2)
11
+ return 0.0 if left.empty? || right.empty?
12
+ return 1.0 if left == right
13
+
14
+ left_pairs = bigrams(left)
15
+ right_pairs = bigrams(right)
16
+ total = left_pairs.values.inject(0, :+) + right_pairs.values.inject(0, :+)
17
+ return 0.0 if total == 0
18
+
19
+ overlap = left_pairs.inject(0) do |sum, entry|
20
+ pair, count = entry
21
+ sum + [count, right_pairs[pair]].min
22
+ end
23
+ 2.0 * overlap / total
24
+ end
25
+
26
+ private
27
+
28
+ def normalize(value)
29
+ value.to_s.encode('UTF-8').downcase
30
+ .gsub(/[\\\/:_\-]+/, ' ')
31
+ .gsub(/\s+/, ' ').strip
32
+ end
33
+
34
+ def bigrams(value)
35
+ counts = Hash.new(0)
36
+ characters = value.each_codepoint.to_a
37
+ characters.each_cons(2) { |pair| counts[pair] += 1 }
38
+ counts
39
+ end
40
+ end
41
+ end
data/lib/obfs.rb CHANGED
@@ -4,8 +4,6 @@ require 'json'
4
4
  require 'set'
5
5
 
6
6
  # obfs
7
+ require 'obfs/levenshtein'
8
+ require 'obfs/string_similarity'
7
9
  require 'obfs/store'
8
-
9
- # third party
10
- require 'text/levenshtein'
11
- require 'text/white_similarity'
metadata CHANGED
@@ -1,24 +1,26 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: obfs
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jensel Gatchalian
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2021-01-16 00:00:00.000000000 Z
10
+ date: 2026-10-04 00:00:00.000000000 Z
11
11
  dependencies: []
12
- description: File-based, object-oriented data store for Ruby
12
+ description: File-based, object-oriented data store for Ruby with recursive access,
13
+ fuzzy discovery, and no external runtime dependencies.
13
14
  email: jensel.gatchalian@gmail.com
14
15
  executables: []
15
16
  extensions: []
16
17
  extra_rdoc_files: []
17
18
  files:
19
+ - README.md
18
20
  - lib/obfs.rb
21
+ - lib/obfs/levenshtein.rb
19
22
  - lib/obfs/store.rb
20
- - lib/text/levenshtein.rb
21
- - lib/text/white_similarity.rb
23
+ - lib/obfs/string_similarity.rb
22
24
  homepage: https://github.com/jenselg/obfs-ruby
23
25
  licenses:
24
26
  - MIT
@@ -37,7 +39,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
37
39
  - !ruby/object:Gem::Version
38
40
  version: '0'
39
41
  requirements: []
40
- rubygems_version: 4.0.18
42
+ rubygems_version: 4.0.20
41
43
  specification_version: 4
42
- summary: OBFS
44
+ summary: Filesystem-backed object store with fuzzy discovery
43
45
  test_files: []
@@ -1,166 +0,0 @@
1
- #
2
- # Credits to threedaymonk
3
- # https://github.com/threedaymonk/text/blob/master/lib/text/levenshtein.rb
4
- #
5
- # Levenshtein distance algorithm implementation for Ruby, with UTF-8 support.
6
- #
7
- # The Levenshtein distance is a measure of how similar two strings s and t are,
8
- # calculated as the number of deletions/insertions/substitutions needed to
9
- # transform s into t. The greater the distance, the more the strings differ.
10
- #
11
- # The Levenshtein distance is also sometimes referred to as the
12
- # easier-to-pronounce-and-spell 'edit distance'.
13
- #
14
- # Author: Paul Battley (pbattley@gmail.com)
15
- #
16
-
17
- module OBFS # :nodoc:
18
-
19
- module Levenshtein
20
-
21
- # Calculate the Levenshtein distance between two strings +str1+ and +str2+.
22
- #
23
- # The optional argument max_distance can reduce the number of iterations by
24
- # stopping if the Levenshtein distance exceeds this value. This increases
25
- # performance where it is only necessary to compare the distance with a
26
- # reference value instead of calculating the exact distance.
27
- #
28
- # The distance is calculated in terms of Unicode codepoints. Be aware that
29
- # this algorithm does not perform normalisation: if there is a possibility
30
- # of different normalised forms being used, normalisation should be performed
31
- # beforehand.
32
- #
33
- def distance(str1, str2, max_distance = nil)
34
- if max_distance
35
- distance_with_maximum(str1, str2, max_distance)
36
- else
37
- distance_without_maximum(str1, str2)
38
- end
39
- end
40
-
41
- private
42
-
43
- def distance_with_maximum(str1, str2, max_distance) # :nodoc:
44
- s = str1.encode(Encoding::UTF_8).unpack("U*")
45
- t = str2.encode(Encoding::UTF_8).unpack("U*")
46
-
47
- n = s.length
48
- m = t.length
49
- big_int = n * m
50
-
51
- # Swap if necessary so that s is always the shorter of the two strings
52
- s, t, n, m = t, s, m, n if m < n
53
-
54
- # If the length difference is already greater than the max_distance, then
55
- # there is nothing else to check
56
- if (n - m).abs >= max_distance
57
- return max_distance
58
- end
59
-
60
- return 0 if s == t
61
- return m if n.zero?
62
- return n if m.zero?
63
-
64
- # The values necessary for our threshold are written; the ones after must
65
- # be filled with large integers since the tailing member of the threshold
66
- # window in the bottom array will run min across them
67
- d = (m + 1).times.map { |i|
68
- if i < m || i < max_distance + 1
69
- i
70
- else
71
- big_int
72
- end
73
- }
74
- x = nil
75
- e = nil
76
-
77
- n.times do |i|
78
- # Since we're reusing arrays, we need to be sure to wipe the value left
79
- # of the starting index; we don't have to worry about the value above the
80
- # ending index as the arrays were initially filled with large integers
81
- # and we progress to the right
82
- if e.nil?
83
- e = i + 1
84
- else
85
- e = big_int
86
- end
87
-
88
- diag_index = t.length - s.length + i
89
-
90
- # If max_distance was specified, we can reduce second loop. So we set
91
- # up our threshold window.
92
- # See:
93
- # Gusfield, Dan (1997). Algorithms on strings, trees, and sequences:
94
- # computer science and computational biology.
95
- # Cambridge, UK: Cambridge University Press. ISBN 0-521-58519-8.
96
- # pp. 263–264.
97
- min = i - max_distance - 1
98
- min = 0 if min < 0
99
- max = i + max_distance
100
- max = m - 1 if max > m - 1
101
-
102
- min.upto(max) do |j|
103
- # If the diagonal value is already greater than the max_distance
104
- # then we can safety return: the diagonal will never go lower again.
105
- # See: http://www.levenshtein.net/
106
- if j == diag_index && d[j] >= max_distance
107
- return max_distance
108
- end
109
-
110
- cost = s[i] == t[j] ? 0 : 1
111
- insertion = d[j + 1] + 1
112
- deletion = e + 1
113
- substitution = d[j] + cost
114
- x = insertion < deletion ? insertion : deletion
115
- x = substitution if substitution < x
116
-
117
- d[j] = e
118
- e = x
119
- end
120
- d[m] = x
121
- end
122
-
123
- if x > max_distance
124
- return max_distance
125
- else
126
- return x
127
- end
128
- end
129
-
130
- def distance_without_maximum(str1, str2) # :nodoc:
131
- s = str1.encode(Encoding::UTF_8).unpack("U*")
132
- t = str2.encode(Encoding::UTF_8).unpack("U*")
133
-
134
- n = s.length
135
- m = t.length
136
-
137
- return m if n.zero?
138
- return n if m.zero?
139
-
140
- d = (0..m).to_a
141
- x = nil
142
-
143
- n.times do |i|
144
- e = i + 1
145
- m.times do |j|
146
- cost = s[i] == t[j] ? 0 : 1
147
- insertion = d[j + 1] + 1
148
- deletion = e + 1
149
- substitution = d[j] + cost
150
- x = insertion < deletion ? insertion : deletion
151
- x = substitution if substitution < x
152
-
153
- d[j] = e
154
- e = x
155
- end
156
- d[m] = x
157
- end
158
-
159
- return x
160
- end
161
-
162
- extend self
163
-
164
- end
165
-
166
- end
@@ -1,66 +0,0 @@
1
- #
2
- # Credits to threedaymonk
3
- # https://github.com/threedaymonk/text/blob/master/lib/text/white_similarity.rb
4
- #
5
- # encoding: utf-8
6
- # Original author: Wilker Lúcio <wilkerlucio@gmail.com>
7
-
8
- module OBFS
9
-
10
- # Ruby implementation of the string similarity described by Simon White
11
- # at: http://www.catalysoft.com/articles/StrikeAMatch.html
12
- #
13
- # 2 * |pairs(s1) INTERSECT pairs(s2)|
14
- # similarity(s1, s2) = -----------------------------------
15
- # |pairs(s1)| + |pairs(s2)|
16
- #
17
- # e.g.
18
- # 2 * |{FR, NC}|
19
- # similarity(FRANCE, FRENCH) = ---------------------------------------
20
- # |{FR,RA,AN,NC,CE}| + |{FR,RE,EN,NC,CH}|
21
- #
22
- # = (2 * 2) / (5 + 5)
23
- #
24
- # = 0.4
25
- #
26
- # WhiteSimilarity.new.similarity("FRANCE", "FRENCH")
27
- #
28
- class WhiteSimilarity
29
-
30
- def self.similarity(str1, str2)
31
- new.similarity(str1, str2)
32
- end
33
-
34
- def initialize
35
- @word_letter_pairs = {}
36
- end
37
-
38
- def similarity(str1, str2)
39
- pairs1 = word_letter_pairs(str1)
40
- pairs2 = word_letter_pairs(str2).dup
41
-
42
- union = pairs1.length + pairs2.length
43
-
44
- intersection = 0
45
- pairs1.each do |pair1|
46
- if index = pairs2.index(pair1)
47
- intersection += 1
48
- pairs2.delete_at(index)
49
- end
50
- end
51
-
52
- (2.0 * intersection) / union
53
- end
54
-
55
- private
56
-
57
- def word_letter_pairs(str)
58
- @word_letter_pairs[str] ||=
59
- str.upcase.split(/\s+/).map{ |word|
60
- (0 ... (word.length - 1)).map { |i| word[i, 2] }
61
- }.flatten.freeze
62
- end
63
-
64
- end
65
-
66
- end