1

我正在尝试更新我的对象图中的一个项目,该项目可以跨越“n”级深度。

以下是我的对象模型:

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

    public class Category : Entity
    {        
        public List<Category> Categories { get; set; }
        public List<Product> Products { get; set; }      
    }

    public class Product : Entity
    {        
    }

我的观点与ObservableCollection<Category> Categories. 我想要做的是给定一个类别名称,我需要从集合中检索与该对象匹配的第一个对象。

例如,给定这样的列表和面部组织类别,我需要从集合中检索面部组织类别对象。

Category - Pharmacy
  |-Product - Aspirin
  |-Product - Tylenol
  |-Category - Tooth Paste
  |  |-Product - Crest
  |  |-Product - Colgate
  |-Category - Paper Products
   |-Category - Toilet Paper
   |  |-Product - NoName
   |  |-Product - Charmin
   |-Category - Facial Tissue
      |-Product - Kleenex
Category - Household
  |-Product - Pinesol Cleaner
  |-Product - Garbage Bags

我已经尝试过了,但是当我在层次结构中搜索级别 >2 时,它会抛出一个未设置为对象异常实例的对象引用。

 return Categories.FirstOrDefault(n => n.Name == name) ??
                   Categories.SelectMany(node => node.Categories).Where(lx => lx.Name == name).FirstOrDefault();

注意:有时类别在层次结构的深处可能为空。即,如果没有类别,则集合设置为空。此外,解决方案不一定需要使用 LINQ。

4

3 回答 3

4

您可以使用以下任一方法递归遍历树结构:

public static IEnumerable<T> Traverse<T>(IEnumerable<T> source, Func<T, IEnumerable<T>> childSelector)
{
    var queue = new Queue<T>(source);
    while (queue.Any())
    {
        var item = queue.Dequeue();
        yield return item;
        foreach (var child in childSelector(item))
        {
            queue.Enqueue(child);
        }
    }
}

public static IEnumerable<T> Traverse<T>(T root, Func<T, IEnumerable<T>> childSelector)
{
    return Traverse(new[] { root }, childSelector);
}

单个根项有一个重载,另一个需要一系列项。

如果您愿意,可以使用实际递归来实现它们,但我更喜欢显式数据结构。如果您想要深度优先搜索而不是呼吸优先搜索,只需将 a 更改Queue为 aStack并相应地更新方法。

要使用它,您可以执行以下操作:

Category root = new Category();
var searchResult = Traverse(root, item => item.Categories)
            .Where(category => category.Name == "testValue")
            .FirstOrDefault();

您似乎也收到了空错误,因为您有空错误Categories。如果可能的话,我强烈建议您解决这个问题,而不是处理它。如果实体没有类别,它应该有一个列表,而不是空列表。Traverse话虽如此,如果您有任何空项目,您可以按如下方式调整调用:

Traverse(root, item => item.Categories ?? Enumerable.Empty<Category>())
于 2012-12-10T17:16:07.643 回答
1

LINQ 本身没有用于深度优先搜索的专用运算符(这是您在这种情况下所需要的)。但是,鉴于您的要求,有一个使用简单递归函数的相当简单的解决方案:

// Returns the first category with the given name or null, if none is found
Category findCategory(Category start, String name) {
    if (start.name == name) {
        return start;
    }
    if (start.Categories == null) {
        return null;
    }
    return (from c in start.Categories
            let found = findCategory(c, name)
            where found != null
            select found).FirstOrDefault()
}

您可以考虑将Categories没有子类别的类别的属性设置为空列表而不是null. 这允许您在此处跳过空值检查(可能在许多其他地方也可能)。

于 2012-12-10T17:15:38.300 回答
0

这是一个简单的解决方案,尽管它不仅使用 Linq:

public Category GetCategory(string name, List<Category> Categories) 
{
    Category found = Categories.FirstOrDefault(cat => cat.Name == name);
    return found ?? Categories.Select(cat => GetCategory(name,cat.Categories))
                              .FirstOrDefault(cat => cat != null);
}
于 2012-12-10T17:33:32.623 回答