Skip to content

Commit 52e4ef3

Browse files
authored
Add Articulation Points algorithm (#574)
1 parent 9cf588d commit 52e4ef3

File tree

2 files changed

+503
-0
lines changed

2 files changed

+503
-0
lines changed
Lines changed: 326 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,326 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using Algorithms.Graph;
5+
using FluentAssertions;
6+
using NUnit.Framework;
7+
8+
namespace Algorithms.Tests.Graph;
9+
10+
public class ArticulationPointsTests
11+
{
12+
[Test]
13+
public void Find_SimpleChain_ReturnsMiddleVertex()
14+
{
15+
// Arrange: A - B - C (B is articulation point)
16+
var vertices = new[] { "A", "B", "C" };
17+
IEnumerable<string> GetNeighbors(string v) => v switch
18+
{
19+
"A" => new[] { "B" },
20+
"B" => new[] { "A", "C" },
21+
"C" => new[] { "B" },
22+
_ => Array.Empty<string>(),
23+
};
24+
25+
// Act
26+
var result = ArticulationPoints.Find(vertices, GetNeighbors);
27+
28+
// Assert
29+
result.Should().ContainSingle();
30+
result.Should().Contain("B");
31+
}
32+
33+
[Test]
34+
public void Find_Triangle_ReturnsEmpty()
35+
{
36+
// Arrange: A - B - C - A (no articulation points)
37+
var vertices = new[] { "A", "B", "C" };
38+
IEnumerable<string> GetNeighbors(string v) => v switch
39+
{
40+
"A" => new[] { "B", "C" },
41+
"B" => new[] { "A", "C" },
42+
"C" => new[] { "A", "B" },
43+
_ => Array.Empty<string>(),
44+
};
45+
46+
// Act
47+
var result = ArticulationPoints.Find(vertices, GetNeighbors);
48+
49+
// Assert
50+
result.Should().BeEmpty();
51+
}
52+
53+
[Test]
54+
public void Find_StarGraph_ReturnsCenterVertex()
55+
{
56+
// Arrange: Star with center A
57+
var vertices = new[] { "A", "B", "C", "D" };
58+
IEnumerable<string> GetNeighbors(string v) => v switch
59+
{
60+
"A" => new[] { "B", "C", "D" },
61+
"B" => new[] { "A" },
62+
"C" => new[] { "A" },
63+
"D" => new[] { "A" },
64+
_ => Array.Empty<string>(),
65+
};
66+
67+
// Act
68+
var result = ArticulationPoints.Find(vertices, GetNeighbors);
69+
70+
// Assert
71+
result.Should().ContainSingle();
72+
result.Should().Contain("A");
73+
}
74+
75+
[Test]
76+
public void Find_BridgeGraph_ReturnsMultiplePoints()
77+
{
78+
// Arrange: (A-B-C) - D - (E-F-G)
79+
var vertices = new[] { "A", "B", "C", "D", "E", "F", "G" };
80+
IEnumerable<string> GetNeighbors(string v) => v switch
81+
{
82+
"A" => new[] { "B" },
83+
"B" => new[] { "A", "C", "D" },
84+
"C" => new[] { "B" },
85+
"D" => new[] { "B", "E" },
86+
"E" => new[] { "D", "F" },
87+
"F" => new[] { "E", "G" },
88+
"G" => new[] { "F" },
89+
_ => Array.Empty<string>(),
90+
};
91+
92+
// Act
93+
var result = ArticulationPoints.Find(vertices, GetNeighbors);
94+
95+
// Assert
96+
result.Should().HaveCount(4);
97+
result.Should().Contain(new[] { "B", "D", "E", "F" });
98+
}
99+
100+
[Test]
101+
public void Find_DisconnectedGraph_FindsPointsInEachComponent()
102+
{
103+
// Arrange: (A-B-C) and (D-E-F)
104+
var vertices = new[] { "A", "B", "C", "D", "E", "F" };
105+
IEnumerable<string> GetNeighbors(string v) => v switch
106+
{
107+
"A" => new[] { "B" },
108+
"B" => new[] { "A", "C" },
109+
"C" => new[] { "B" },
110+
"D" => new[] { "E" },
111+
"E" => new[] { "D", "F" },
112+
"F" => new[] { "E" },
113+
_ => Array.Empty<string>(),
114+
};
115+
116+
// Act
117+
var result = ArticulationPoints.Find(vertices, GetNeighbors);
118+
119+
// Assert
120+
result.Should().HaveCount(2);
121+
result.Should().Contain(new[] { "B", "E" });
122+
}
123+
124+
[Test]
125+
public void Find_SingleVertex_ReturnsEmpty()
126+
{
127+
// Arrange
128+
var vertices = new[] { "A" };
129+
IEnumerable<string> GetNeighbors(string v) => Array.Empty<string>();
130+
131+
// Act
132+
var result = ArticulationPoints.Find(vertices, GetNeighbors);
133+
134+
// Assert
135+
result.Should().BeEmpty();
136+
}
137+
138+
[Test]
139+
public void Find_TwoVertices_ReturnsEmpty()
140+
{
141+
// Arrange: A - B
142+
var vertices = new[] { "A", "B" };
143+
IEnumerable<string> GetNeighbors(string v) => v switch
144+
{
145+
"A" => new[] { "B" },
146+
"B" => new[] { "A" },
147+
_ => Array.Empty<string>(),
148+
};
149+
150+
// Act
151+
var result = ArticulationPoints.Find(vertices, GetNeighbors);
152+
153+
// Assert
154+
result.Should().BeEmpty();
155+
}
156+
157+
[Test]
158+
public void Find_ComplexGraph_ReturnsCorrectPoints()
159+
{
160+
// Arrange: Complex graph with multiple articulation points
161+
var vertices = new[] { 1, 2, 3, 4, 5, 6, 7 };
162+
IEnumerable<int> GetNeighbors(int v) => v switch
163+
{
164+
1 => new[] { 2, 3 },
165+
2 => new[] { 1, 3 },
166+
3 => new[] { 1, 2, 4 },
167+
4 => new[] { 3, 5, 6 },
168+
5 => new[] { 4, 6 },
169+
6 => new[] { 4, 5, 7 },
170+
7 => new[] { 6 },
171+
_ => Array.Empty<int>(),
172+
};
173+
174+
// Act
175+
var result = ArticulationPoints.Find(vertices, GetNeighbors);
176+
177+
// Assert
178+
result.Should().Contain(new[] { 3, 4, 6 });
179+
}
180+
181+
[Test]
182+
public void Find_EmptyGraph_ReturnsEmpty()
183+
{
184+
// Arrange
185+
var vertices = Array.Empty<string>();
186+
IEnumerable<string> GetNeighbors(string v) => Array.Empty<string>();
187+
188+
// Act
189+
var result = ArticulationPoints.Find(vertices, GetNeighbors);
190+
191+
// Assert
192+
result.Should().BeEmpty();
193+
}
194+
195+
[Test]
196+
public void Find_NullVertices_ThrowsArgumentNullException()
197+
{
198+
// Act
199+
Action act = () => ArticulationPoints.Find<string>(null!, v => Array.Empty<string>());
200+
201+
// Assert
202+
act.Should().Throw<ArgumentNullException>().WithParameterName("vertices");
203+
}
204+
205+
[Test]
206+
public void Find_NullGetNeighbors_ThrowsArgumentNullException()
207+
{
208+
// Arrange
209+
var vertices = new[] { "A" };
210+
211+
// Act
212+
Action act = () => ArticulationPoints.Find(vertices, null!);
213+
214+
// Assert
215+
act.Should().Throw<ArgumentNullException>().WithParameterName("getNeighbors");
216+
}
217+
218+
[Test]
219+
public void IsArticulationPoint_ValidPoint_ReturnsTrue()
220+
{
221+
// Arrange: A - B - C
222+
var vertices = new[] { "A", "B", "C" };
223+
IEnumerable<string> GetNeighbors(string v) => v switch
224+
{
225+
"A" => new[] { "B" },
226+
"B" => new[] { "A", "C" },
227+
"C" => new[] { "B" },
228+
_ => Array.Empty<string>(),
229+
};
230+
231+
// Act
232+
var result = ArticulationPoints.IsArticulationPoint("B", vertices, GetNeighbors);
233+
234+
// Assert
235+
result.Should().BeTrue();
236+
}
237+
238+
[Test]
239+
public void IsArticulationPoint_NotArticulationPoint_ReturnsFalse()
240+
{
241+
// Arrange: A - B - C
242+
var vertices = new[] { "A", "B", "C" };
243+
IEnumerable<string> GetNeighbors(string v) => v switch
244+
{
245+
"A" => new[] { "B" },
246+
"B" => new[] { "A", "C" },
247+
"C" => new[] { "B" },
248+
_ => Array.Empty<string>(),
249+
};
250+
251+
// Act
252+
var result = ArticulationPoints.IsArticulationPoint("A", vertices, GetNeighbors);
253+
254+
// Assert
255+
result.Should().BeFalse();
256+
}
257+
258+
[Test]
259+
public void Count_SimpleChain_ReturnsOne()
260+
{
261+
// Arrange: A - B - C
262+
var vertices = new[] { "A", "B", "C" };
263+
IEnumerable<string> GetNeighbors(string v) => v switch
264+
{
265+
"A" => new[] { "B" },
266+
"B" => new[] { "A", "C" },
267+
"C" => new[] { "B" },
268+
_ => Array.Empty<string>(),
269+
};
270+
271+
// Act
272+
var result = ArticulationPoints.Count(vertices, GetNeighbors);
273+
274+
// Assert
275+
result.Should().Be(1);
276+
}
277+
278+
[Test]
279+
public void Count_Triangle_ReturnsZero()
280+
{
281+
// Arrange: A - B - C - A
282+
var vertices = new[] { "A", "B", "C" };
283+
IEnumerable<string> GetNeighbors(string v) => v switch
284+
{
285+
"A" => new[] { "B", "C" },
286+
"B" => new[] { "A", "C" },
287+
"C" => new[] { "A", "B" },
288+
_ => Array.Empty<string>(),
289+
};
290+
291+
// Act
292+
var result = ArticulationPoints.Count(vertices, GetNeighbors);
293+
294+
// Assert
295+
result.Should().Be(0);
296+
}
297+
298+
[Test]
299+
public void Find_LargeGraph_FindsAllPoints()
300+
{
301+
// Arrange: Large chain
302+
var vertices = Enumerable.Range(1, 10).ToArray();
303+
IEnumerable<int> GetNeighbors(int v)
304+
{
305+
var neighbors = new List<int>();
306+
if (v > 1)
307+
{
308+
neighbors.Add(v - 1);
309+
}
310+
311+
if (v < 10)
312+
{
313+
neighbors.Add(v + 1);
314+
}
315+
316+
return neighbors;
317+
}
318+
319+
// Act
320+
var result = ArticulationPoints.Find(vertices, GetNeighbors);
321+
322+
// Assert
323+
result.Should().HaveCount(8); // All except endpoints
324+
result.Should().NotContain(new[] { 1, 10 });
325+
}
326+
}

0 commit comments

Comments
 (0)