我们从数据库中检索对象列表,并且不能依赖Id顺序来保证它们的顺序正确,因为对象可能已被编辑,删除等。
他们看起来像这样:
Id NextId
1 3
2 0
3 17
17 2
所以正确的顺序是1,3,17,2。
我想出了这个代码来解决问题:
long lastStep = steps.Single(x => x.NextId == 0).Id;
//Probably should be a guard clause for nulls
List<MyObject> orderedSteps = new List<MyObject>();
int retries = 0;
do
{
foreach (var entry in steps)
{
if (lastStep == entry.NextId) orderedSteps.Add(entry);
retries++;
}
} while (orderedSteps.Count() < steps.Count() && retries < 10000);
//Flip the order so it runs first to last
orderedSteps.Reverse();
return orderedSteps;
我认为这有效......但它感觉有点hacky,并且这是一种更安全有效的方法。
有什么建议吗?谢谢!
答案 0 :(得分:3)
您可以使用递归CTE直接在数据库中执行此操作:
WITH SequenceQuery (Id, NextId, Ordering)
AS
(
SELECT Id,
NextId,
0 AS Ordering
FROM Steps
WHERE Id = 1
UNION ALL
SELECT Steps.Id,
Steps.NextId,
SequenceQuery.Ordering + 1 AS Ordering
FROM SequenceQuery INNER JOIN Steps
ON SequenceQuery.NextId = Steps.Id
)
SELECT *
FROM SequenceQuery
ORDER BY Ordering
如果出现循环,一旦达到最大递归深度,就会返回错误。最大深度默认为100;如果您的数据集合法地拥有超过100个元素,则可以使用以下子句增加限制(在查询结束时,在SELECT
语句之后):
OPTION (MAXRECURSION 1000) -- (for example)
这是获取数据的最快方法,前提是Id
列已正确编入索引。
如果您更喜欢在代码中执行此操作,那么您需要事先将整个表加载到字典中,然后再遍历它。这样做的好处是,您可以显式检测周期,而不是取决于级别数量的数字限制。
var steps = ...;
var stepById = steps.ToDictionary(step => step.Id);
var stepsInOrder = new List<int>();
var visited = new HashSet<int>();
// Make sure that when we hit 0, we'll definitely stop.
Debug.Assert(!stepsInOrder.ContainsKey(0));
int currentStepId = 1;
while (stepById.TryGetValue(currentStepId, out Step step))
{
stepsInOrder.Add(currentStepId);
int nextStepId = step.NextId;
if (!visited.Add(nextStepId))
throw new Exception($"Cycle found at step {nextStepId}");
currentStepId = nextStepId;
}
(经过SQL测试,C#代码未经测试)
答案 1 :(得分:2)
这是我的解决方案。需要几个假设:单链,以0 Id终止。
public class Item
{
public int Id;
public int NextId;
public override string ToString()
{
return string.Format("Item {0} (links to {1})", Id, NextId);
}
};
class Program
{
static void Main(string[] args)
{
Item[] items = new Item[] {
new Item() { Id = 1, NextId = 3 },
new Item() { Id = 2, NextId = 0 },
new Item() { Id = 3, NextId = 17 },
new Item() { Id = 17, NextId = 2 }
};
Dictionary<int, int> idToIndex = new Dictionary<int, int>();
int headId = 0;
for (int index = 0; index < items.Length; ++index)
{
idToIndex.Add(items[index].Id, index);
headId = headId ^ items[index].NextId ^ items[index].Id;
}
int currentId = headId;
while (currentId != 0)
{
var item = items[idToIndex[currentId]];
Console.WriteLine(item);
currentId = item.NextId;
}
}
}
答案 2 :(得分:1)
我的建议如下:
class MyObject
{
public long Id;
public long NextId;
public override string ToString() => Id.ToString();
};
public void q48710242()
{
var items = new[]
{
new MyObject{ Id = 1, NextId = 3 },
new MyObject{ Id = 2, NextId = 0 },
new MyObject{ Id = 3, NextId = 17 },
new MyObject{ Id = 17, NextId = 2 }
};
var nextIdIndex = items.ToDictionary(item => item.NextId);
var orderedSteps = new List<MyObject>();
var currentStep = new MyObject() { Id = 0 };
while (nextIdIndex.TryGetValue(currentStep.Id, out currentStep))
{
orderedSteps.Add(currentStep);
}
orderedSteps.Reverse();
var output = string.Join(", ", orderedSteps);
}
返回:
output =“1,3,17,2”
这使用字典来构建项目的索引,如Jonathan的答案,但是使用NextId
作为关键字。然后算法从原始问题中的0向后返回以反向构建列表。这种方法对数据中的循环没有问题,因为任何这样的循环都不会输入,假设Id
是唯一的。
如果数据包含具有相同NextId的多个元素,则它形成树结构:
var items = new[]
{
new { Id = 1, NextId = 3 },
new { Id = 2, NextId = 0 },
new { Id = 3, NextId = 17 },
new { Id = 17, NextId = 2 },
new { Id = 100, NextId = 2 }
};
这会导致.ToDictionary()
调用失败并显示System.ArgumentException: An item with the same key has already been added.
如果数据中不包含NextId
等于0的条目,则会返回一个空列表。
更新更改为返回对象列表而不是索引。
希望这有帮助