我有一个简单的类定义为:
public class IndexEntry
{
public bool HighScore { get; set; }
public List<IndexEntry> SubEntries { get; set; }
//Other properties, etc...
}
我现在需要搜索一个列表,找到其HighScore属性设置为 true 的项目。既然它不是一个平面列表,而是一个层次结构,它可以是一个未知数量级别的深度,因为我正在寻找的项目可能包含在任何一个SubEnties列表中,我不能做一个简单的Lambda像这样:
var foundHighScore = myList.FirstOrDefault(IE => IE.HighScore == true);
这是我的代码。我知道这很难看(至少对我而言似乎这样)。它有效,但在一个甚至远程大的列表中作为罪恶很慢,我相信必须有更好的方法。
private IndexEntry GetHighScoreEntry(IEnumerable<IndexEntry> entryList)
{
IndexEntry result = null;
IndexEntry recursiveResult = null;
foreach (IndexEntry currentEntry in entryList)
{
if (currentEntry.HighScore)
{
result = currentEntry;
break; //Don't need to look anymore, we found our highscore.;
}
else
{
if ((currentEntry.SubEntries == null) || (currentEntry.SubEntries.Count < 1))
{
continue;
}
else
{
recursiveResult = GetHighScoreEntry(currentEntry.SubEntries);
if (recursiveResult == null)
continue;
result = recursiveResult;
break;
}
}
}
return result;
}
我相信有一种更好的方法可以使用稍微复杂的lambda或LINQ来清理这些代码并使其更高效。
提前感谢您的帮助。
答案 0 :(得分:9)
到目前为止发布的所有解决方案都是专门的 - 它们不是通用的或一般的,因此,下次有分层列表时,您必须编写新的解决方案。呸。
这是一个通用的通用解决方案,可以满足您的所有分层需求:
public static IEnumerable<T> Flatten<T>(this IEnumerable<T> sequence, Func<T, IEnumerable<T>> childFetcher)
{
var itemsToYield = new Queue<T>(sequence);
while (itemsToYield.Count > 0)
{
var item = itemsToYield.Dequeue();
yield return item;
var children = childFetcher(item);
if (children != null)
{
foreach (var child in children)
{
itemsToYield.Enqueue(child);
}
}
}
}
以下是您使用它的方式:
myList.Flatten(i => i.SubEntries).FirstOrDefault(i => i.HighScore);
容易干酪。
此扩展方法可用于将任何分层数据转换为平面列表,可以使用LINQ对其进行搜索。
这个解决方案的另一个好处是使用延迟评估,因此它只能完成与调用者要求一样多的工作。例如,在上面的代码中,Flatten将在找到HighScore后立即停止生成项目。
此解决方案还避免了递归,这对于深层嵌套的层次结构来说可能是一项代价高昂的操作,从而避免了递归解决方案产生的大量堆栈分配。
答案 1 :(得分:2)
递归是你的朋友。
public IndexEntry FindHighScore(IEnumerable<IndexEntry> entries)
{
foreach (IndexEntry entry in entries)
{
IndexEntry highScore = FindHighScore(entry);
if (highScore != null)
{
return highScore;
}
}
return null;
}
private IndexEntry FindHighScore(IndexEntry entry)
{
return entry.HighScore ? entry : FindHighScore(entry.SubEntries);
}
答案 2 :(得分:0)
您可以使用lambda表达式来缩小搜索范围,例如:
var foundHighScore = myList.FirstOrDefault(IE => IE.HighScore or (IE.SubEntries != null && IE.SubEntries.Any(IES => IES.HighScore));
var indexEntry = foundHighScore;
if (!indexEntry.HighScore)
{
indexEntry = indexEntry.SubEntries.FirstOrDefault(IE => IE.HighScore);
}
// do something with indexEntry
更新
实际上,第一个解决方案不会正常遍历。我不认为有一个lambda唯一的解决方案,你将不得不做一些形式的递归函数。在我的头脑中,以下是可行的,它如何应对性能明智我不确定:
public IndexEntry FindHighScore(List<IndexEntry> entries)
{
var highScore = GetHighScore(entries);
if (highScore == null)
{
// give me only entries that contain sub-entries
var entriesWithSub = entries.Where(e => e.SubEntries != null);
foreach (var e in entriesWithSub)
{
highScore = FindHighScore(e.SubEntries);
if (highScore != null)
return highScore;
}
}
return highScore;
}
private IndexEntry GetHighScore(List<IndexEntry> entries)
{
return entries.FirstOrDefault(IE => IE.HighScore);
}