一个涉及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
的参数。
我几乎不动所动地实现了一个简单的递归解决方案,但当然,由于涉及到的对象相互关联的方式,事情并没有那么简单。
尽我所能,我想不出一个干净的方法来做到这一点,我的采访后谷歌已经建议我需要做的事情是(对我来说,只有LINQ
和List<T>
的工作知识)一些比我在过去十年左右一直在做的那种web开发代码技术上更先进的东西。是这样吗?或者我是否应该考虑退出软件开发,因为我在这方面很烂?
感谢你们所有人的回复/建议。我接受了@Daniel Hilgarth的答案,主要是因为这是我唯一能真正理解的答案:-o
我同意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