diff --git a/Main/src/CodeJam.Main.csproj b/Main/src/CodeJam.Main.csproj index 739428b5e..73c2005b2 100644 --- a/Main/src/CodeJam.Main.csproj +++ b/Main/src/CodeJam.Main.csproj @@ -74,6 +74,9 @@ True DebugCode.tt + + + diff --git a/Main/src/Collections/DisjointSets.cs b/Main/src/Collections/DisjointSets.cs new file mode 100644 index 000000000..1bf5a12dc --- /dev/null +++ b/Main/src/Collections/DisjointSets.cs @@ -0,0 +1,30 @@ +namespace CodeJam.Collections +{ + /// Disjoint sets without payload + /// + /// See http://en.wikipedia.org/wiki/Disjoint-set_data_structure + /// + public sealed class DisjointSets : DisjointSetsBase + { + /// Creates an empty Disjoint sets + public DisjointSets() { } + + /// Creates a Disjoint sets with the given number of elements + /// The initial number of elements + public DisjointSets(int count) + { + Add(count); + } + + /// Appends the given number of new elements + /// The number of elements to add + public void Add(int count) + { + for (var i = 0; i < count; ++i) + { + Nodes_.Add(new BasicNode { ParentIndex = -1, Rank = 0 }); + } + SetsCount += count; + } + } +} diff --git a/Main/src/Collections/DisjointSetsBase.cs b/Main/src/Collections/DisjointSetsBase.cs new file mode 100644 index 000000000..6a1bef7a2 --- /dev/null +++ b/Main/src/Collections/DisjointSetsBase.cs @@ -0,0 +1,104 @@ +using System.Collections.Generic; + +namespace CodeJam.Collections +{ + /// Disjoint sets implementation base + /// + /// See http://en.wikipedia.org/wiki/Disjoint-set_data_structure + /// + public class DisjointSetsBase where T : BasicNode + { + /// All nodes + protected readonly List Nodes_ = new List(); + + /// Creates an empty base + protected DisjointSetsBase() { } + + /// The number of nodes + public int Count => Nodes_.Count; + + /// The number of disjoint sets + public int SetsCount { get; protected set; } + + /// Finds a set identifier for the element + /// The element index + /// The identifier of the containing set + /// + /// The set identifier is the index of a single element representing the set. + /// The Union operation may lead to a choice of a different representative for a set. + /// In this case IndexToSetId(oldSetId) may be called to get the new set id. + /// + public int IndexToSetId(int index) + { + // First, find a root element of a tree containing the passed element + var rootIndex = index; + for (;;) + { + var parentIndex = Nodes_[rootIndex].ParentIndex; + if (parentIndex == -1) + { + break; + } + rootIndex = parentIndex; + } + + // Then, do the path compression: + // walk from the passed element upto the root replacing the the ParentIndex with the root index + while (index != rootIndex) + { + var node = Nodes_[index]; + index = node.ParentIndex; + node.ParentIndex = rootIndex; + } + return rootIndex; + } + + /// Combines to distjoint sets into a single set + /// Index of an element of the first set + /// Index of an element of the second set + public void Union(int elementOfSet1Index, int elementOfSet2Index) + { + elementOfSet1Index = IndexToSetId(elementOfSet1Index); + elementOfSet2Index = IndexToSetId(elementOfSet2Index); + if (elementOfSet1Index == elementOfSet2Index) + { + return; // Already the single set + } + + var set1Root = Nodes_[elementOfSet1Index]; + var set2Root = Nodes_[elementOfSet2Index]; + var rankDifference = set1Root.Rank - set2Root.Rank; + // Attach the tree with a smaller rank to the tree with a higher rank. + // The resulting tree rank is equal to the higher rank + // except the case when initial ranks are equal. + // In the latter case the new rank will be increased by 1 + if (rankDifference > 0) // 1st has higher rank + { + set2Root.ParentIndex = elementOfSet1Index; + } + else if (rankDifference < 0) // 2nd has the higher rank + { + set1Root.ParentIndex = elementOfSet2Index; + } + else // ranks are equal and the new root choice is arbitrary + { + set2Root.ParentIndex = elementOfSet1Index; + ++set1Root.Rank; + } + + // we have joined 2 sets, so we have to decrease the count + --SetsCount; + } + } + + /// Node base class + public class BasicNode + { + /// Parent node index + /// Points to the root after a path compression + public int ParentIndex; + + /// Estimated height of the tree (i.e. maximum length of the path from the root to a node. Path compression is not taken into account) + public int Rank; + } +} diff --git a/Main/src/Collections/DisjointSetsT.cs b/Main/src/Collections/DisjointSetsT.cs new file mode 100644 index 000000000..3b46ad3ba --- /dev/null +++ b/Main/src/Collections/DisjointSetsT.cs @@ -0,0 +1,52 @@ +using System.Collections.Generic; +using System.Linq; + +namespace CodeJam.Collections +{ + /// + /// Generic implementation of the Disjoint sets + /// + /// + /// See http://en.wikipedia.org/wiki/Disjoint-set_data_structure + /// + public sealed class DisjointSets : DisjointSetsBase.Node> + { + /// Creates an empty Disjoint sets + public DisjointSets() { } + + /// Creates a Disjoint sets with the passed values + /// The values to store + public DisjointSets(IEnumerable values) + { + Add(values); + } + + /// Gets an element by its index + /// Elmement's index + public T this[int index] => Nodes_[index].Value; + + /// Appends a list of values + /// The values to append + public void Add(IEnumerable values) + { + var initialNodesCount = Nodes_.Count; + Nodes_.AddRange(values.Select(_ => new Node { Value = _, ParentIndex = -1, Rank = 0 })); + SetsCount += Nodes_.Count - initialNodesCount; + } + + /// Appends a single element + /// The value to append + public void Add(T value) + { + Nodes_.Add(new Node { Value = value, ParentIndex = -1, Rank = 0 }); + ++SetsCount; + } + + /// A sets node + public class Node : BasicNode + { + /// The node data + public T Value; + } + } +} diff --git a/Main/tests/CodeJam.Main-Tests.csproj b/Main/tests/CodeJam.Main-Tests.csproj index 9e1a99331..988ecce31 100644 --- a/Main/tests/CodeJam.Main-Tests.csproj +++ b/Main/tests/CodeJam.Main-Tests.csproj @@ -59,6 +59,7 @@ + diff --git a/Main/tests/Collections/DisjointSetsTest.cs b/Main/tests/Collections/DisjointSetsTest.cs new file mode 100644 index 000000000..4c3dd846c --- /dev/null +++ b/Main/tests/Collections/DisjointSetsTest.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +using CodeJam.Collections; + +using NUnit.Framework; + +namespace Tests +{ + [TestFixture] + public class DisjointSetsTest + { + private readonly Random random_ = new Random(); + private const int ElementsNumber = 10000; + private readonly List seq_ = Enumerable.Range(0, ElementsNumber).ToList(); + + [Test] + public void Test01NonGeneric() + { + for (var i = 1; i <= ElementsNumber; i += 1 + i / (10 + random_.Next(0, 10))) + { + Console.WriteLine("i = {0}", i); + var djs = new DisjointSets(ElementsNumber); + foreach (var el in RandomShuffle(seq_)) + { + djs.Union(el, el % i); + } + VerifySets(djs, i); + } + } + + [Test] + public void Test02Generic() + { + for (var i = 1; i <= ElementsNumber; i += 1 + i / (10 + random_.Next(0, 10))) + { + Console.WriteLine("i = {0}", i); + var rs = RandomShuffle(seq_).ToList(); + var djs = new DisjointSets(rs); + foreach (var el in rs) + { + djs.Union(el, el % i); + } + VerifySets(djs, i); + for (var j = 0; j < ElementsNumber; ++j) + { + Assert.That(djs[j], Is.EqualTo(rs[j])); + } + } + } + + private static void VerifySets(DisjointSetsBase djs, int mod) where T : BasicNode + { + Assert.That(djs.Count, Is.EqualTo(ElementsNumber)); + Assert.That(djs.SetsCount, Is.EqualTo(mod)); + for (var i = 0; i < ElementsNumber; ++i) + { + Assert.That(djs.IndexToSetId(i), Is.EqualTo(djs.IndexToSetId(i % mod)), "i = {0}, mod = {1}", i, mod); + } + } + + private IEnumerable RandomShuffle(IEnumerable en) => en.OrderBy(x => random_.Next()); + } +}