зеркало из https://github.com/microsoft/Power-Fx.git
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:
Родитель
8f4ee9629a
Коммит
3b8c6449fe
|
@ -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"]);
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче