TopSort for future multi-formula sort feature (#320)

* Initial work on a multi-formula sort feature

* Isolate TopSort as it's own change
This commit is contained in:
bryancroteau-MSFT 2022-04-18 16:09:50 -07:00 коммит произвёл GitHub
Родитель 8f4ee9629a
Коммит 3b8c6449fe
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
3 изменённых файлов: 238 добавлений и 0 удалений

Просмотреть файл

@ -0,0 +1,76 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
using System.Collections.Generic;
using System.Linq;
namespace Microsoft.PowerFx.Core.Utils
{
internal static class TopologicalSort
{
public static bool TrySort<T>(
IEnumerable<T> nodes,
IEnumerable<TopologicalSortEdge<T>> edges,
out IEnumerable<T> result,
out IEnumerable<T> cycles)
{
// Setup
var links = new Dictionary<T, List<T>>();
var counts = new Dictionary<T, int>();
foreach (var node in nodes)
{
counts.Add(node, 0);
links.Add(node, new List<T>());
}
foreach (var edge in edges)
{
if (!links.ContainsKey(edge.ProcessFirst) || !counts.ContainsKey(edge.ProcessSecond))
{
result = null;
cycles = null;
return false;
}
links[edge.ProcessFirst].Add(edge.ProcessSecond);
counts[edge.ProcessSecond] = counts[edge.ProcessSecond] + 1;
}
// Main Algorithm
var workingResult = new List<T>();
var noIncomingEdges = counts.Where(kvp => kvp.Value == 0).Select(kvp => kvp.Key).ToList();
while (counts.Count != 0 && noIncomingEdges.Count != 0)
{
foreach (var item in noIncomingEdges)
{
workingResult.Add(item);
counts.Remove(item);
var itemLinks = links[item];
foreach (var link in itemLinks)
{
counts[link] = counts[link] - 1;
}
}
noIncomingEdges = counts.Where(kvp => kvp.Value <= 0).Select(kvp => kvp.Key).ToList();
}
// Output
if (counts.Count == 0)
{
result = workingResult;
cycles = null;
return true;
}
else
{
result = null;
cycles = counts.Keys.ToList();
return false;
}
}
}
}

Просмотреть файл

@ -0,0 +1,17 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
namespace Microsoft.PowerFx.Core.Utils
{
internal class TopologicalSortEdge<T>
{
public readonly T ProcessFirst;
public readonly T ProcessSecond;
public TopologicalSortEdge(T processFirst, T processSecond)
{
ProcessFirst = processFirst;
ProcessSecond = processSecond;
}
}
}

Просмотреть файл

@ -0,0 +1,145 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
using System.Collections.Generic;
using System.Linq;
using Microsoft.PowerFx.Core.Utils;
using Xunit;
namespace Microsoft.PowerFx.Core.Tests
{
public class TopologicalSortTests : PowerFxTest
{
[Fact]
public void BasicOrderTest()
{
var nodes = new List<string>() { "f", "a", "d", "c", "b", "e" };
var edges = new List<TopologicalSortEdge<string>>()
{
new TopologicalSortEdge<string>("d", "f"),
new TopologicalSortEdge<string>("b", "d"),
new TopologicalSortEdge<string>("a", "b"),
new TopologicalSortEdge<string>("c", "e"),
new TopologicalSortEdge<string>("e", "f"),
new TopologicalSortEdge<string>("a", "c")
};
var success = TopologicalSort.TrySort(nodes, edges, out var result, out var cycles);
Assert.True(success);
Assert.NotNull(result);
Assert.Null(cycles);
var indexMap = new Dictionary<string, int>();
var index = 0;
foreach (var item in result)
{
indexMap[item] = index;
index += 1;
}
Assert.True(indexMap["a"] < indexMap["b"]);
Assert.True(indexMap["a"] < indexMap["c"]);
Assert.True(indexMap["b"] < indexMap["d"]);
Assert.True(indexMap["c"] < indexMap["e"]);
Assert.True(indexMap["d"] < indexMap["f"]);
Assert.True(indexMap["e"] < indexMap["f"]);
}
[Fact]
public void CycleTest()
{
var nodes = new List<string>() { "f", "a", "d", "c", "b", "e" };
var edges = new List<TopologicalSortEdge<string>>()
{
new TopologicalSortEdge<string>("d", "f"),
new TopologicalSortEdge<string>("b", "d"),
new TopologicalSortEdge<string>("a", "b"),
new TopologicalSortEdge<string>("c", "e"),
new TopologicalSortEdge<string>("e", "f"),
new TopologicalSortEdge<string>("a", "c"),
new TopologicalSortEdge<string>("f", "d")
};
var success = TopologicalSort.TrySort(nodes, edges, out var result, out var cycles);
Assert.False(success);
Assert.Null(result);
Assert.NotNull(cycles);
var set = new HashSet<string>(cycles);
Assert.True(set.Count == 2);
Assert.Contains("f", set);
Assert.Contains("d", set);
}
[Fact]
public void EmptyTest()
{
var nodes = new List<string>();
var edges = new List<TopologicalSortEdge<string>>();
var success = TopologicalSort.TrySort(nodes, edges, out var result, out var cycles);
Assert.True(success);
Assert.NotNull(result);
Assert.Null(cycles);
Assert.True(result.Count() == 0);
}
[Fact]
public void MissingNodeTest1()
{
var nodes = new List<string>() { "c", "a", "b" };
var edges = new List<TopologicalSortEdge<string>>()
{
new TopologicalSortEdge<string>("e", "a")
};
var success = TopologicalSort.TrySort(nodes, edges, out var result, out var cycles);
Assert.False(success);
Assert.Null(result);
Assert.Null(cycles);
}
[Fact]
public void MissingNodeTest2()
{
var nodes = new List<string>() { "c", "a", "b" };
var edges = new List<TopologicalSortEdge<string>>()
{
new TopologicalSortEdge<string>("a", "e")
};
var success = TopologicalSort.TrySort(nodes, edges, out var result, out var cycles);
Assert.False(success);
Assert.Null(result);
Assert.Null(cycles);
}
[Fact]
public void UnusedNodeTest()
{
var nodes = new List<string>() { "c", "a", "unused" };
var edges = new List<TopologicalSortEdge<string>>()
{
new TopologicalSortEdge<string>("a", "c")
};
var success = TopologicalSort.TrySort(nodes, edges, out var result, out var cycles);
Assert.True(success);
Assert.NotNull(result);
Assert.Null(cycles);
var indexMap = new Dictionary<string, int>();
var index = 0;
foreach (var item in result)
{
indexMap[item] = index;
index += 1;
}
Assert.True(indexMap.ContainsKey("unused"));
Assert.True(indexMap["unused"] < indexMap["c"]);
}
}
}