Browse files

Observed-removed sets

  • Loading branch information...
1 parent 4caf7b8 commit 525b9e977ae1dc17c769e0f747eb9e04ae498815 Kyle Kingsbury committed Mar 31, 2012
Showing with 76 additions and 14 deletions.
  1. +2 −0 lib/meangirls.rb
  2. +43 −14 lib/meangirls/or_set.rb
  3. +1 −0 lib/meangirls/set.rb
  4. +30 −0 spec/or_set.rb
View
2 lib/meangirls.rb
@@ -16,6 +16,8 @@ def parse(s)
TwoPhaseSet.new s
when 'lww-set'
LWWSet.new s
+ when 'or-set'
+ ORSet.new s
else
raise ArgumentError, "unknown type #{s['type']}"
end
View
57 lib/meangirls/or_set.rb
@@ -1,5 +1,19 @@
class Meangirls::ORSet < Meangirls::Set
- Pair = Struct :adds, :removes
+ class Pair
+ attr_accessor :adds, :removes
+ def initialize(adds = [], removes = [])
+ @adds = adds
+ @removes = removes
+ end
+
+ def to_s
+ inspect
+ end
+
+ def inspect
+ "(#{adds.inspect}, #{removes.inspect})"
+ end
+ end
def self.biases
['a']
@@ -28,7 +42,7 @@ def <<(e)
def ==(other)
other.kind_of? self.class and
(@e.keys | other.e.keys).all? do |e, pair|
- a = @e[e] and b = other[e] and
+ a = @e[e] and b = other.e[e] and
uaeq(a.adds, b.adds) and
uaeq(a.removes, b.removes)
end
@@ -37,14 +51,15 @@ def ==(other)
# Inserts e into the set. Tag will be randomly generated if not given.
def add(e, tag = Meangirls.tag)
pair = (@e[e] ||= Pair.new)
- pair.adds |= tag
+ pair.adds |= [tag]
+ self
end
def as_json
{
'type' => type,
'e' => @e.map do |e, pair|
- [e, pair.adds, pair.deletes]
+ [e, pair.adds, pair.removes]
end
}
end
@@ -56,23 +71,24 @@ def bias
# UGH defensive copying
def clone
c = super
+ c.e = {}
@e.each do |e, pair|
c.merge_internal! e, pair.clone
end
c
end
- # Deletes e from self by cancelling all known tags.
- # Returns nil if no changes, e otherwise.
- def delete(e)
+ # Deletes e from self by cancelling all known tags (or a specific tag if
+ # given.) Returns nil if no changes, e otherwise.
+ def delete(e, tag = nil)
pair = @e[e] or return
- new = pair.adds - pair.deletes
+ new = pair.adds - pair.removes
return if new.empty?
- pair.deletes += new
+ pair.removes += new
e
end
- # Merge with another OR-Set
+ # Merge with another OR-Set and return the merged copy.
def merge(other)
unless other.kind_of? self.class
raise ArgumentError, "other must be a #{self.class}"
@@ -85,18 +101,31 @@ def merge(other)
other.e.each do |e, pair|
copy.merge_internal! e, pair
end
+ copy
end
# Updates self with new adds and removes for an element.
def merge_internal!(element, pair)
- if (a,r) = @e[element]
- a |= pair.adds
- r |= pair.removes
+ if my = @e[element]
+ my.adds |= pair.adds
+ my.removes |= pair.removes
else
@e[element] = pair
end
end
-
+
+ def to_set
+ s = Set.new
+ @e.each do |element, pair|
+ s << element unless (pair.adds - pair.removes).empty?
+ end
+ s
+ end
+
+ def type
+ 'or-set'
+ end
+
# Unordered array equality
# TODO: slow
def uaeq(a, b)
View
1 lib/meangirls/set.rb
@@ -1,6 +1,7 @@
class Meangirls::Set < Meangirls::CRDT
require 'meangirls/two_phase_set'
require 'meangirls/lww_set'
+ require 'meangirls/or_set'
include Enumerable
View
30 spec/or_set.rb
@@ -0,0 +1,30 @@
+describe 'or-set' do
+ before do
+ @class = Meangirls::ORSet
+ @idempotent = false
+ @s = @class.new
+ @examples = [
+ @class.new,
+ (@class.new << 1),
+ (@class.new - [1,2]),
+ (@class.new - [1,2] + [2,3]),
+ (@class.new + [1,2] - [2,3])
+ ]
+ end
+
+ behaves_like :crdt
+ behaves_like :set
+
+ should '==' do
+ a = @class.new
+ b = @class.new
+ a.add 1, 0
+ b.add 1, 0
+ a.should == b
+
+ a.delete 1
+ a.should.not == b
+ b.delete 1
+ a.should == b
+ end
+end

0 comments on commit 525b9e9

Please sign in to comment.