2017-03-03 78 views
1

我有一個類如下:遞歸列表中包含財產

public class Feature 
{ 
    public string Name { get; set; } 

    public string DisplayName { get; set; } 

    public List<Feature> SubFeatures { get; set; } = new List<Feature>(); 
} 

然後我有一個List<Feature> features = new List<Feature>;在那裏我存儲我的所有功能。

現在,我想知道在我的features變量中是否存在特定Feature(按名稱)。

然而,它可以存在於例如SubFeatureSubFeature的任何級別(SubFeature)。

我已經得到的最接近的是這樣的:

public bool FeatureExists(Feature feature, string name) 
{ 
    return feature.Name == name || feature.SubFeatures.Select(subFeature => FeatureExists(subFeature, name)).Any(result => result); 
} 

但它涉及到有使用上的FeatureExists()呼叫者在同一時間給它傳遞一個頂級功能。

我確定有一個更簡單的方法來做到這一點,我該如何正確地做到這一點?

+0

爲什麼你需要使用'for'?你不能指向所有功能的頂級父母嗎?這個方法被遞歸地調用,所以它將針對每個「SubFeatures」對象執行。 – foobar

+0

你可以做的是創建一個名爲層次結構的新變量.......從你的程序可以找出多少時間遞歸列表子功能:) –

+3

我覺得我誤解了一些東西......你是基本上要求'features.Any(x => FeatureExists(x,featureName))'? – grek40

回答

3

定義一個遞歸方法是這樣的:

public IEnumerable<Feature> FeatureAndSubFeatures(Feature feature) 
{ 
    yield return feature; 

    foreach (var subFeature in feature.SubFeatures) 
    { 
     foreach (var child in FeatureAndSubFeatures(subFeature)) 
     { 
      yield return child; 
     } 
    } 
} 

然後使用它:

FeatureAndSubFeatures(feature).Any(x => x.Name == name); 

另一種選擇是把這個方法在功能本身,所謂的像SelfAndSubFeaturesRecursive()。

這種方法 - 編寫一個遞歸地扁平化樹的方法,而不是編寫一個特定方法來搜索具有給定名稱的特徵 - 是非常靈活的,因爲您可以使用它來搜索任何基於樹的節點標準或節點的任何子集,而不是專門用於查找具有特定名稱的節點。

您也可以編寫它以開始使用一組功能。喜歡的東西:

public IEnumerable<Feature> FeaturesAndSubFeatures(IEnumerable<Feature> features) 
{ 
    foreach (var feature in features) 
    { 
     yield return feature; 

     foreach (var child in FeaturesAndSubFeatures(feature.SubFeatures)) 
     { 
      yield return child; 
     } 
    } 
} 

這僅僅是有用的,如果你總是有特徵集合出發,但你做的情況下節省了SelectMany

+0

好吧,這與我所得到的非常相似,但是我仍然存在的問題是我從一個'List '開始,並且該方法接受單個特徵。我如何搜索所有這些? – cogumel0

+0

你可以寫'features.SelectMany(x => FeatureAndSubFeatures(x))。任何(...)',或者你可以重寫'FeatureAndSubFeatures'來獲得一個'IEnumerable '。 – canton7

+0

雖然在這裏有很多正確的答案(正如在評論中指出的那樣,最簡單的事情就是對於我來說只是做'features.Any(x => FeatureExists(x,「無論」))「,這種方法很遠更靈活 – cogumel0

0

你想爲你做一個單獨的遞歸方法。 試試這個:

public bool FeatureExists(Feature feature, string name) { 
    if(feature.Name == name) { 
     return true; 
    } 
    if(!feature.SubFeatures.isEmpty) { 
     foreach(Feature subFeature in feature.SubFeatures){ 
      FeatureExists(subFeature, name) 
     } 
    } 
     return false; 
    } 
1

我儘量避免遞歸。這是一個版本,而不:

public bool FeatureExists(Feature feature, string name) 
{ 
    var featureQueue = new Queue<Feature>(); 
    featureQueue.Enqueue(feature); 
    while (featureQueue.Count > 0) 
    { 
     Feature current = featureQueue.Dequeue(); 
     if (feature.Name == name) 
      return true; 
     foreach (Feature f in current.SubFeatures) 
      featureQueue.Enqueue(f); 
    } 
    return false; 
} 

如果您發現本作評論,你可以使用一個通用的擴展方法,只要你需要遞歸檢查,例如使用它的可讀性:

public static class Extensions 
{ 
    public static bool RecursiveCheck<T>(this T rootItem, Func<T, IEnumerable<T>> getChildrenFunc, Func<T, bool> predicate) 
    { 
     var queue = new Queue<T>(); 
     queue.Enqueue(rootItem); 
     while (queue.Count > 0) 
     { 
      T current = queue.Dequeue(); 
      if (predicate(current)) 
       return true; 
      foreach (T child in getChildrenFunc(current)) 
       queue.Enqueue(child); 
     } 
     return false; 
    } 
} 

測試功能:

Feature f1 = new Feature 
{ 
    Name = "1", SubFeatures = new List<Feature> { new Feature {Name="1.1", SubFeatures = new List<Feature> {new Feature {Name= "thename" } } }} 
}; 

這簡單的一行仍然是:

bool containsName = f1.RecursiveCheck<Feature>(f => f.SubFeatures, f => f.Name == "thename"); 
+1

你能詳細說明爲什麼你試圖避免遞歸嗎?我很想知道更多關於這個的信息。 – cogumel0

+1

@ cogumel0:遞歸通常更難以理解,更容易引起錯誤,它可能會導致'StackOverFlowExceptions '等 –

+1

但是我個人發現你的代碼難以遵循,與當前的遞歸方法相反。StackOverflow'部分我同意。 – Emad

0

嗯,我不確定這是你問什麼,但如果你想要更乾淨的代碼,你可以寫你的函數作爲擴展方法,並使用LINQ而不是循環來獲得更乾淨的代碼。

public static bool FeatureExists(this Feature feature, string name) 
{ 
    return feature.Name == name || feature.SubFeatures.Select(subFeature => FeatureExists(subFeature, name)).Any(result => result); 
} 

然後

List<Feature> mainFeatures = new List<Feature>(); 
mainFeatures.Any(obj => obj.FeatureExists("abc")); 

如果你想事件更短,更乾淨的代碼,你可以考慮有一個特徵作爲像母親一樣功能的所有功能,父母,然後打電話給你的遞歸方法在這一點。

但無論如何,考慮讓你的方法擴展方法。

+0

您的代碼可以簡化爲'mainFeatures.Any(obj => obj.FeatureExists(「abc」))''。但是,它很好地回答了這個問題。 – cogumel0

+0

@ cogumel0謝謝我試圖回答很快錯過了這個。 – Emad

0

以下應該通過所有列表重複並填寫「結果」與包含在整個層級中的所有可能的特徵

public class Feature 
    { 
     public string Name { get; set; } 

     public string DisplayName { get; set; } 

     public List<Feature> SubFeatures { get; set; } = new List<Feature>(); 
    } 

其他類

List<feature> result = new List<feature>(); 

public void FindItems(Feature yourFeature) 
{ 
    result.add(yourFeature); 
    foreach(Feature feature in yourFeature) 
    { 
     if(feature.SubFeatures.count != 0) 
     { 
     foreach(Feature subfeature in feature) 
     { 
      FindItems(subfeature); 
     } 
     } 
     else 
     { 
     result.add(feature); 
     } 
    } 
} 
+0

使用yield return生成'IEnumerable '效率更高,特別是如果在找到具有給定名稱的第一個節點後搜索將停止。即使正在搜索的節點是第一個節點,您的方法也會使整個樹變平。 – canton7

+0

扁平化整個樹是他想要的,即時通訊只是想知道,如果你的遞歸如果層次結構是5-10深?將會發生什麼?(合法的問題,由我可以遵循它的原因將只有約3深) –

+0

當然,但他不需要將整棵樹包含在列表中。當他找到符合他的標準的項目時,他只需要搜索樹並**停止**。將整棵樹放在新列表中是浪費時間和空間。 – canton7