我正在尝试提供一种有效的解决方案,以便能够查询实体列表并正确排序。我在SQL DB模式中创建了一个单链表类型结构。我使用GUID作为我的ID,但为了简单起见,我将在这里使用int。我可以通过在DB上安装SortOrder列来轻松解决此问题,但由于其他要求,我必须实现此表。
我有一个类似于以下实体模型的表结构:
public class Record
{
public int ID;
public string Name;
public int? ChildID; //References the next record
}
我最初的想法是创建一个如下所示的部分类:
public partial class Record
{
public int SortOrder
{
get
{
//query table and loop through the entire list building it from
//the bottom and keeping count of the integer sort order and return
//the one specific to this record
}
}
}
但是,每次查询整个列表并迭代查找SortOrder似乎效率非常低。还有什么我可以像定制的OrderBy函数或任何东西一样利用?我正在尝试按迭代构建列表时创建的顺序排序。例如,ChildID = null的记录是列表中的最后一个记录,因为它没有子记录。我将从该记录开始,然后获取它上面的下一个记录,它引用前一个作为其ChildID,直到列表中没有其他ID引用,这应该是列表完成并正确排序。没有两个记录具有相同的ChildID。
如果我在列表中有以下3条记录,
ID = 3, Name = "Apple", ChildID = 6,
ID = 54, Name = "Orange", ChildID = 3,
ID = 6, Name = "Banana", ChildID = null
然后我希望按顺序获得Orange,Apple,Banana。
答案 0 :(得分:1)
一种方法是编写一个按排序顺序返回列表的方法。您首先会找到ChildId == null
的记录,将其添加到结果列表,然后继续搜索item.ChildId == previousItem.Id
的项目,然后将它们插入列表的开头:
private static IEnumerable<Record> OrderRecords(IReadOnlyCollection<Record> records)
{
// "Exit fast" checks
if (records == null) return null;
if (records.Count < 2) return records.ToList();
// Get last record and add it to our results
Record currentRecord = records.Single(r => r.ChildID == null);
var results = new List<Record> {currentRecord};
// Keep getting the parent reference to the previous record
// and insert it at the beginning of the results list
while ((currentRecord = records.SingleOrDefault(r =>
r.ChildID == currentRecord.ID)) != null)
{
results.Insert(0, currentRecord);
}
return results;
}
在使用中,这看起来像是:
private static void Main()
{
var records = new List<Record>
{
new Record {ID = 3, Name = "Apple", ChildID = 6},
new Record {ID = 54, Name = "Orange", ChildID = 3},
new Record {ID = 6, Name = "Banana", ChildID = null}
};
var sortedRecords = OrderRecords(records);
Console.WriteLine(string.Join(", ", sortedRecords.Select(r => r.Name)));
Console.Write("\nPress any key to exit...");
Console.ReadKey();
}
<强>输出强>
答案 1 :(得分:1)
鉴于记录ID顺序是随机的,并假设您订购的List
已完成,或者如果您必须扫描整个表以订购,则不会耗尽内存/时间列表,我认为您可以做的最好的事情是计算Record
的深度并缓存结果:
我使用List
作为表,但如果您要订购的列表不完整,则可以使用该表:
public partial class Record {
static Dictionary<int, int> depth = new Dictionary<int, int>();
public int Depth(List<Record> dbTable) {
int ans = 0;
var working = new Queue<int>();
var cur = this;
do {
if (depth.TryGetValue(cur.ID, out var curDepth)) {
ans += curDepth;
break;
}
else {
working.Enqueue(cur.ID);
cur = dbTable.FirstOrDefault(r => r.ChildID == cur.ID);
if (cur != null)
++ans;
}
} while (cur != null);
var workAns = ans;
while (working.Count > 0) {
var workingID = working.Dequeue();
depth.Add(workingID, workAns);
--workAns;
}
return ans;
}
}
更新:我重新编写代码以使用特定队列;我的第一个版本是递归的,这是直截了当的,但有可能溢出堆栈,我的第二个版本没有缓存中间结果时跟随链表不是很有效。使用中间ID的队列确保我只跟踪一次特定的链深度。
现在你有Depth
方法,排序很简单:
var ans = work.OrderBy(w => w.Depth(work));
答案 2 :(得分:1)
此任务的最佳算法是按Dictionary
准备Record
的快速查找数据结构(如ChildID
)。然后,可以从ChildID = null
开始向后生成有序结果,并使用记录ID
查找上一条记录。
由于哈希查找时间复杂度为O(1),因此算法的时间复杂度为线性O(N) - 最快。
以下是实施:
static Record[] Ordered(IEnumerable<Record> records)
{
var recordByNextId = records.ToDictionary(e => e.ChildID.Wrap());
var result = new Record[recordByNextId.Count];
int? nextId = null;
for (int i = result.Length - 1; i >=0; i--)
nextId = (result[i] = recordByNextId[nextId]).ID;
return result;
}
e.ChildID.Wrap()
自定义扩展方法的说明。我希望我只能使用e.ChildID
,但是BCL Dictionary
类会引发null
密钥的恼人异常。为了克服这个限制,我使用一个简单的包装器struct
和“流利的”帮助器:
public struct ValueWrapper<T> : IEquatable<ValueWrapper<T>>
{
public readonly T Value;
public ValueWrapper(T value) => Value = value;
public bool Equals(ValueWrapper<T> other) => EqualityComparer<T>.Default.Equals(Value, other.Value);
public override bool Equals(object obj) => obj is ValueWrapper<T> other && Equals(other);
public override int GetHashCode() => EqualityComparer<T>.Default.GetHashCode(Value);
public static implicit operator ValueWrapper<T>(T x) => new ValueWrapper<T>(x);
public static implicit operator T(ValueWrapper<T> x) => x.Value;
}
public static class ValueWrapper
{
public static ValueWrapper<T> Wrap<T>(this T value) => new ValueWrapper<T>(value);
}