一个涉及List <t>和对象转换</t>的棘手问题

时间:2011-12-12 09:39:11

标签: c# linq inheritance casting

我最近在求职面试中得到了这个问题,并且无法弄清楚如何优雅地做到这一点。从那以后,它一直在唠叨我,如果我对某些我不知道的“现代”技术/技术缺乏了解,或者我只是愚蠢,我就无法解决问题。任何建议都会非常受欢迎。

问题

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

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" } 
            }
        }
    }
};

输出应该是这样的:

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

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

我几乎没有想到,实现了一个简单的递归解决方案,但当然因为所涉及的对象相互关联的方式不是那么简单。

尽我所能,我想不出干净的方式这样做,我的采访后谷歌搜索建议我需要做一些事情(对我来说,只有LINQ和{的工作知识{1}})技术上比我在过去十年左右一直在做的网络开发编码更先进的东西。是这样的吗?或者我应该考虑退出软件开发,因为我对它很垃圾?

更新

感谢大家的回复/建议。我接受了@Daniel Hilgarth的答案主要是因为它是我唯一能够真正理解的答案:-o

7 个答案:

答案 0 :(得分:9)

我同意马克的评论说这种类型的系统是没有意义的。不过,你可以用代表来解决它。这有点作弊,因为基本上它们只不过是方法,但我们走了:

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);
}

答案 1 :(得分:4)

这很可怕,但是在问题的范围内(没有额外的方法,所以不能添加多态/鉴别器,方法必须采用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));
            }
        }
    }
}

答案 2 :(得分:2)

这是我的方法,使用堆栈来模拟递归:

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));
            }
        }
    }
}

答案 3 :(得分:2)

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();
}

答案 4 :(得分:1)

有一点“作弊”(感谢daniel for ides“这使用了递归并且在任务的约束下保持不变。

免责声明这更加证明了所提出的班级是奇怪的。特别是对于任务,我不会在生产中使用这种技术

  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表达式,可以自我递归地调用它。表达式的主体与下面的方法相同。

如果方法signatur不需要祖先,我们可以做类似的事情:

    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)));
    }

首先创建一个包含所有父项和一个所有子项的列表。然后为所有子项创建字符串(不需要递归)和所有父项的重复

答案 5 :(得分:1)

修改

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

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

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

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);

答案 6 :(得分:1)

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

    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