【问题标题】:How to return a path of nodes from a recursive hierarchy given a condition?如何在给定条件的情况下从递归层次结构中返回节点路径?
【发布时间】:2013-09-14 00:13:09
【问题描述】:

我正在尝试创建一个 linq 扩展方法,该方法将路径返回到选定节点。

节点 Item4 的路径将产生 - { Item1, Item2, Item4 }
节点 Item3 的路径将产生 - { Item1, Item3 }

public class Item
{
    public int Id { get; set; }
    public IList<Item> Items { get; set; }
}

Item1
    Item2
        Item4
    Item3

调用代码

var path = myItem.Items
    .Path(e => e.Items, e => MyPredicateCondition())
    .ToList();

扩展方法

public static IEnumerable<T> Path<T>(
    this IEnumerable<T> source,
    Func<T, IEnumerable<T>> childrenSelector,
    Predicate<T> condition)
{
    if (source == null || !source.Any()) return default(T);

    var attempt = source.FirstOrDefault(t => condition(t));
    if (!Equals(attempt, default(T))) return attempt;

    var items = source.SelectMany(childrenSelector)
        .Path(childrenSelector, condition)
        .ToList();

    return attempt;
}

我的问题不是找到实际的节点,而是返回节点递归地将树备份到根节点。数据结构应该保持原样 - 即我不希望 Item 引用它的 parent item

注意:代码当前无法编译,因为我正在尝试通过其他方式使用 IEnumerable yield 等。

效率不是问题,因为它将用于 3 或 4 个级别,每个级别只有几个项目。

【问题讨论】:

  • 请提供示例输入和预期输出以澄清问题。
  • 对不起,我错过了层次结构。我想就是这样。

标签: c# linq c#-4.0 predicate


【解决方案1】:

这是一个基于Stack 的简单实现。

这个想法是将我们要检查的每个项目放在一个堆栈上,如果它不是我们正在寻找的路径的一部分,则将其从堆栈中删除,直到我们找到我们正在寻找的项目为。

IEnumerable<T> Path<T>(IEnumerable<T> source,
                       Func<T, IEnumerable<T>> childrenSelector,
                       Func<T, bool> condition)
{
    var path = new Stack<T>();
    Path(source, childrenSelector, condition, path);
    return path.Reverse().ToList();
}                       
bool Path<T>(IEnumerable<T> source,
             Func<T, IEnumerable<T>> childrenSelector,
             Func<T, bool> condition,
             Stack<T> path)
{
    foreach (var item in source)
    {
        path.Push(item);
        if (condition(item) || Path(childrenSelector(item), childrenSelector, condition, path))
            return true;
        else
            path.Pop();
    }
    return false;
}

示例:

Item[] items = { new Item {Id = 1, Items = new List<Item> 
{ 
    new Item {Id = 2,  Items = new List<Item> {new Item { Id = 4, Items = new List<Item>()}}},
    new Item {Id = 3,  Items = new List<Item>()},
}}};

var path = Path(items, e => e.Items, e => e.Id == 4);

// prints '1 -> 2 -> 4'
Console.WriteLine(String.Join(" -> ", path.Select(i=>i.Id)));

【讨论】:

    【解决方案2】:

    我真的不知道为什么你必须为此使用 linq,所以我创建了一个递归替代方案:

    class Program
    {
        static void Main(string[] args)
        {
            var item1 = new Item()
            {
                Id = 1,
                Items = new List<Item>()
                {
                    new Item()
                    {
                        Id = 2,
                        Items = new List<Item>()
                        {
                            new Item()
                            {
                                Id = 4,
                                Items = new List<Item>()
                            }
                        }
                    },
                    new Item()
                    {
                        Id = 3,
                        Items = new List<Item>()
                    }
                }
            };
            var path = GetPath(item1, (i) => i.Id == 3);
            foreach (var item in path)
            {
                Console.WriteLine(item.Id);
            }
            Console.ReadLine();
        }
    
        private static IEnumerable<Item> GetPath(Item item, Func<Item, bool> predicate)
        {
            return GetPathRecursive(item, predicate, new List<Item>());
        }
    
        private static IEnumerable<Item> GetPathRecursive(Item item, Func<Item, bool> predicate, IEnumerable<Item> items)
        {
            var resultCandidate = items.Concat(new List<Item> { item });
            if (predicate(item))
            {
                return resultCandidate;
            }
            else
            {
                if (item.Items == null || item.Items.Count == 0)
                {
                    return new List<Item>();
                }
                foreach (var nextItem in item.Items)
                {
                    var newResult = GetPathRecursive(nextItem, predicate, resultCandidate);
                    if (newResult != null && newResult.Any())
                    {
                        return newResult;
                    }
                }
                return new List<Item>();
            }
        }
    }
    
    public class Item
    {
        public int Id { get; set; }
        public IList<Item> Items { get; set; }
    }
    

    我尝试搜索Item3Item4。你可能可以改进算法,因为我只是在几分钟内把它放在一起。

    【讨论】:

    • 也许......看看解决方案:)。我只是以为您出于某种原因真的想使用 linq。希望对您有所帮助。
    【解决方案3】:

    另一个基于您的示例的解决方案

    public static class StaticItem
    {
        public static IEnumerable<T> Path<T>(
            this IEnumerable<T> source,
            Func<T, IEnumerable<T>> childrenSelector,
            Predicate<T> condition)
        {
            // no data, no processing
            if (source == null || !source.Any()) return null;
    
            // if condition matches, return list containing element
            var attempt = source.FirstOrDefault(t => condition(t));
            if (!Equals(attempt, default(T))) return new List<T> { attempt };
    
            // iterate current items (btw: already done by FirstOrDefault)
            foreach (var item in source)
            {
                // select children
                IEnumerable<T> childs = childrenSelector(item);
    
                var x = childs.Path(childrenSelector, condition);
    
                // if element was found in path, the result is not null
                if (x != null)
                {
                    // adding item to list
                    var list = new List<T>();
                    list.Add(item);
                    list.AddRange(x);
                    return list;
                }
            }
            return null;
        }
    }
    
        static void Main()
        {
            Item item1 = new Item { Id = 1 };
            Item item2 = new Item { Id = 2 };
            Item item3 = new Item { Id = 3 };
            Item item4 = new Item { Id = 4 };
    
            item1.Items = new List<Item> { item2, item3 };
            item2.Items = new List<Item> { item4 };
    
            List<Item> all = new List<Item> { item1 };
    
            var x = all.Path( item => item.Items, i => i.Id == 4);
        }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-12-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-09-27
      • 2020-08-23
      • 1970-01-01
      • 2017-07-24
      相关资源
      最近更新 更多