C# 使用LINQ构建递归层次结构
在过去的几个小时里,我一直在努力解决一个看似简单的问题。我知道这个解决方案将使用LINQ和递归,但我就是无法实现 下面是我的示例代码,以及所需的输出(类似于它,我真的不在乎,它是正确构建层次结构的基础) 任何帮助都会有帮助C# 使用LINQ构建递归层次结构,c#,linq,C#,Linq,在过去的几个小时里,我一直在努力解决一个看似简单的问题。我知道这个解决方案将使用LINQ和递归,但我就是无法实现 下面是我的示例代码,以及所需的输出(类似于它,我真的不在乎,它是正确构建层次结构的基础) 任何帮助都会有帮助 using System; using System.Collections.Generic; namespace ConsoleApp14 { class Program { static string DoSomething(KeyVal
using System;
using System.Collections.Generic;
namespace ConsoleApp14
{
class Program
{
static string DoSomething(KeyValuePair<string, string>[] dir)
{
return ""; //something here
}
static void Main(string[] args)
{
KeyValuePair<string, string>[] dir = new[]
{
new KeyValuePair<string, string>(@"c:\one\two\three","100.txt"),
new KeyValuePair<string, string>(@"c:\one\two\three","101.txt"),
new KeyValuePair<string, string>(@"c:\one\four\five","501.txt"),
new KeyValuePair<string, string>(@"c:\one\six\","6.txt"),
new KeyValuePair<string, string>(@"c:\one\six","7.txt"),
new KeyValuePair<string, string>(@"c:\one\","1.txt"),
new KeyValuePair<string, string>(@"c:\one\six\","8.txt"),
new KeyValuePair<string, string>(@"c:\one\two","2.txt"),
new KeyValuePair<string, string>(@"c:\one\two\three\four\five\six\seven","y.txt"),
new KeyValuePair<string, string>(@"c:\one\xxx","xxx.txt")
};
// this is the output I want, rough indentation and crlf, the ordering is not important, just the hierarchy
Console.WriteLine(DoSomething(dir));
//
// one
// (1.txt)
// two
// (2.txt)
// three
// (100.txt)
// (101.txt)
// four
// five
// six
// seven
// (y.txt)
// four
// five
// (501.txt)
// six
// (6.txt)
// (7.txt)
// (8.txt)
// xxx
// (xxx.txt)
//
}
}
}
使用系统;
使用System.Collections.Generic;
名称空间控制台App14
{
班级计划
{
静态字符串DoSomething(KeyValuePair[]dir)
{
return”“;//这里有东西
}
静态void Main(字符串[]参数)
{
KeyValuePair[]目录=新建[]
{
新的KeyValuePair(@“c:\one\two\three”,“100.txt”),
新的KeyValuePair(@“c:\one\two\three”,“101.txt”),
新的KeyValuePair(@“c:\one\four\five”,“501.txt”),
新的KeyValuePair(@“c:\one\six\,“6.txt”),
新的KeyValuePair(@“c:\one\six”,“7.txt”),
新的KeyValuePair(@“c:\one\,“1.txt”),
新的KeyValuePair(@“c:\one\six\,“8.txt”),
新的KeyValuePair(@“c:\one\two”,“2.txt”),
新的KeyValuePair(@“c:\one\two\three\four\five\six\seven”,“y.txt”),
新的KeyValuePair(@“c:\one\xxx”、“xxx.txt”)
};
//这是我想要的输出,粗略缩进和crlf,排序并不重要,只是层次结构
控制台写入线(DoSomething(dir));
//
//一个
//(1.txt)
//两个
//(2.txt)
//三
//(100.txt)
//(101.txt)
//四
//五
//六
//七
//(y.txt)
//四
//五
//(501.txt)
//六
//(6.txt)
//(7.txt)
//(8.txt)
//xxx
//(xxx.txt)
//
}
}
}
使用我喜欢的一些实用程序扩展方法:
public static class Ext {
public static ArraySegment<T> Slice<T>(this T[] src, int start, int? count = null) => (count.HasValue ? new ArraySegment<T>(src, start, count.Value) : new ArraySegment<T>(src, start, src.Length - start));
public static string Join(this IEnumerable<string> strings, string sep) => String.Join(sep, strings.ToArray());
public static string Join(this IEnumerable<string> strings, char sep) => String.Join(sep.ToString(), strings.ToArray());
public static string Repeat(this char ch, int n) => new String(ch, n);
}
这是一个数据结构问题,而不是算法问题。一旦有了正确的数据结构,算法就会变得简单明了 您需要的数据结构是:节点是文件或目录:
abstract class Node {}
sealed class Directory : Node {}
sealed class File : Node {}
好的,我们对节点了解多少?只是它有一个名字:
abstract class Node
{
public string Name { get; private set; }
public Node(string name) { this.Name = name; }
}
我们对文件了解多少?只是它有一个名字
sealed class File : Node
{
public File(string name) : base(name) { }
}
我们对目录了解多少?它具有名称和子节点列表:
sealed class Directory : Node
{
public Directory(string name) : base(name) { }
public List<Node> Children { get; } = new List<Node>();
很好,现在我们可以获取一个目录并添加一个子目录或一个文件;如果已经有了,我们就把它拿回来
现在,你的具体问题是什么?您有一系列目录名和文件名,并且希望将该文件添加到目录中。那就写吧
public Directory WithDirectory(IEnumerable<string> directories)
{
Directory current = this;
foreach(string d in directories)
current = current.WithDirectory(d);
return current;
}
public File WithFile(IEnumerable<string> directories, string name)
{
return this.WithDirectory(directories).WithFile(name);
}
这里的关键是正确获取数据结构。目录只是一个名称加上一系列子目录和文件,因此编写代码。一旦数据结构正确,接下来的工作自然会进行。请注意,我编写的每个方法都只有几行。(我留下的TODO同样非常小。实现它们。)这就是你想要的:在每个方法中做一件事,并且做得非常好。如果您发现您正在编写庞大而复杂的方法,请停止编写,并将其重构为许多小方法,每个小方法都能完成一件清晰的事情
练习:实现名为ToBoxyString的ToString版本,该版本将生成:
c:
└─one
├─(1.txt)
├─two
│ ├─(2.txt)
│ └─three
。。。等等它不像看上去那么难;这只是一个更奇特的缩进。你能找出模式吗?因为我第一次尝试的时间太长,所以我决定添加这个选项作为单独的答案。此版本的效率更高,因为它只需通过dir数组一次 如前所述,使用一些扩展方法:
public static class Ext {
public static ArraySegment<T> Slice<T>(this T[] src, int start, int? count = null) => (count.HasValue ? new ArraySegment<T>(src, start, count.Value) : new ArraySegment<T>(src, start, src.Length - start));
public static string Join(this IEnumerable<string> strings, string sep) => String.Join(sep, strings.ToArray());
public static string Join(this IEnumerable<string> strings, char sep) => String.Join(sep.ToString(), strings.ToArray());
public static string Repeat(this char ch, int n) => new String(ch, n);
}
Console.WriteLine(DoSomething(dir).Join(Environment.NewLine));
同样,您可以像以前一样将其称为:
public static class Ext {
public static ArraySegment<T> Slice<T>(this T[] src, int start, int? count = null) => (count.HasValue ? new ArraySegment<T>(src, start, count.Value) : new ArraySegment<T>(src, start, src.Length - start));
public static string Join(this IEnumerable<string> strings, string sep) => String.Join(sep, strings.ToArray());
public static string Join(this IEnumerable<string> strings, char sep) => String.Join(sep.ToString(), strings.ToArray());
public static string Repeat(this char ch, int n) => new String(ch, n);
}
Console.WriteLine(DoSomething(dir).Join(Environment.NewLine));
我很尴尬。我有递归和LINQ,不能让它工作。我在这里把问题简化为基本问题。我认为我被否决是不公平的。产出的顺序对你重要吗?它必须与您的样本输出匹配吗?顺序或缩进根本不重要。我遇到的绊脚石是递归,尽管我可以很容易地分割目录字符串,但我似乎无法到达最后一位。有趣的方法。要让它正常工作,有一些令人惊讶的棘手细节(例如,输出不应该包括c:)。我在
with directories
方法中将路径拆分为组件。@NetMage:最初的海报说明问题在于了解算法,而输出的确切细节并不重要。如果您不喜欢“c:”的根,那么“”的根也可以。
c:
└─one
├─(1.txt)
├─two
│ ├─(2.txt)
│ └─three
public static class Ext {
public static ArraySegment<T> Slice<T>(this T[] src, int start, int? count = null) => (count.HasValue ? new ArraySegment<T>(src, start, count.Value) : new ArraySegment<T>(src, start, src.Length - start));
public static string Join(this IEnumerable<string> strings, string sep) => String.Join(sep, strings.ToArray());
public static string Join(this IEnumerable<string> strings, char sep) => String.Join(sep.ToString(), strings.ToArray());
public static string Repeat(this char ch, int n) => new String(ch, n);
}
static IEnumerable<string> DoSomething(KeyValuePair<string, string>[] dir) {
char[] PathSeparators = new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar };
// some local utility functions
int PathDepth(string p) => p.Count(ch => PathSeparators.Contains(ch));
string PathToDepth(string p, int d) => p.Split(PathSeparators).Slice(0, d+1).Join(Path.DirectorySeparatorChar);
// gather distinct paths (without trailing separators) and the files beneath them
var pathsWithFiles = dir.ToLookup(d => d.Key.TrimEnd(PathSeparators), d => d.Value);
// order paths with files into tree
var pfl = pathsWithFiles.Select(pfs => new {
Path = pfs.Key, // each path
Files = pfs.ToList() // the files beneath it
})
.OrderBy(dpef => dpef.Path); // sort into tree
// convert each entry into its directory path end followed by all files beneath that directory
// add entries for each directory that has no files
var stringTree = pfl.SelectMany(pf => Enumerable.Range(1, PathDepth(pf.Path))
// find directories without files
.Where(d => !pathsWithFiles.Contains(PathToDepth(pf.Path, d)))
// and add an entry for them
.Select(d => ' '.Repeat(4 * (d-1)) + Path.GetFileName(PathToDepth(pf.Path, d)))
// then add all the files
.Concat(pf.Files.Select(f => ' '.Repeat(4 * (PathDepth(pf.Path)- 1)) + $"({f})")
// and put the top dir first
.Prepend(' '.Repeat(4 * (PathDepth(pf.Path)-1)) + Path.GetFileName(pf.Path)))
);
return stringTree;
}
Console.WriteLine(DoSomething(dir).Join(Environment.NewLine));