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());
+ }
+}