diff --git a/ICD.Common.Utils.Tests/RecursionUtilsTest.cs b/ICD.Common.Utils.Tests/RecursionUtilsTest.cs index e87e74c..f64c5f2 100644 --- a/ICD.Common.Utils.Tests/RecursionUtilsTest.cs +++ b/ICD.Common.Utils.Tests/RecursionUtilsTest.cs @@ -8,7 +8,7 @@ namespace ICD.Common.Utils.Tests [TestFixture] public sealed class RecursionUtilsTest { - private IEnumerable Graph(int node) + private static IEnumerable Graph(int node) { switch (node) { @@ -26,9 +26,56 @@ namespace ICD.Common.Utils.Tests } } + private static IEnumerable WideGraph(int node) + { + switch (node) + { + case 1: + yield return 2; + yield return 3; + yield return 4; + yield return 5; + yield return 6; + break; + case 2: + yield return 21; + yield return 22; + break; + case 3: + yield return 1; + yield return 31; + yield return 32; + break; + case 4: + yield return 41; + yield return 42; + break; + case 5: + yield return 51; + yield return 52; + break; + case 6: + yield return 61; + yield return 62; + break; + case 21: + yield return 62; + break; + case 41: + yield return 43; + break; + case 42: + yield return 43; + break; + default: + yield break; + } + } + [Test] public void BreadthFirstSearchTest() { + // ReSharper disable once ReturnValueOfPureMethodIsNotUsed Assert.Throws(() => RecursionUtils.BreadthFirstSearch(1, null).ToArray()); int[] nodes = RecursionUtils.BreadthFirstSearch(1, Graph).ToArray(); @@ -43,8 +90,11 @@ namespace ICD.Common.Utils.Tests [Test] public void BreadthFirstSearchPathTest() { - Assert.Throws(() => RecursionUtils.BreadthFirstSearchPath(1, 4, null).ToArray()); + Assert.Throws(() => RecursionUtils.BreadthFirstSearchPath(1, 4, null)); + Assert.IsNull(RecursionUtils.BreadthFirstSearchPath(1, 5, Graph)); + + // ReSharper disable once AssignNullToNotNullAttribute int[] path = RecursionUtils.BreadthFirstSearchPath(1, 4, Graph).ToArray(); Assert.AreEqual(3, path.Length); @@ -63,10 +113,67 @@ namespace ICD.Common.Utils.Tests [Test] public void BreadthFirstSearchPathSingleNodeTest() { + // ReSharper disable once AssignNullToNotNullAttribute int[] path = RecursionUtils.BreadthFirstSearchPath(1, 1, Graph).ToArray(); Assert.AreEqual(1, path.Length); Assert.AreEqual(1, path[0]); } + + [Test] + public void BreadthFirstSearchManyDestinationsTest() + { + Assert.Throws(() => RecursionUtils.BreadthFirstSearchManyDestinations(1, new[] { 1 }, null)); + Assert.Throws(() => RecursionUtils.BreadthFirstSearchManyDestinations(1, null, Graph)); + Assert.IsEmpty(RecursionUtils.BreadthFirstSearchManyDestinations(1, new[] { 5 }, Graph)); + + Dictionary> paths = RecursionUtils.BreadthFirstSearchManyDestinations(1, new[] { 21, 22, 31, 43, 62 }, WideGraph); + + //Make sure all paths were found + Assert.IsTrue(paths.Keys.Contains(21)); + Assert.IsTrue(paths.Keys.Contains(22)); + Assert.IsTrue(paths.Keys.Contains(31)); + Assert.IsTrue(paths.Keys.Contains(43)); + Assert.IsTrue(paths.Keys.Contains(62)); + + //Make sure the shortest paths are taken + Assert.AreEqual(3, paths[21].Count()); + Assert.AreEqual(3, paths[22].Count()); + Assert.AreEqual(3, paths[31].Count()); // infinite loop exists between 1 and 3 + Assert.AreEqual(4, paths[43].Count()); // 43 has two parents of equal distance, 41 and 42 + Assert.AreEqual(3, paths[62].Count()); // two paths exist, one is 3, one is 4 + + // make sure that destinations which were not asked for were not returned + Assert.IsFalse(paths.Keys.Contains(1)); + Assert.IsFalse(paths.Keys.Contains(2)); + Assert.IsFalse(paths.Keys.Contains(3)); + Assert.IsFalse(paths.Keys.Contains(32)); + + //Verify path for destination 21 + Assert.AreEqual(1, paths[21].ToArray()[0]); + Assert.AreEqual(2, paths[21].ToArray()[1]); + Assert.AreEqual(21, paths[21].ToArray()[2]); + + //Verify path for destination 22 + Assert.AreEqual(1, paths[22].ToArray()[0]); + Assert.AreEqual(2, paths[22].ToArray()[1]); + Assert.AreEqual(22, paths[22].ToArray()[2]); + + //Verify path for destination 31 + Assert.AreEqual(1, paths[31].ToArray()[0]); + Assert.AreEqual(3, paths[31].ToArray()[1]); + Assert.AreEqual(31, paths[31].ToArray()[2]); + + //Verify path for destination 43 + Assert.AreEqual(1, paths[43].ToArray()[0]); + Assert.AreEqual(4, paths[43].ToArray()[1]); + Assert.AreEqual(41, paths[43].ToArray()[2]); // when multiple parents exist with valid paths back, the first one should be consistently selected + Assert.AreEqual(43, paths[43].ToArray()[3]); + + //Verify path for destination 62 + Assert.AreEqual(1, paths[62].ToArray()[0]); + Assert.AreEqual(6, paths[62].ToArray()[1]); + Assert.AreEqual(62, paths[62].ToArray()[2]); + } } } diff --git a/ICD.Common.Utils.Tests/StringUtilsTest.cs b/ICD.Common.Utils.Tests/StringUtilsTest.cs index 0a13c41..c819051 100644 --- a/ICD.Common.Utils.Tests/StringUtilsTest.cs +++ b/ICD.Common.Utils.Tests/StringUtilsTest.cs @@ -91,5 +91,77 @@ namespace ICD.Common.Utils.Tests { Assert.AreEqual(StringUtils.IsNullOrWhitespace(value), expectedResult); } + + [Test, UsedImplicitly] + public void TryParseIntTest() + { + int testVal; + Assert.IsFalse(StringUtils.TryParse("fish", out testVal)); + Assert.IsTrue(StringUtils.TryParse("1", out testVal)); + Assert.AreEqual(1, testVal); + } + + [Test, UsedImplicitly] + public void TryParseUintTest() + { + uint testVal; + Assert.IsFalse(StringUtils.TryParse("fish", out testVal)); + Assert.IsTrue(StringUtils.TryParse("1", out testVal)); + Assert.AreEqual((uint)1, testVal); + } + + [Test, UsedImplicitly] + public void TryParseShortTest() + { + short testVal; + Assert.IsFalse(StringUtils.TryParse("fish", out testVal)); + Assert.IsTrue(StringUtils.TryParse("1", out testVal)); + Assert.AreEqual((short)1, testVal); + } + + [Test, UsedImplicitly] + public void TryParseUshortTest() + { + ushort testVal; + Assert.IsFalse(StringUtils.TryParse("fish", out testVal)); + Assert.IsTrue(StringUtils.TryParse("1", out testVal)); + Assert.AreEqual((ushort)1, testVal); + } + + [Test, UsedImplicitly] + public void TryParseLongTest() + { + long testVal; + Assert.IsFalse(StringUtils.TryParse("fish", out testVal)); + Assert.IsTrue(StringUtils.TryParse("1", out testVal)); + Assert.AreEqual((long)1, testVal); + } + + [Test, UsedImplicitly] + public void TryParseUlongTest() + { + ulong testVal; + Assert.IsFalse(StringUtils.TryParse("fish", out testVal)); + Assert.IsTrue(StringUtils.TryParse("1", out testVal)); + Assert.AreEqual((ulong)1, testVal); + } + + [Test, UsedImplicitly] + public void TryParseFloatTest() + { + float testVal; + Assert.IsFalse(StringUtils.TryParse("fish", out testVal)); + Assert.IsTrue(StringUtils.TryParse("1.1", out testVal)); + Assert.AreEqual((float)1.1, testVal); + } + + [Test, UsedImplicitly] + public void TryParseBoolTest() + { + bool testVal; + Assert.IsFalse(StringUtils.TryParse("fish", out testVal)); + Assert.IsTrue(StringUtils.TryParse("true", out testVal)); + Assert.AreEqual(true, testVal); + } } } diff --git a/ICD.Common.Utils/IcdConsole.cs b/ICD.Common.Utils/IcdConsole.cs index bcbf993..2745f32 100644 --- a/ICD.Common.Utils/IcdConsole.cs +++ b/ICD.Common.Utils/IcdConsole.cs @@ -87,7 +87,6 @@ namespace ICD.Common.Utils #if SIMPLSHARP return CrestronConsole.SendControlSystemCommand(command, ref result); #else - result = string.Empty; return false; #endif } diff --git a/ICD.Common.Utils/Json/AbstractGenericJsonConverter.cs b/ICD.Common.Utils/Json/AbstractGenericJsonConverter.cs index 9f3e44a..900b1ce 100644 --- a/ICD.Common.Utils/Json/AbstractGenericJsonConverter.cs +++ b/ICD.Common.Utils/Json/AbstractGenericJsonConverter.cs @@ -12,7 +12,7 @@ namespace ICD.Common.Utils.Json /// The to write to. /// The value. /// The calling serializer. - public override sealed void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + public sealed override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { if (value == null) { @@ -42,7 +42,7 @@ namespace ICD.Common.Utils.Json /// /// The object value. /// - public override sealed object ReadJson(JsonReader reader, Type objectType, object existingValue, + public sealed override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { if (reader.TokenType == JsonToken.Null) diff --git a/ICD.Common.Utils/RecursionUtils.cs b/ICD.Common.Utils/RecursionUtils.cs index fd491f4..f7abb4e 100644 --- a/ICD.Common.Utils/RecursionUtils.cs +++ b/ICD.Common.Utils/RecursionUtils.cs @@ -92,27 +92,115 @@ namespace ICD.Common.Utils // Found a path to the destination if (comparer.Equals(node, destination)) - return GetPath(destination, nodeParents).Reverse(); + return GetPath(destination, root, nodeParents, comparer).Reverse(); } } return null; } + [NotNull] + public static Dictionary> BreadthFirstSearchManyDestinations(T root, + IEnumerable destinations, + Func> + getChildren) + { + if (getChildren == null) + throw new ArgumentNullException("getChildren"); + + return BreadthFirstSearchPathManyDestinations(root, destinations, getChildren, EqualityComparer.Default); + } + + [NotNull] + public static Dictionary> BreadthFirstSearchPathManyDestinations(T root, + IEnumerable + destinations, + Func> + getChildren, + IEqualityComparer + comparer) + { + if (destinations == null) + throw new ArgumentNullException("destinations"); + + if (getChildren == null) + throw new ArgumentNullException("getChildren"); + + if (comparer == null) + throw new ArgumentNullException("comparer"); + + IcdHashSet destinationsToBeProcessed = new IcdHashSet(destinations); + IcdHashSet destinationsProcessed = new IcdHashSet(); + Dictionary> pathsToReturn = new Dictionary>(); + + // Edge case, root is the destination + foreach (T destination in + destinationsToBeProcessed.Where(destination => comparer.Equals(root, destination))) + { + destinationsProcessed.Add(destination); + pathsToReturn.Add(destination, new[] {root}); + } + + foreach (T destination in destinationsProcessed) + destinationsToBeProcessed.Remove(destination); + destinationsProcessed.Clear(); + if (destinationsToBeProcessed.Count == 0) + return pathsToReturn; + + Queue queue = new Queue(); + queue.Enqueue(root); + + Dictionary nodeParents = new Dictionary(); + + while (queue.Count > 0) + { + T current = queue.Dequeue(); + + foreach (T node in getChildren(current).Where(node => !nodeParents.ContainsKey(node))) + { + queue.Enqueue(node); + nodeParents.Add(node, current); + + T closureNode = node; + foreach (T destination in + destinationsToBeProcessed.Where(destination => comparer.Equals(closureNode, destination))) + { + destinationsProcessed.Add(destination); + pathsToReturn.Add(destination, GetPath(destination, root, nodeParents, comparer).Reverse()); + } + + foreach (T destination in destinationsProcessed) + destinationsToBeProcessed.Remove(destination); + + destinationsProcessed.Clear(); + + if (destinationsToBeProcessed.Count == 0) + return pathsToReturn; + } + } + + return pathsToReturn; + } + /// /// Walks through a map of nodes from the starting point. /// /// /// + /// /// /// /// - private static IEnumerable GetPath(T start, IDictionary nodeParents) + private static IEnumerable GetPath(T start, T end, IDictionary nodeParents, IEqualityComparer comparer) { IcdHashSet visited = new IcdHashSet(); while (true) { yield return start; + + if (comparer.Equals(start, end)) + break; + visited.Add(start); T next; diff --git a/ICD.Common.Utils/StringUtils.cs b/ICD.Common.Utils/StringUtils.cs index 8d8c0c8..df05254 100644 --- a/ICD.Common.Utils/StringUtils.cs +++ b/ICD.Common.Utils/StringUtils.cs @@ -191,6 +191,54 @@ namespace ICD.Common.Utils return TryConvert(Convert.ToUInt32, value, out result); } + /// + /// Attempts to parse the string as a short integer. + /// + /// + /// + /// + [PublicAPI] + public static bool TryParse(string value, out short result) + { + return TryConvert(Convert.ToInt16, value, out result); + } + + /// + /// Attempts to parse the string as an unsigned short integer. + /// + /// + /// + /// + [PublicAPI] + public static bool TryParse(string value, out ushort result) + { + return TryConvert(Convert.ToUInt16, value, out result); + } + + /// + /// Attempts to parse the string as a long integer. + /// + /// + /// + /// + [PublicAPI] + public static bool TryParse(string value, out long result) + { + return TryConvert(Convert.ToInt64, value, out result); + } + + /// + /// Attempts to parse the string as an unsigned long integer. + /// + /// + /// + /// + [PublicAPI] + public static bool TryParse(string value, out ulong result) + { + return TryConvert(Convert.ToUInt64, value, out result); + } + /// /// Attempts to parse the string as a float. ///