Permalink
Browse files

WIP: or-sets

  • Loading branch information...
1 parent cd835af commit 6d1428121f1acc929ff1d52d79c9f4366d070114 @aphyr committed Jan 6, 2012
Showing with 113 additions and 0 deletions.
  1. +8 −0 lib/meangirls.rb
  2. +105 −0 lib/meangirls/or_set.rb
View
@@ -5,6 +5,8 @@ class ReinsertNotAllowed < RuntimeError; end
class DeleteNotAllowed < RuntimeError; end
require 'set'
+ require 'base64'
+ require 'securerandom'
require 'meangirls/crdt'
# Transforms a JSON data structure into a CRDT datatype.
@@ -17,4 +19,10 @@ def parse(s)
end
end
module_function :parse
+
+ # Return a pseudounique tag.
+ def tag
+ SecureRandom.urlsafe_base64
+ end
+ module_function :tag
end
View
@@ -0,0 +1,105 @@
+class Meangirls::ORSet < Meangirls::Set
+ Pair = Struct :adds, :removes
+
+ def self.biases
+ ['a']
+ end
+
+ attr_accessor :e
+ def initialize(hash = nil)
+ @e = {}
+
+ if hash
+ raise ArgumentError, 'hash must contain e' unless hash['e']
+ hash['e'].each do |list|
+ element, adds, removes = list
+ merge_internal! element, Pair.new(adds, removes)
+ end
+ end
+ end
+
+ # Inserts e into the set.
+ def <<(e)
+ add e
+ end
+
+ # Strict equality: all adds/removes match for every element.
+ # TODO: slow
+ 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
+ uaeq(a.adds, b.adds) and
+ uaeq(a.removes, b.removes)
+ end
+ end
+
+ # 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
+ end
+
+ def as_json
+ {
+ 'type' => type,
+ 'e' => @e.map do |e, pair|
+ [e, pair.adds, pair.deletes]
+ end
+ }
+ end
+
+ def bias
+ 'a'
+ end
+
+ # UGH defensive copying
+ def clone
+ c = super
+ @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)
+ pair = @e[e] or return
+ new = pair.adds - pair.deletes
+ return if new.empty?
+ pair.deletes += new
+ e
+ end
+
+ # Merge with another OR-Set
+ def merge(other)
+ unless other.kind_of? self.class
+ raise ArgumentError, "other must be a #{self.class}"
+ end
+
+ copy = clone
+ @e.each do |e, pair|
+ copy.merge_internal! e, pair
+ end
+ other.e.each do |e, pair|
+ copy.merge_internal! e, pair
+ end
+ 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
+ else
+ @e[element] = pair
+ end
+ end
+
+ # Unordered array equality
+ # TODO: slow
+ def uaeq(a, b)
+ (a - b).empty? and (b - a).empty?
+ end
+end

0 comments on commit 6d14281

Please sign in to comment.