2

我正在尝试创建一个 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 层深,每层只有几个项目。

4

3 回答 3

4

这是一个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)));
于 2013-09-10T11:03:39.760 回答
1

我真的不知道为什么你必须为此使用 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。你可能可以改进算法,因为我只是在几分钟内把它放在一起。

于 2013-09-10T11:00:42.490 回答
0

以及基于您的示例的另一种解决方案

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);
    }
于 2013-09-10T11:06:19.290 回答