一个涉及List对象投射

本文关键字:对象 List 一个 | 更新日期: 2023-09-27 18:17:28

在最近的一次工作面试中,有人问了我这个问题,我不知道怎样才能优雅地回答。从那以后,它就一直困扰着我,我不知道是我对一些我不知道的"现代"技术/技术缺乏了解,还是我只是愚蠢。如有任何建议,欢迎指教。

问题

想象一个简单的类层次结构:

abstract class Person {
    public string Name { get; set; }
}
class Child : Person { }
class Parent : Person {
    public List<Person> Children { get; set; }
}
class Ancestor : Parent { }

问题是如何遍历这些对象的层次结构并打印出遇到的所有人。所以对于下面的场景:

Ancestor myAncestor = new Ancestor {    
    Name = "GrandDad",
    Children = new List<Person> { 
        new Child { Name = "Aunt" },
        new Child { Name = "Uncle" },
        new Parent {
            Name = "Dad", 
            Children = new List<Person> { 
                new Child { Name = "Me" }, 
                new Child { Name = "Sister" } 
            }
        }
    }
};

的输出应该类似于:

<>之前爷爷——阿姨——叔叔- *爸爸加新妹妹之前

所有的处理都需要在一个方法中完成,该方法接受一个类型为Ancestor的参数。

我几乎不动所动地实现了一个简单的递归解决方案,但当然,由于涉及到的对象相互关联的方式,事情并没有那么简单。

尽我所能,我想不出一个干净的方法来做到这一点,我的采访后谷歌已经建议我需要做的事情是(对我来说,只有LINQList<T>的工作知识)一些比我在过去十年左右一直在做的那种web开发代码技术上更先进的东西。是这样吗?或者我是否应该考虑退出软件开发,因为我在这方面很烂?

更新

感谢你们所有人的回复/建议。我接受了@Daniel Hilgarth的答案,主要是因为这是我唯一能真正理解的答案:-o

一个涉及List<T>对象投射

我同意Marc的评论,认为这种类型系统毫无意义。你仍然可以用委托来解决。这有点作弊,因为基本上它们只不过是方法,但我们开始吧:

void PrintFamily(Ancestor a)
{
    Action<Parent, int> printParent = null;
    printParent = (parent, level) => 
    {
        var indentation = new string(' ', level * 4);
        var indentationChildren = new string(' ', (level + 1) * 4);
        Console.WriteLine(indentation + parent.Name);
        foreach(var child in parent.Children)
        {
            if(child is Child)
                Console.WriteLine(indentationChildren + child.Name);
            else if(child is Parent)
            {
                printParent((Parent)child, level + 1);
            }
        }
    };
    printParent(a, 0);
}

这是可怕的,但在问题的范围内(没有额外的方法,所以不能添加多态性/鉴别器,方法必须采取Ancestor,所以没有方法递归):

static void Write(Ancestor root)
{
    // stack since depth-first
    var stack = new Stack<Tuple<Person,int>>();
    stack.Push(Tuple.Create((Person)root, 0));
    while(stack.Count > 0)
    {
        var pair= stack.Pop();
        var person = pair.Item1;
        // indentation
        Console.Write(new string(''t', pair.Item2));
        Parent parent = person as Parent;
        // node markers aren't fully specified, but this gets somewhere
        // near; maybe * should actually be checking Children != null &&
        // Children.Count > 0             
        if(person == root) {}
        else if (parent != null) { Console.Write("*");}
        else {Console.Write("-");}            
        Console.WriteLine(person.Name);
        // recursion on the stack
        if(parent != null && parent.Children != null) {
            foreach(var next in Enumerable.Reverse(parent.Children)) {
                stack.Push(Tuple.Create(next, pair.Item2 + 1));
            }
        }
    }
}

这是我的尝试,使用堆栈来模拟递归:

static void PrintTree(Ancestor ancestor)
{
    Stack<Tuple<int, Person>> PersonStack = new Stack<Tuple<int, Person>>();
    PersonStack.Push(new Tuple<int, Person>(0, ancestor));
    while (PersonStack.Count != 0)
    {
        Tuple<int, Person> NextPerson = PersonStack.Pop();
        Console.WriteLine(new string(' ', NextPerson.Item1) + NextPerson.Item2.Name);
        Parent NextPersonAsParent = NextPerson.Item2 as Parent;
        if (NextPersonAsParent != null && NextPersonAsParent.Children != null)
        {
            foreach (var Child in NextPersonAsParent.Children)
            {
                PersonStack.Push(new Tuple<int, Person>(NextPerson.Item1 + 1, Child));
            }
        }
    }
}
internal void ToString(Ancestor root)
{
    Trace.WriteLine(root.Name);
    Trace.Indent();
    foreach(var child in root.Children)
    {
         if(child is Parent)
             ToString(new Ancestor(){Name = child.Name, 
                                     Children = ((Parent)child).Children});
         else
             Trace.WriteLine(child.Name);
    }
    Trace.Unindent();
}

有一点"作弊"(感谢daniel的想法),这使用递归,并保持在任务的约束。

免责声明这进一步证明了所提出的类层次结构是奇数的。特别是对于任务,我不会在生产中使用这种技术

  internal static string ToString(Ancestor root){
        Func<Parent, string, string> inner = (x, y) => string.Empty;
        inner = (p, indentation) =>{
                    var parents = p.Children.OfType<Parent>();
                    var children = p.Children.OfType<Child>();
                    var childString =
                        string.Concat
                            (children.Select
                                 (c => indentation + "-" + c.Name + Environment.NewLine));
                    return indentation + "-" + p.Name + Environment.NewLine +
                           childString +
                           string.Concat
                               (parents.Select(par => inner(par, " " + indentation)));
                };
        return inner(root, string.Empty);
    }

首先声明一个函函数并初始化一个虚值。

然后构造一个lambda表达式,该表达式可以递归地调用self。表达式体的作用与下面的方法相同。

如果方法签名不需要祖先,我们可以这样做:

    internal string ToString(Parent parent, string indentation){
        var parents = parent.Children.OfType<Parent>();
        var children = parent.Children.OfType<Child>();
        var childString = children.Select(c => indentation + "-" + c.Name + Environment.NewLine);
        return indentation + "-" + parent.Name + Environment.NewLine + childString +
               string.Concat(parents.Select(par => ToString(par, " " + indentation)));
    }

首先创建一个包含所有父元素和一个所有子元素的列表。然后为所有子节点创建字符串(不需要递归),并为所有父节点创建循环符

Edit:

多态解决方案可能是最简单的,但您需要能够更改对象。让Person实现一个虚拟方法:例如Print(),它将在Parent中被覆盖,以便打印出子进程。例如,可以通过提供缩进级参数来处理缩进。正如您所注意到的,问题的约束禁止使用

问题中提供的对象结构相当无意义,约束也相当狭窄。此外,您需要实现一个采用Ancestor并且仅限于该方法主体的方法,这一事实使我认为该问题是专门为引导您走向堆栈方法而提出的。此外,与递归相比,后者具有重要的性能优势。

已经有几个很好的堆栈示例,所以我建议使用另一种递归方法,原则上应该符合"不声明任何额外的方法"规则,并且更具可读性(如果你知道你的lambda:)

Action<IEnumerable<Person>, int> traverseAndPrint = null;
traverseAndPrint =
    (ps, i) =>
    {
        foreach (var p in ps)
        {
            Console.WriteLine("{0}-{1}", new string(' ', i), p.Name);
            if (p is Parent) traverseAndPrint(((Parent)p).Children, i + 1);
        }
    };
traverseAndPrint(new [] {ancestor}, 0);

如果允许(重新)创建对象并利用它们的简单性,您可以使用:

    private static void PrintAncestor(Ancestor myAncestor)
    {
        Console.WriteLine(myAncestor.Name);
        foreach (var child in myAncestor.Children)
        {
            string intend = new string(myAncestor.Name.TakeWhile(c => c == ''t').ToArray()) + ''t';
            if (child is Ancestor)
                PrintAncestor(new Ancestor {
                    Children = (child as Ancestor).Children,
                    Name = intend + child.Name
                });
            if (child is Parent)
                PrintAncestor(new Ancestor {
                    Children = (child as Parent).Children,
                    Name = intend + "- *" + child.Name
                });
            if (child is Child)
                Console.WriteLine(intend + "-  " + child.Name);
        }
    }

打印:

GrandDad
        -  Aunt
        -  Uncle
        - *Dad
                -  Me
                -  Sister

相关文章: