vector_number 0.6.0 → 0.7.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.
- checksums.yaml +4 -4
- data/README.md +255 -86
- data/lib/vector_number/comparing.rb +16 -8
- data/lib/vector_number/converting.rb +37 -19
- data/lib/vector_number/enumerating.rb +237 -22
- data/lib/vector_number/mathing.rb +62 -22
- data/lib/vector_number/querying.rb +3 -1
- data/lib/vector_number/{math_converting.rb → rounding.rb} +12 -35
- data/lib/vector_number/similarity.rb +97 -0
- data/lib/vector_number/special_unit.rb +20 -8
- data/lib/vector_number/stringifying.rb +52 -18
- data/lib/vector_number/vectoring.rb +504 -0
- data/lib/vector_number/version.rb +1 -1
- data/lib/vector_number.rb +105 -106
- data/sig/manifest.yaml +4 -0
- data/sig/vector_number.rbs +143 -56
- metadata +14 -18
- data/doc/vector_space.svg +0 -94
- data/lib/vector_number/numeric_refinements.rb +0 -97
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class VectorNumber
|
|
4
|
+
# @group Similarity measures
|
|
5
|
+
|
|
6
|
+
# Calculate cosine between this vector and +other+.
|
|
7
|
+
#
|
|
8
|
+
# Cosine can be used as a measure of similarity.
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# v = VectorNumber[2, "a"]
|
|
12
|
+
# v.cosine(v) # => 1.0
|
|
13
|
+
# v.cosine_similarity(1) # => 0.8944271909999159
|
|
14
|
+
# v.cosine("b") # => 0.0
|
|
15
|
+
# v.cosine_similarity(-v) # => -1.0
|
|
16
|
+
# v.cosine(0) # ZeroDivisionError
|
|
17
|
+
# VectorNumber[0].cosine(v) # ZeroDivisionError
|
|
18
|
+
#
|
|
19
|
+
# @see #angle
|
|
20
|
+
#
|
|
21
|
+
# @param other [VectorNumber, Any]
|
|
22
|
+
# @return [Numeric]
|
|
23
|
+
# @raise [ZeroDivisionError] if either +self+ or +other+ is a zero vector
|
|
24
|
+
#
|
|
25
|
+
# @since 0.7.0
|
|
26
|
+
def cosine(other)
|
|
27
|
+
has_direction?
|
|
28
|
+
return 1.0 if equal?(other)
|
|
29
|
+
|
|
30
|
+
other = new([other]) unless VectorNumber === other
|
|
31
|
+
has_direction?(other)
|
|
32
|
+
return 0.0 if (product = dot_product(other)).zero?
|
|
33
|
+
|
|
34
|
+
# Due to precision errors, the result might be slightly outside [-1, 1], so we clamp.
|
|
35
|
+
(product / magnitude / other.magnitude).clamp(-1.0, 1.0)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @since 0.7.0
|
|
39
|
+
alias cosine_similarity cosine
|
|
40
|
+
|
|
41
|
+
# Calculate Jaccard index of similarity between this vector and +other+.
|
|
42
|
+
#
|
|
43
|
+
# This measure is binary: it considers only sets of non-zero dimensions in vectors,
|
|
44
|
+
# ignoring coefficients.
|
|
45
|
+
#
|
|
46
|
+
# @example
|
|
47
|
+
# v = VectorNumber[2, "a"]
|
|
48
|
+
# v.jaccard_index(v) # => (1/1)
|
|
49
|
+
# v.jaccard_index(1) # => (1/2)
|
|
50
|
+
# v.jaccard_index("b") # => (0/1)
|
|
51
|
+
# v.jaccard_index(-v) # => (1/1)
|
|
52
|
+
# v.jaccard_index(0) # => (0/1)
|
|
53
|
+
# VectorNumber[0].jaccard_index(v) # => (0/1)
|
|
54
|
+
# VectorNumber[0].jaccard_index(0) # ZeroDivisionError
|
|
55
|
+
#
|
|
56
|
+
# @see #jaccard_similarity
|
|
57
|
+
#
|
|
58
|
+
# @param other [VectorNumber, Any]
|
|
59
|
+
# @return [Rational]
|
|
60
|
+
# @raise [ZeroDivisionError] if both vectors are zero vectors
|
|
61
|
+
#
|
|
62
|
+
# @since 0.7.0
|
|
63
|
+
def jaccard_index(other)
|
|
64
|
+
other = new([other]) unless VectorNumber === other
|
|
65
|
+
intersection = units.intersection(other.units)
|
|
66
|
+
Rational(intersection.size, size + other.size - intersection.size)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Calculate weighted Jaccard similarity index between this vector and +other+.
|
|
70
|
+
#
|
|
71
|
+
# This measure only makes sense for non-negative vectors.
|
|
72
|
+
#
|
|
73
|
+
# @example
|
|
74
|
+
# v = VectorNumber[2, "a"]
|
|
75
|
+
# v.jaccard_similarity(v) # => (1/1)
|
|
76
|
+
# v.jaccard_similarity(1) # => (1/3)
|
|
77
|
+
# v.jaccard_similarity("b") # => (0/1)
|
|
78
|
+
# v.jaccard_similarity(-v) # => (-1/1)
|
|
79
|
+
# v.jaccard_similarity(0) # => (0/1)
|
|
80
|
+
# VectorNumber[0].jaccard_similarity(v) # => (0/1)
|
|
81
|
+
# VectorNumber[0].jaccard_similarity(0) # ZeroDivisionError
|
|
82
|
+
#
|
|
83
|
+
# @see #jaccard_index
|
|
84
|
+
#
|
|
85
|
+
# @param other [VectorNumber, Any]
|
|
86
|
+
# @return [Rational]
|
|
87
|
+
# @raise [ZeroDivisionError] if both vectors are zero vectors
|
|
88
|
+
#
|
|
89
|
+
# @since 0.7.0
|
|
90
|
+
def jaccard_similarity(other)
|
|
91
|
+
other = new([other]) unless VectorNumber === other
|
|
92
|
+
Rational(
|
|
93
|
+
@data.sum { |u, c| [c, other[u]].min },
|
|
94
|
+
units.union(other.units).sum { |u| [self[u], other[u]].max }
|
|
95
|
+
)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
class VectorNumber
|
|
4
|
-
# Class for representing special units.
|
|
4
|
+
# Class for representing special (unique) units.
|
|
5
5
|
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
6
|
+
# There usually isn't much point in using these, aside from {VectorNumber::NUMERIC_UNITS}.
|
|
7
|
+
# However, they can be helpful to denote units that aren't equal to any other object.
|
|
8
|
+
#
|
|
9
|
+
# +VectorNumber#to_s+ (and +inspect+) will use {#to_s} text instead of {#inspect} text
|
|
10
|
+
# for these units by default, without a multiplication operator. However, consider
|
|
11
|
+
# using customization of +VectorNumber#to_s+ instead if this is what you want.
|
|
11
12
|
#
|
|
12
13
|
# @since 0.6.0
|
|
13
14
|
class SpecialUnit
|
|
14
|
-
#
|
|
15
|
+
# Returns a new, unique unit, not equal to any other unit.
|
|
16
|
+
#
|
|
15
17
|
# @param unit [#to_s] name for {#inspect}
|
|
16
18
|
# @param text [String] text for {#to_s}
|
|
17
|
-
def initialize(unit, text)
|
|
19
|
+
def initialize(unit, text = unit.to_s)
|
|
18
20
|
@unit = unit
|
|
19
21
|
@text = text
|
|
20
22
|
end
|
|
@@ -26,6 +28,16 @@ class VectorNumber
|
|
|
26
28
|
@text
|
|
27
29
|
end
|
|
28
30
|
|
|
31
|
+
# Support for PP, outputs the same text as {#inspect}.
|
|
32
|
+
#
|
|
33
|
+
# @param pp [PP]
|
|
34
|
+
# @return [void]
|
|
35
|
+
#
|
|
36
|
+
# @since 0.7.0
|
|
37
|
+
def pretty_print(pp)
|
|
38
|
+
pp.text(inspect)
|
|
39
|
+
end
|
|
40
|
+
|
|
29
41
|
# Get string representation of the unit for debugging.
|
|
30
42
|
#
|
|
31
43
|
# @return [String]
|
|
@@ -15,29 +15,30 @@ class VectorNumber
|
|
|
15
15
|
|
|
16
16
|
# @group Miscellaneous methods
|
|
17
17
|
|
|
18
|
-
# Return string representation of the vector.
|
|
18
|
+
# Return string representation of the vector suitable for output.
|
|
19
19
|
#
|
|
20
20
|
# An optional block can be supplied to provide customized substrings
|
|
21
21
|
# for each unit and coefficient pair.
|
|
22
22
|
# Care needs to be taken in handling +VectorNumber::R+ and +VectorNumber::I+ units.
|
|
23
|
-
# {.numeric_unit?} can be used to check if a particular unit
|
|
23
|
+
# {.numeric_unit?}/{.special_unit?} can be used to check if a particular unit
|
|
24
|
+
# requires different logic.
|
|
24
25
|
#
|
|
25
26
|
# @example
|
|
26
27
|
# VectorNumber[5, "s"].to_s # => "5 + 1⋅\"s\""
|
|
27
28
|
# VectorNumber["s", 5].to_s # => "1⋅\"s\" + 5"
|
|
28
29
|
# @example with :mult argument
|
|
29
|
-
# VectorNumber[5, :s].to_s(mult: :asterisk) # => "5 + 1
|
|
30
|
-
# (-VectorNumber[5, :s]).to_s(mult: "~~~") # => "-5 - 1
|
|
30
|
+
# VectorNumber[5, :s].to_s(mult: :asterisk) # => "5 + 1*:s"
|
|
31
|
+
# (-VectorNumber[5, :s]).to_s(mult: "~~~") # => "-5 - 1~~~:s"
|
|
31
32
|
# @example with a block
|
|
32
33
|
# VectorNumber[5, :s].to_s { |k, v| "#{format("%+.0f", v)}%#{k}" } # => "+5%1+1%s"
|
|
33
34
|
# VectorNumber[5, :s].to_s(mult: :cross) { |k, v, i, op|
|
|
34
|
-
# "#{',' unless i.zero?}#{v}#{op+k.
|
|
35
|
-
# } # => "5,1
|
|
35
|
+
# "#{',' unless i.zero?}#{v}#{op+k.inspect unless k == VectorNumber::R}"
|
|
36
|
+
# } # => "5,1×:s"
|
|
36
37
|
#
|
|
37
38
|
# @param mult [Symbol, String]
|
|
38
39
|
# text to use between coefficient and unit,
|
|
39
40
|
# can be one of the keys in {MULT_STRINGS} or an arbitrary string
|
|
40
|
-
# @yieldparam unit [
|
|
41
|
+
# @yieldparam unit [Any]
|
|
41
42
|
# @yieldparam coefficient [Numeric]
|
|
42
43
|
# @yieldparam index [Integer]
|
|
43
44
|
# @yieldparam operator [String]
|
|
@@ -46,27 +47,57 @@ class VectorNumber
|
|
|
46
47
|
# @raise [ArgumentError]
|
|
47
48
|
# if +mult+ is not a String and is not in {MULT_STRINGS}'s keys
|
|
48
49
|
def to_s(mult: :dot, &block)
|
|
49
|
-
if !
|
|
50
|
+
if !(String === mult) && !MULT_STRINGS.key?(mult)
|
|
50
51
|
raise ArgumentError, "unknown key #{mult.inspect}", caller
|
|
51
52
|
end
|
|
52
53
|
return "0" if zero?
|
|
53
54
|
|
|
54
|
-
operator =
|
|
55
|
+
operator = (String === mult) ? mult : MULT_STRINGS[mult]
|
|
55
56
|
build_string(operator, &block)
|
|
56
57
|
end
|
|
57
58
|
|
|
58
|
-
# Return string representation of the vector.
|
|
59
|
+
# Return string representation of the vector suitable for display.
|
|
59
60
|
#
|
|
60
|
-
# This is similar to +Complex#inspect
|
|
61
|
+
# This is similar to +Complex#inspect+ — it returns result of {#to_s} in round brackets.
|
|
61
62
|
#
|
|
62
63
|
# @example
|
|
63
|
-
# VectorNumber[5, :s].inspect # => "(5 + 1
|
|
64
|
+
# VectorNumber[5, :s].inspect # => "(5 + 1⋅:s)"
|
|
64
65
|
#
|
|
65
66
|
# @return [String]
|
|
66
67
|
#
|
|
67
68
|
# @see to_s
|
|
68
69
|
def inspect
|
|
69
|
-
"(
|
|
70
|
+
return "(0)" if zero?
|
|
71
|
+
|
|
72
|
+
"(#{build_string("⋅")})"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Support for PP, usually outputs the same text as {#inspect}.
|
|
76
|
+
#
|
|
77
|
+
# @param pp [PP]
|
|
78
|
+
# @return [void]
|
|
79
|
+
#
|
|
80
|
+
# @since 0.7.0
|
|
81
|
+
def pretty_print(pp) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
|
82
|
+
pp.text("(0)") and return if zero?
|
|
83
|
+
|
|
84
|
+
pp.group(1, "(", ")") do
|
|
85
|
+
# This should use `pp.fill_breakable`, but PrettyPrint::SingleLine haven't had it.
|
|
86
|
+
pp.seplist(@data, -> { fill_breakable(pp) }, :each_with_index) do |(unit, coefficient), i|
|
|
87
|
+
pp.text("-") if coefficient.negative?
|
|
88
|
+
unless i.zero?
|
|
89
|
+
pp.text("+") if coefficient.positive?
|
|
90
|
+
fill_breakable(pp)
|
|
91
|
+
end
|
|
92
|
+
pp.pp(coefficient.abs)
|
|
93
|
+
if SpecialUnit === unit
|
|
94
|
+
pp.text(unit.to_s)
|
|
95
|
+
else
|
|
96
|
+
pp.text("⋅")
|
|
97
|
+
pp.pp(unit)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
70
101
|
end
|
|
71
102
|
|
|
72
103
|
private
|
|
@@ -90,16 +121,19 @@ class VectorNumber
|
|
|
90
121
|
result
|
|
91
122
|
end
|
|
92
123
|
|
|
93
|
-
# @param unit [
|
|
124
|
+
# @param unit [Any]
|
|
94
125
|
# @param coefficient [Numeric]
|
|
95
126
|
# @param operator [String]
|
|
96
127
|
# @return [String]
|
|
97
128
|
def value_to_s(unit, coefficient, operator)
|
|
98
|
-
if
|
|
99
|
-
"#{coefficient}#{unit}"
|
|
129
|
+
if SpecialUnit === unit
|
|
130
|
+
"#{coefficient.inspect}#{unit}"
|
|
100
131
|
else
|
|
101
|
-
|
|
102
|
-
"#{coefficient}#{operator}#{unit}"
|
|
132
|
+
"#{coefficient.inspect}#{operator}#{unit.inspect}"
|
|
103
133
|
end
|
|
104
134
|
end
|
|
135
|
+
|
|
136
|
+
def fill_breakable(pp)
|
|
137
|
+
pp.group { pp.breakable }
|
|
138
|
+
end
|
|
105
139
|
end
|