优化获取下一个值列表功能

时间:2018-02-09 16:33:50

标签: c# list do-while

我们从数据库中检索对象列表,并且不能依赖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,并且这是一种更安全有效的方法。

有什么建议吗?谢谢!

3 个答案:

答案 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的条目,则会返回一个空列表。

更新更改为返回对象列表而不是索引。

希望这有帮助