/// <summary> /// Returns the merged result of all the <paramref name="replicas"/>. /// </summary> /// <param name="biasAdd">True to have equal timestamps bias towards add; /// false to have equal timestamps bias towards remove.</param> /// <param name="replicas">The list of replicas to merge.</param> /// <returns>The merged replica of all the <paramref name="replicas"/>. /// </returns> static public LwwElementSet <T> Merge( bool biasAdd, params LwwElementSet <T>[] replicas) { LwwElementSet <T> mergedReplica = new LwwElementSet <T>(); mergedReplica.m_biasAdd = biasAdd; // Collect all the unique elements from all the replicas. COLG.HashSet <T> allElements = new COLG.HashSet <T>(); for (int i = 0; i < replicas.Length; i++) { allElements.UnionWith(replicas[i].m_addSet.Keys); allElements.UnionWith(replicas[i].m_removeSet.Keys); } // Go through all the unique elements and get the latest timestamp for // its add and/or remove operation. foreach (T element in allElements) { long maxAddTimestamp = DateTime.MinValue.Ticks; long maxRemoveTimestamp = DateTime.MinValue.Ticks; for (int i = 0; i < replicas.Length; i++) { if (replicas[i].m_addSet.TryGetValue( element, out long addTimestamp)) { maxAddTimestamp = Math.Max(maxAddTimestamp, addTimestamp); } if (replicas[i].m_removeSet.TryGetValue( element, out long removeTimestamp)) { maxRemoveTimestamp = Math.Max(maxRemoveTimestamp, removeTimestamp); } } if (maxAddTimestamp != DateTime.MinValue.Ticks) { mergedReplica.m_addSet[element] = maxAddTimestamp; } if (maxRemoveTimestamp != DateTime.MinValue.Ticks) { mergedReplica.m_removeSet[element] = maxRemoveTimestamp; } } return(mergedReplica); }
/// <summary> /// Override Equals method to check if two objects are equal. /// </summary> /// <param name="obj">The object to compare.</param> /// <returns>True if they're equal; false if not.</returns> public override bool Equals(object obj) { LwwElementSet <T> other = obj as LwwElementSet <T>; if (obj == null || this.m_biasAdd != other.m_biasAdd || this.m_addSet.Count != other.m_addSet.Count || this.m_removeSet.Count != other.m_removeSet.Count) { return(false); } // Check the elements and timestamps in the add sets are equal. foreach (COLG.KeyValuePair <T, long> kvp in this.m_addSet) { T element = kvp.Key; long timestamp = kvp.Value; if (other.m_addSet.TryGetValue(element, out long otherTimestamp)) { if (timestamp != otherTimestamp) { return(false); } } else { return(false); } } // Check the elements and timestamps in the remove sets are equal. foreach (COLG.KeyValuePair <T, long> kvp in this.m_removeSet) { T element = kvp.Key; long timestamp = kvp.Value; if (other.m_removeSet.TryGetValue(element, out long otherTimestamp)) { if (timestamp != otherTimestamp) { return(false); } } else { return(false); } } return(true); }
public void BiasRemoveTest() { int element = 1; long time = m_time.GetDateTimeNow(); LwwElementSet <int> replica = new LwwElementSet <int>(); replica.Add(element, time); replica.Remove(element, time); replica.SetBiasRemove(); Assert.IsTrue(replica.IsEmpty()); Assert.IsFalse(replica.Lookup(element)); }
public void MergeReplicasAddTest() { LwwElementSet <int> replica1 = new LwwElementSet <int>(); replica1.Add(1, m_time.GetDateTimeNow()); LwwElementSet <int> replica2 = new LwwElementSet <int>(); replica2.Add(2, m_time.GetDateTimeNow()); LwwElementSet <int> mergedReplica = LwwElementSet <int> .Merge(true, replica1, replica2); Assert.IsFalse(mergedReplica.IsEmpty()); Assert.IsTrue(mergedReplica.Lookup(1)); Assert.IsTrue(mergedReplica.Lookup(2)); }
public void RemoveTest() { LwwElementSet <int> replica = new LwwElementSet <int>(); int[] elements = { 1, 2 }; for (int i = 0; i < elements.Length; i++) { replica.Remove(elements[i], m_time.GetDateTimeNow()); } Assert.IsTrue(replica.IsEmpty()); for (int i = 0; i < elements.Length; i++) { Assert.IsFalse(replica.Lookup(elements[i])); } }
public void MergeReplicasAddRemoveTest() { int element = 1; LwwElementSet <int> replica1 = new LwwElementSet <int>(); replica1.Add(element, m_time.GetDateTimeNow()); LwwElementSet <int> replica2 = new LwwElementSet <int>(); replica2.Add(element, m_time.GetDateTimeNow()); replica2.Remove(element, m_time.GetDateTimeNow()); LwwElementSet <int> mergedReplica = LwwElementSet <int> .Merge(true, replica1, replica2); Assert.IsTrue(mergedReplica.IsEmpty()); Assert.IsFalse(mergedReplica.Lookup(element)); }
public void EmptyReplicaTest() { LwwElementSet <char> replica = new LwwElementSet <char>(); Assert.IsTrue(replica.IsEmpty()); }
public void MergeReplicasRandomOperationsTest() { // Create multiple replicas to apply multiple operations across. int numReplicas = 5; COLG.List <LwwElementSet <int> > replicas = new COLG.List <LwwElementSet <int> >(numReplicas); for (int i = 0; i < numReplicas; i++) { replicas.Add(new LwwElementSet <int>()); } // Create a single replica to apply all the operations on. // This will be used to verify the merged replica of the replicas above. LwwElementSet <int> expectedReplica = new LwwElementSet <int>(); Random rand = new Random(); COLG.List <long> previousTimes = new COLG.List <long>(); previousTimes.Add(m_time.GetDateTimeNow()); int numOperations = 1000; for (int i = 0; i < numOperations; i++) { // Determine which replica to use. int index = rand.Next(numReplicas); // Determine to use the current time or a previously used time. long time; if (rand.NextDouble() < 0.5) { time = m_time.GetDateTimeNow(); previousTimes.Add(time); } else { time = previousTimes[rand.Next(previousTimes.Count)]; } // Determine which element to use (0 to 9, both inclusive). int element = rand.Next(100); // Determine which operation to use. if (rand.NextDouble() < 0.5) { replicas[index].Add(element, time); expectedReplica.Add(element, time); } else { replicas[index].Remove(element, time); expectedReplica.Remove(element, time); } } LwwElementSet <int> mergedReplica = LwwElementSet <int> .Merge( true, replicas.ToArray()); Assert.IsTrue(expectedReplica.Equals(mergedReplica)); }