-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathelastic_matcher.rb
94 lines (75 loc) · 2.15 KB
/
elastic_matcher.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# coding:utf-8
# can be used as a measure between two strokes (arrays of vectors)
# TODO sets of strokes
require 'matrix'
# ElasticMatcher = lambda do |first, second|
#
# end
module Enumerable
def sum(*args, &block)
if block_given?
map &block
else
self
end.inject(*args) { |sum, x| sum + x }
end
end
class ElasticMatcher
def initialize measure
@measure = measure
end
def call first, second
reset first.size
D first, second
end
private
def reset num
@distance_memory = Hash.new { |h,v| h[v] = {} }
@dynamic_memory = Array.new(num) { [] }
end
def d point, qoint # two vectors
@distance_memory[point][qoint] ||= @measure.call(point, qoint)
end
def D r,s
n, m = r.size-1, s.size-1
if n == 0 # r is only one point left
# need to map remaining points of s to remaining point in s
return(s.sum { |e| d(e, r.first) })
end
if m == 0 # s is only 1 point left
# need to map remaining points of s to remaining point in r
return(r.sum { |e| d(e, s.first) })
end
@dynamic_memory[n][m] ||= d(r[n],s[m]) + [
D(r, s[0..-2]),
D(r[0..-2], s),
D(r[0..-2], s[0..-2])
].min
end
end
# TODO make a MultielasticMatcher that actually makes sense
# MultiElasticMatcher = lambda do |first, second|
# @matcher = ElasticMatcher.new
# small, long = first.size < second.size ? [first, second] : [second, first]
# res = (0...small.size).sum { |i| @matcher.call(small[i], long[i]) }
# # if long is longer match all remaining against last stroke of small
# res += (small.size...long.size).sum { |i| @matcher.call(small.last, long[i]) } || 0
# end
if __FILE__ == $0
require 'test/unit'
class TestElasticMatcher < Test::Unit::TestCase
def setup
@matcher = ElasticMatcher.new lambda { |q,p| (q-p).r }
end
def test_match_self_exactly
s,t = [(1..10).map { Vector[rand, rand] }]*2
assert_equal(@matcher.call(s,t), 0)
end
def test_does_not_match_different
s,t = [[Vector[0,0]],[Vector[0,1]]]
assert_not_equal(@matcher.call(s,t), 0)
end
# def test_sanity
# end
end
end