meangirls 0.1.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,168 @@
1
+ class Meangirls::LWWSet < Meangirls::Set
2
+ require 'time'
3
+
4
+ class Pair
5
+ attr_accessor :add, :remove
6
+ def initialize(add, remove)
7
+ @add = add
8
+ @remove = remove
9
+ end
10
+
11
+ def ==(o)
12
+ o.kind_of? Pair and
13
+ add == o.add and
14
+ remove == o.remove
15
+ end
16
+
17
+ def exists_a?
18
+ return false unless @add
19
+ return true unless @remove
20
+ @add >= @remove
21
+ end
22
+
23
+ def exists_r?
24
+ return false unless @add
25
+ return true unless @remove
26
+ @add > @remove
27
+ end
28
+
29
+ def inspect
30
+ "(#{add.inspect}, #{remove.inspect})"
31
+ end
32
+
33
+ # Merge with another pair, taking the largest add and largest delete stamp.
34
+ def merge(other)
35
+ unless other
36
+ return clone
37
+ end
38
+
39
+ Pair.new(
40
+ [add, other.add].compact.max,
41
+ [remove, other.remove].compact.max
42
+ )
43
+ end
44
+ alias | merge
45
+ end
46
+
47
+ def self.biases
48
+ ['a', 'r']
49
+ end
50
+
51
+ attr_accessor :e
52
+ attr_accessor :bias
53
+ def initialize(hash = nil)
54
+ @e = {}
55
+ @bias = 'a'
56
+
57
+ if hash
58
+ raise ArgumentError, "hash must contain e" unless hash['e']
59
+ @bias = hash['bias'] if hash['bias']
60
+ hash['e'].each do |list|
61
+ element, add, delete = list
62
+ @e[element] = Pair.new(add, delete).merge(@e[element])
63
+ end
64
+ end
65
+ end
66
+
67
+ # Inserts e into the set with a default generated timestamp.
68
+ # Your clocks ARE synchronized, right? WRONG!
69
+ def <<(e)
70
+ add(e, timestamp)
71
+ end
72
+
73
+ # Strict equality: both adds and removes are equal.
74
+ def ==(other)
75
+ other.kind_of? self.class and
76
+ e == other.e
77
+ end
78
+
79
+ # Add e, with an optional timestamp.
80
+ def add(e, time = timestamp)
81
+ merge_element! e, Pair.new(time, nil)
82
+ self
83
+ end
84
+
85
+ def as_json
86
+ {
87
+ 'type' => type,
88
+ 'e' => @e.map { |e, pair|
89
+ [e, pair.add, pair.remove]
90
+ }
91
+ }
92
+ end
93
+
94
+ def clone
95
+ c = super
96
+ c.e = e.clone
97
+ c
98
+ end
99
+
100
+ # Delete e from self, with optional timestamp.
101
+ def delete(e, time = timestamp)
102
+ merge_element! e, Pair.new(nil, time)
103
+ e
104
+ end
105
+
106
+ # Is e an element of the set?
107
+ def include?(e)
108
+ return false unless pair = @e[e]
109
+ case bias
110
+ when 'a'
111
+ pair.exists_a?
112
+ when 'r'
113
+ pair.exists_r?
114
+ else
115
+ raise RuntimeError, "unsupported bias #{bias.inspect}"
116
+ end
117
+ end
118
+
119
+ # Merge with another lww-set
120
+ def merge(other)
121
+ unless other.kind_of? self.class
122
+ raise ArgumentError, "other must be a #{self.class}"
123
+ end
124
+
125
+ c = clone
126
+ other.e.each do |element, pair|
127
+ c.merge_element!(element, pair)
128
+ end
129
+ c
130
+ end
131
+
132
+ # Mutates self to update the value for e with the given pair.
133
+ def merge_element!(e, pair)
134
+ if cur = @e[e]
135
+ @e[e] = cur | pair
136
+ else
137
+ @e[e] = pair
138
+ end
139
+ end
140
+
141
+ def to_set
142
+ case bias
143
+ when 'a'
144
+ @e.inject(Set.new) do |s, l|
145
+ element, pair = l
146
+ s << element if pair.exists_a?
147
+ s
148
+ end
149
+ when 'r'
150
+ @e.inject(Set.new) do |s, l|
151
+ element, pair = l
152
+ s << element if pair.exists_r?
153
+ s
154
+ end
155
+ end
156
+ end
157
+
158
+ # Default timestamps are the present time in UTC as ISO 8601 strings, WITH a
159
+ # seconds fraction guaranteed to be unique and monotonically increasing for
160
+ # all use of Meangirls.
161
+ def timestamp
162
+ Meangirls.timestamp
163
+ end
164
+
165
+ def type
166
+ 'lww-set'
167
+ end
168
+ end
@@ -0,0 +1,134 @@
1
+ class Meangirls::ORSet < Meangirls::Set
2
+ class Pair
3
+ attr_accessor :adds, :removes
4
+ def initialize(adds = [], removes = [])
5
+ @adds = adds
6
+ @removes = removes
7
+ end
8
+
9
+ def to_s
10
+ inspect
11
+ end
12
+
13
+ def inspect
14
+ "(#{adds.inspect}, #{removes.inspect})"
15
+ end
16
+ end
17
+
18
+ def self.biases
19
+ ['a']
20
+ end
21
+
22
+ attr_accessor :e
23
+ def initialize(hash = nil)
24
+ @e = {}
25
+
26
+ if hash
27
+ raise ArgumentError, 'hash must contain e' unless hash['e']
28
+ hash['e'].each do |list|
29
+ element, adds, removes = list
30
+ merge_internal! element, Pair.new(adds, removes)
31
+ end
32
+ end
33
+ end
34
+
35
+ # Inserts e into the set.
36
+ def <<(e)
37
+ add e
38
+ end
39
+
40
+ # Strict equality: all adds/removes match for every element.
41
+ # TODO: slow
42
+ def ==(other)
43
+ other.kind_of? self.class and
44
+ (@e.keys | other.e.keys).all? do |e, pair|
45
+ a = @e[e] and b = other.e[e] and
46
+ uaeq(a.adds, b.adds) and
47
+ uaeq(a.removes, b.removes)
48
+ end
49
+ end
50
+
51
+ # Inserts e into the set. Tag will be randomly generated if not given.
52
+ def add(e, tag = Meangirls.tag)
53
+ pair = (@e[e] ||= Pair.new)
54
+ pair.adds |= [tag]
55
+ self
56
+ end
57
+
58
+ def as_json
59
+ {
60
+ 'type' => type,
61
+ 'e' => @e.map do |e, pair|
62
+ [e, pair.adds, pair.removes]
63
+ end
64
+ }
65
+ end
66
+
67
+ def bias
68
+ 'a'
69
+ end
70
+
71
+ # UGH defensive copying
72
+ def clone
73
+ c = super
74
+ c.e = {}
75
+ @e.each do |e, pair|
76
+ c.merge_internal! e, pair.clone
77
+ end
78
+ c
79
+ end
80
+
81
+ # Deletes e from self by cancelling all known tags (or a specific tag if
82
+ # given.) Returns nil if no changes, e otherwise.
83
+ def delete(e, tag = nil)
84
+ pair = @e[e] or return
85
+ new = pair.adds - pair.removes
86
+ return if new.empty?
87
+ pair.removes += new
88
+ e
89
+ end
90
+
91
+ # Merge with another OR-Set and return the merged copy.
92
+ def merge(other)
93
+ unless other.kind_of? self.class
94
+ raise ArgumentError, "other must be a #{self.class}"
95
+ end
96
+
97
+ copy = clone
98
+ @e.each do |e, pair|
99
+ copy.merge_internal! e, pair
100
+ end
101
+ other.e.each do |e, pair|
102
+ copy.merge_internal! e, pair
103
+ end
104
+ copy
105
+ end
106
+
107
+ # Updates self with new adds and removes for an element.
108
+ def merge_internal!(element, pair)
109
+ if my = @e[element]
110
+ my.adds |= pair.adds
111
+ my.removes |= pair.removes
112
+ else
113
+ @e[element] = pair
114
+ end
115
+ end
116
+
117
+ def to_set
118
+ s = Set.new
119
+ @e.each do |element, pair|
120
+ s << element unless (pair.adds - pair.removes).empty?
121
+ end
122
+ s
123
+ end
124
+
125
+ def type
126
+ 'or-set'
127
+ end
128
+
129
+ # Unordered array equality
130
+ # TODO: slow
131
+ def uaeq(a, b)
132
+ (a - b).empty? and (b - a).empty?
133
+ end
134
+ end
@@ -0,0 +1,63 @@
1
+ class Meangirls::Set < Meangirls::CRDT
2
+ require 'meangirls/two_phase_set'
3
+ require 'meangirls/lww_set'
4
+ require 'meangirls/or_set'
5
+
6
+ include Enumerable
7
+
8
+ # Add all elements of other to a copy of self.
9
+ def |(other)
10
+ other.inject(clone) do |copy, e|
11
+ copy << e
12
+ end
13
+ end
14
+
15
+ # Return a copy of self where [all elements not present in other] have been
16
+ # deleted.
17
+ def &(other)
18
+ (to_set - other.to_set).inject(clone) do |copy, e|
19
+ copy.delete e
20
+ copy
21
+ end
22
+ end
23
+
24
+ # Add all elements of other to a copy of self.
25
+ def +(other)
26
+ other.inject(clone) do |copy, e|
27
+ copy << e
28
+ end
29
+ end
30
+
31
+ # Remove all elements of other from a copy of self.
32
+ def -(other)
33
+ other.inject(clone) do |copy, e|
34
+ copy.delete e
35
+ copy
36
+ end
37
+ end
38
+
39
+ # Loose equality: present elements of each set are equal
40
+ def ===(other)
41
+ to_set == other.to_set
42
+ end
43
+
44
+ # Iterate over all present elements
45
+ def each(&block)
46
+ to_set.each(&block)
47
+ end
48
+
49
+ # Are there no elements present?
50
+ def empty?
51
+ size == 0
52
+ end
53
+
54
+ # How many elements are present in the set?
55
+ def size
56
+ to_set.size
57
+ end
58
+
59
+ # Convert to an array
60
+ def to_a
61
+ to_set.to_a
62
+ end
63
+ end
@@ -0,0 +1,92 @@
1
+ class Meangirls::TwoPhaseSet < Meangirls::Set
2
+ def self.biases
3
+ ['r']
4
+ end
5
+
6
+ attr_accessor :a, :r
7
+ def initialize(hash = nil)
8
+ if hash
9
+ raise ArgumentError, "hash must contain a" unless hash['a']
10
+ raise ArgumentError, "hash must contain r" unless hash['r']
11
+
12
+ @a = Set.new hash['a']
13
+ @r = Set.new hash['r']
14
+ else
15
+ # Empty set
16
+ @a = Set.new
17
+ @r = Set.new
18
+ end
19
+ end
20
+
21
+ # Inserts e into the set. Raises ReinsertNotAllowed if e was previously
22
+ # deleted.
23
+ def <<(e)
24
+ if @r.include? e
25
+ raise Meangirls::ReinsertNotAllowed
26
+ end
27
+
28
+ @a << e
29
+ self
30
+ end
31
+ alias add <<
32
+
33
+ # Strict equality: both adds and removes for both 2p-sets are equal.
34
+ def ==(other)
35
+ other.kind_of? self.class and
36
+ a == other.a and
37
+ r == other.r
38
+ end
39
+
40
+ def as_json
41
+ {
42
+ 'type' => type,
43
+ 'a' => a.to_a,
44
+ 'r' => r.to_a
45
+ }
46
+ end
47
+
48
+ def bias
49
+ 'r'
50
+ end
51
+
52
+ def clone
53
+ c = super
54
+ c.a = a.clone
55
+ c.r = r.clone
56
+ c
57
+ end
58
+
59
+ # Deletes e from self. Raises DeleteNotAllowed if e does not presently
60
+ # exist.
61
+ def delete(e)
62
+ unless @a.include? e
63
+ raise Meangirls::DeleteNotAllowed
64
+ end
65
+ @r << e
66
+ e
67
+ end
68
+
69
+ # Merge with another 2p-set.
70
+ def merge(other)
71
+ unless other.kind_of? self.class
72
+ raise ArgumentError, "other must be a #{self.class}"
73
+ end
74
+
75
+ self.class.new(
76
+ 'a' => (a | other.a),
77
+ 'r' => (r | other.r)
78
+ )
79
+ end
80
+
81
+ def include?(e)
82
+ @a.include? e and not @r.include? e
83
+ end
84
+
85
+ def to_set
86
+ @a - @r
87
+ end
88
+
89
+ def type
90
+ '2p-set'
91
+ end
92
+ end