Skip to content

Commit a206abf

Browse files
committed
Merge pull request #12 from Lexey/master
Added DisjointSets and DisjointSets<T> collections
2 parents d306315 + fa3ce54 commit a206abf

File tree

6 files changed

+255
-0
lines changed

6 files changed

+255
-0
lines changed

Main/src/CodeJam.Main.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@
7474
<DesignTime>True</DesignTime>
7575
<DependentUpon>DebugCode.tt</DependentUpon>
7676
</Compile>
77+
<Compile Include="Collections\DisjointSets.cs" />
78+
<Compile Include="Collections\DisjointSetsBase.cs" />
79+
<Compile Include="Collections\DisjointSetsT.cs" />
7780
<Compile Include="Collections\EnumerableExtensions.Index.cs" />
7881
<Compile Include="Collections\IndexedItem.cs" />
7982
<Compile Include="Collections\EnumerableExtensions.Page.cs" />
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
namespace CodeJam.Collections
2+
{
3+
/// <summary>Disjoint sets without payload</summary>
4+
/// <remarks>
5+
/// See http://en.wikipedia.org/wiki/Disjoint-set_data_structure
6+
/// </remarks>
7+
public sealed class DisjointSets : DisjointSetsBase<BasicNode>
8+
{
9+
/// <summary>Creates an empty Disjoint sets</summary>
10+
public DisjointSets() { }
11+
12+
/// <summary>Creates a Disjoint sets with the given number of elements</summary>
13+
/// <param name="count">The initial number of elements</param>
14+
public DisjointSets(int count)
15+
{
16+
Add(count);
17+
}
18+
19+
/// <summary>Appends the given number of new elements</summary>
20+
/// <param name="count">The number of elements to add</param>
21+
public void Add(int count)
22+
{
23+
for (var i = 0; i < count; ++i)
24+
{
25+
Nodes_.Add(new BasicNode { ParentIndex = -1, Rank = 0 });
26+
}
27+
SetsCount += count;
28+
}
29+
}
30+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
using System.Collections.Generic;
2+
3+
namespace CodeJam.Collections
4+
{
5+
/// <summary>Disjoint sets implementation base</summary>
6+
/// <remarks>
7+
/// See http://en.wikipedia.org/wiki/Disjoint-set_data_structure
8+
/// </remarks>
9+
public class DisjointSetsBase<T> where T : BasicNode
10+
{
11+
/// <summary>All nodes</summary>
12+
protected readonly List<T> Nodes_ = new List<T>();
13+
14+
/// <summary>Creates an empty base</summary>
15+
protected DisjointSetsBase() { }
16+
17+
/// <summary>The number of nodes</summary>
18+
public int Count => Nodes_.Count;
19+
20+
/// <summary>The number of disjoint sets</summary>
21+
public int SetsCount { get; protected set; }
22+
23+
/// <summary>Finds a set identifier for the element</summary>
24+
/// <param name="index">The element index</param>
25+
/// <returns>The identifier of the containing set</returns>
26+
/// <remarks>
27+
/// The set identifier is the index of a single element representing the set.
28+
/// The Union operation may lead to a choice of a different representative for a set.
29+
/// In this case IndexToSetId(oldSetId) may be called to get the new set id.
30+
/// </remarks>
31+
public int IndexToSetId(int index)
32+
{
33+
// First, find a root element of a tree containing the passed element
34+
var rootIndex = index;
35+
for (;;)
36+
{
37+
var parentIndex = Nodes_[rootIndex].ParentIndex;
38+
if (parentIndex == -1)
39+
{
40+
break;
41+
}
42+
rootIndex = parentIndex;
43+
}
44+
45+
// Then, do the path compression:
46+
// walk from the passed element upto the root replacing the the ParentIndex with the root index
47+
while (index != rootIndex)
48+
{
49+
var node = Nodes_[index];
50+
index = node.ParentIndex;
51+
node.ParentIndex = rootIndex;
52+
}
53+
return rootIndex;
54+
}
55+
56+
/// <summary>Combines to distjoint sets into a single set</summary>
57+
/// <param name="elementOfSet1Index">Index of an element of the first set</param>
58+
/// <param name="elementOfSet2Index">Index of an element of the second set</param>
59+
public void Union(int elementOfSet1Index, int elementOfSet2Index)
60+
{
61+
elementOfSet1Index = IndexToSetId(elementOfSet1Index);
62+
elementOfSet2Index = IndexToSetId(elementOfSet2Index);
63+
if (elementOfSet1Index == elementOfSet2Index)
64+
{
65+
return; // Already the single set
66+
}
67+
68+
var set1Root = Nodes_[elementOfSet1Index];
69+
var set2Root = Nodes_[elementOfSet2Index];
70+
var rankDifference = set1Root.Rank - set2Root.Rank;
71+
// Attach the tree with a smaller rank to the tree with a higher rank.
72+
// The resulting tree rank is equal to the higher rank
73+
// except the case when initial ranks are equal.
74+
// In the latter case the new rank will be increased by 1
75+
if (rankDifference > 0) // 1st has higher rank
76+
{
77+
set2Root.ParentIndex = elementOfSet1Index;
78+
}
79+
else if (rankDifference < 0) // 2nd has the higher rank
80+
{
81+
set1Root.ParentIndex = elementOfSet2Index;
82+
}
83+
else // ranks are equal and the new root choice is arbitrary
84+
{
85+
set2Root.ParentIndex = elementOfSet1Index;
86+
++set1Root.Rank;
87+
}
88+
89+
// we have joined 2 sets, so we have to decrease the count
90+
--SetsCount;
91+
}
92+
}
93+
94+
/// <summary>Node base class</summary>
95+
public class BasicNode
96+
{
97+
/// <summary>Parent node index</summary>
98+
/// <remarks>Points to the root after a path compression</remarks>
99+
public int ParentIndex;
100+
101+
/// <summary>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)</summary>
102+
public int Rank;
103+
}
104+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
4+
namespace CodeJam.Collections
5+
{
6+
/// <summary>
7+
/// Generic implementation of the Disjoint sets
8+
/// </summary>
9+
/// <remarks>
10+
/// See http://en.wikipedia.org/wiki/Disjoint-set_data_structure
11+
/// </remarks>
12+
public sealed class DisjointSets<T> : DisjointSetsBase<DisjointSets<T>.Node>
13+
{
14+
/// <summary>Creates an empty Disjoint sets</summary>
15+
public DisjointSets() { }
16+
17+
/// <summary>Creates a Disjoint sets with the passed values</summary>
18+
/// <param name="values">The values to store</param>
19+
public DisjointSets(IEnumerable<T> values)
20+
{
21+
Add(values);
22+
}
23+
24+
/// <summary>Gets an element by its index</summary>
25+
/// <param name="index">Elmement's index</param>
26+
public T this[int index] => Nodes_[index].Value;
27+
28+
/// <summary>Appends a list of values</summary>
29+
/// <param name="values">The values to append</param>
30+
public void Add(IEnumerable<T> values)
31+
{
32+
var initialNodesCount = Nodes_.Count;
33+
Nodes_.AddRange(values.Select(_ => new Node { Value = _, ParentIndex = -1, Rank = 0 }));
34+
SetsCount += Nodes_.Count - initialNodesCount;
35+
}
36+
37+
/// <summary>Appends a single element</summary>
38+
/// <param name="value">The value to append</param>
39+
public void Add(T value)
40+
{
41+
Nodes_.Add(new Node { Value = value, ParentIndex = -1, Rank = 0 });
42+
++SetsCount;
43+
}
44+
45+
/// <summary>A sets node</summary>
46+
public class Node : BasicNode
47+
{
48+
/// <summary>The node data</summary>
49+
public T Value;
50+
}
51+
}
52+
}

Main/tests/CodeJam.Main-Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
<Compile Include="Assertions\CodeAssertionsTest.cs" />
6060
<Compile Include="Collections\AggregateFuncsTest.cs" />
6161
<Compile Include="Collections\ArrayExtensionsTest.cs" />
62+
<Compile Include="Collections\DisjointSetsTest.cs" />
6263
<Compile Include="Collections\QueryableExtensionsTests.ApplyOrder.cs" />
6364
<Compile Include="Collections\DictionaryExtensionsTest.cs" />
6465
<Compile Include="Collections\AggregateFuncsTest.generated.cs">
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
5+
using CodeJam.Collections;
6+
7+
using NUnit.Framework;
8+
9+
namespace Tests
10+
{
11+
[TestFixture]
12+
public class DisjointSetsTest
13+
{
14+
private readonly Random random_ = new Random();
15+
private const int ElementsNumber = 10000;
16+
private readonly List<int> seq_ = Enumerable.Range(0, ElementsNumber).ToList();
17+
18+
[Test]
19+
public void Test01NonGeneric()
20+
{
21+
for (var i = 1; i <= ElementsNumber; i += 1 + i / (10 + random_.Next(0, 10)))
22+
{
23+
Console.WriteLine("i = {0}", i);
24+
var djs = new DisjointSets(ElementsNumber);
25+
foreach (var el in RandomShuffle(seq_))
26+
{
27+
djs.Union(el, el % i);
28+
}
29+
VerifySets(djs, i);
30+
}
31+
}
32+
33+
[Test]
34+
public void Test02Generic()
35+
{
36+
for (var i = 1; i <= ElementsNumber; i += 1 + i / (10 + random_.Next(0, 10)))
37+
{
38+
Console.WriteLine("i = {0}", i);
39+
var rs = RandomShuffle(seq_).ToList();
40+
var djs = new DisjointSets<int>(rs);
41+
foreach (var el in rs)
42+
{
43+
djs.Union(el, el % i);
44+
}
45+
VerifySets(djs, i);
46+
for (var j = 0; j < ElementsNumber; ++j)
47+
{
48+
Assert.That(djs[j], Is.EqualTo(rs[j]));
49+
}
50+
}
51+
}
52+
53+
private static void VerifySets<T>(DisjointSetsBase<T> djs, int mod) where T : BasicNode
54+
{
55+
Assert.That(djs.Count, Is.EqualTo(ElementsNumber));
56+
Assert.That(djs.SetsCount, Is.EqualTo(mod));
57+
for (var i = 0; i < ElementsNumber; ++i)
58+
{
59+
Assert.That(djs.IndexToSetId(i), Is.EqualTo(djs.IndexToSetId(i % mod)), "i = {0}, mod = {1}", i, mod);
60+
}
61+
}
62+
63+
private IEnumerable<T> RandomShuffle<T>(IEnumerable<T> en) => en.OrderBy(x => random_.Next());
64+
}
65+
}

0 commit comments

Comments
 (0)