Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/333.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 使用LINQ构建递归层次结构_C#_Linq - Fatal编程技术网

C# 使用LINQ构建递归层次结构

C# 使用LINQ构建递归层次结构,c#,linq,C#,Linq,在过去的几个小时里,我一直在努力解决一个看似简单的问题。我知道这个解决方案将使用LINQ和递归,但我就是无法实现 下面是我的示例代码,以及所需的输出(类似于它,我真的不在乎,它是正确构建层次结构的基础) 任何帮助都会有帮助 using System; using System.Collections.Generic; namespace ConsoleApp14 { class Program { static string DoSomething(KeyVal

在过去的几个小时里,我一直在努力解决一个看似简单的问题。我知道这个解决方案将使用LINQ和递归,但我就是无法实现

下面是我的示例代码,以及所需的输出(类似于它,我真的不在乎,它是正确构建层次结构的基础)

任何帮助都会有帮助

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