Skip to content

Commit

Permalink
Observed-removed sets
Browse files Browse the repository at this point in the history
  • Loading branch information
Kyle Kingsbury committed Mar 31, 2012
1 parent 4caf7b8 commit 525b9e9
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 14 deletions.
2 changes: 2 additions & 0 deletions lib/meangirls.rb
Expand Up @@ -16,6 +16,8 @@ def parse(s)
TwoPhaseSet.new s TwoPhaseSet.new s
when 'lww-set' when 'lww-set'
LWWSet.new s LWWSet.new s
when 'or-set'
ORSet.new s
else else
raise ArgumentError, "unknown type #{s['type']}" raise ArgumentError, "unknown type #{s['type']}"
end end
Expand Down
57 changes: 43 additions & 14 deletions lib/meangirls/or_set.rb
@@ -1,5 +1,19 @@
class Meangirls::ORSet < Meangirls::Set 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 def self.biases
['a'] ['a']
Expand Down Expand Up @@ -28,7 +42,7 @@ def <<(e)
def ==(other) def ==(other)
other.kind_of? self.class and other.kind_of? self.class and
(@e.keys | other.e.keys).all? do |e, pair| (@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.adds, b.adds) and
uaeq(a.removes, b.removes) uaeq(a.removes, b.removes)
end end
Expand All @@ -37,14 +51,15 @@ def ==(other)
# Inserts e into the set. Tag will be randomly generated if not given. # Inserts e into the set. Tag will be randomly generated if not given.
def add(e, tag = Meangirls.tag) def add(e, tag = Meangirls.tag)
pair = (@e[e] ||= Pair.new) pair = (@e[e] ||= Pair.new)
pair.adds |= tag pair.adds |= [tag]
self
end end


def as_json def as_json
{ {
'type' => type, 'type' => type,
'e' => @e.map do |e, pair| 'e' => @e.map do |e, pair|
[e, pair.adds, pair.deletes] [e, pair.adds, pair.removes]
end end
} }
end end
Expand All @@ -56,23 +71,24 @@ def bias
# UGH defensive copying # UGH defensive copying
def clone def clone
c = super c = super
c.e = {}
@e.each do |e, pair| @e.each do |e, pair|
c.merge_internal! e, pair.clone c.merge_internal! e, pair.clone
end end
c c
end end


# Deletes e from self by cancelling all known tags. # Deletes e from self by cancelling all known tags (or a specific tag if
# Returns nil if no changes, e otherwise. # given.) Returns nil if no changes, e otherwise.
def delete(e) def delete(e, tag = nil)
pair = @e[e] or return pair = @e[e] or return
new = pair.adds - pair.deletes new = pair.adds - pair.removes
return if new.empty? return if new.empty?
pair.deletes += new pair.removes += new
e e
end end


# Merge with another OR-Set # Merge with another OR-Set and return the merged copy.
def merge(other) def merge(other)
unless other.kind_of? self.class unless other.kind_of? self.class
raise ArgumentError, "other must be a #{self.class}" raise ArgumentError, "other must be a #{self.class}"
Expand All @@ -85,18 +101,31 @@ def merge(other)
other.e.each do |e, pair| other.e.each do |e, pair|
copy.merge_internal! e, pair copy.merge_internal! e, pair
end end
copy
end end


# Updates self with new adds and removes for an element. # Updates self with new adds and removes for an element.
def merge_internal!(element, pair) def merge_internal!(element, pair)
if (a,r) = @e[element] if my = @e[element]
a |= pair.adds my.adds |= pair.adds
r |= pair.removes my.removes |= pair.removes
else else
@e[element] = pair @e[element] = pair
end end
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 # Unordered array equality
# TODO: slow # TODO: slow
def uaeq(a, b) def uaeq(a, b)
Expand Down
1 change: 1 addition & 0 deletions lib/meangirls/set.rb
@@ -1,6 +1,7 @@
class Meangirls::Set < Meangirls::CRDT class Meangirls::Set < Meangirls::CRDT
require 'meangirls/two_phase_set' require 'meangirls/two_phase_set'
require 'meangirls/lww_set' require 'meangirls/lww_set'
require 'meangirls/or_set'


include Enumerable include Enumerable


Expand Down
30 changes: 30 additions & 0 deletions 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.