meangirls 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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