清理树中只包含空文件夹的文件夹。

huangapple go评论83阅读模式
英文:

Clean out folders which only have empty folders from tree

问题

我有一个切片:

type Node struct {
   Id       string
   Children []Node
}

我有一个由这个切片建模的目录结构。这个目录中可能有多层文件夹结构,最终这些文件夹中可能没有任何文件。例如:

folder1/folder2/folder3/folder4
folder1/file1.txt

我想要清理那些只包含空文件夹的文件夹。所以在这个例子中,只有folder1会保留,它里面有一个文件,下面的所有内容都会被删除。

然而,我似乎想不出一个好的方法来做到这一点。我完全可以创建一个新的树,而不是改变原来的树,但我不知道如何有效地遍历树,并查看最后一个子节点是否没有子节点,然后返回到根节点并删除那个只是一系列空文件夹的子节点。

欢迎任何想法!

我的初始解决方案只删除叶子节点,而不是父文件夹:

func removeChildlessFolders(original, tree []Node) []Node {
	for i, node := range original {
		if len(node.Children) == 0 {
			continue
		}

		dir := Node{}
		dir.Id = node.Id
		dir.Children = append(dir.Children, node.Children...)
		tree = append(tree, dir)
		removeChildlessFolders(original[i].Children, node.Children)
	}

	return tree
}
英文:

I have a slice of

type Node struct {
   Id       string
   Children []Node
}

I have a diretory structure modelled by this slice. It can happen that there are multi level folder structure in this directory which eventually do not have any files in them. See:ű

folder1/folder2/folder3/folder4
folder1/file1.txt

I would want to clean up those folders which only have empty folders in them. So in this example only folder1 would remain with a file in it, everything below would be deleted.
However I can't seem to come up with a good idea to do so. I'm perfectly fine with creating a new tree and not mutating the original one, but I don't know how I could traverse the tree effectively and see if the last child is childless and then go back to the root and remove that child which has turned out to be just a list of empty folders.
Any idea would be welcomed!

My initial solution which only removes leaves and not the parent folder also:

func removeChildlessFolders(original, tree []Node) []Node {
	for i, node := range original {
		if len(node.Children) == 0 {
			continue
		}

		dir := Node{}
		dir.Id = node.Id
		dir.Children = append(dir.Children, node.Children...)
		tree = append(tree, dir)
		removeChildlessFolders(original[i].Children, node.Children)
	}

	return tree
}

答案1

得分: 2

首先是一个很好的问题,但是对于其他人来说很难复现你的使用情况。下次尝试添加一个可复现的代码,这样人们可以使用并快速测试他们的方法并给出结果。比如你已经传递了根目录,但是你是如何初始化它的?如果有人需要帮助你,他们需要首先构建这个树。一般来说这是不方便的。不过,让我们来解决这个问题。

目录结构

输入目录

test-folder
├── folder1
│    └── folder2
│        └── folder3
├── folder4
│    ├── folder5
│    └── joker
└── folder6
    └── file.txt

期望结果

test-folder
└── folder6
    └── file.txt

节点定义

首先,我不知道你是如何创建目录树的。如果你是硬编码的方式创建的,那就是另外一个问题了,但是通常情况下,一个n-ary树是通过自引用指针来填充的,而不是使用精确的切片。所以我会按照以下方式定义节点:

type Node struct {
    Id       string
    Children []*Node
}

辅助方法

这是一个辅助方法,用于检查路径是否指向一个目录:

func ifDir(path string) bool {
    file, err := os.Open(path)
    if err != nil {
        panic(err)
    }
    defer file.Close()
    info, err := file.Stat()
    if err != nil {
        panic(err)
    }
    if info.IsDir() {
        return true
    }
    return false
}

如何构建树

这是一种使用队列来输入n-ary树的简单迭代方式。Golang没有提供队列实现,但是Golang的通道实际上就是队列。我将其设置为500,因为我们无法在Golang中创建动态缓冲通道。在我看来,这个数字对于几乎任何情况都应该足够了。

func buildTreeFromDir(baseDir string) *Node {
    _, err := ioutil.ReadDir(baseDir)
    if err != nil {
        return nil
    }
    root := &Node{
        Id: baseDir,
    }
    /////////
    queue := make(chan *Node, 500) // 假设没有深度超过500的目录
    queue <- root
    for {
        if len(queue) == 0 {
            break
        }
        data, ok := <-queue
        if ok {
            // 遍历目录中的所有内容
            curDir := (*data).Id
            if ifDir(curDir) {
                contents, _ := ioutil.ReadDir(curDir)

                data.Children = make([]*Node, len(contents))
                for i, content := range contents {
                    node := new(Node)
                    node.Id = filepath.Join(curDir, content.Name())
                    data.Children[i] = node
                    if content.IsDir() {
                        queue <- node
                    }
                }
            }
        }
    }
    return root
}

另一个辅助方法

这只是用于打印目录树的方法,仅用于调试目的。

func printDirTree(root *Node) {
    fmt.Println(root.Id)
    for _, each := range root.Children {
        printDirTree(each)
    }
    if len(root.Children) == 0 {
        fmt.Println("===")
    }
}

最后是你的解决方案

非常直接。如果有任何问题,请告诉我。

func recursiveEmptyDelete(root *Node) {
    // 如果当前根节点不指向任何目录
    if root == nil {
        return
    }
    for _, each := range root.Children {
        recursiveEmptyDelete(each)
    }
    if !ifDir(root.Id) {
        return
    } else if content, _ := ioutil.ReadDir(root.Id); len(content) != 0 {
        return
    }
    os.Remove(root.Id)
}

这是main()函数

func main() {
    root := buildTreeFromDir("test-folder")
    printDirTree(root)
    recursiveEmptyDelete(root)
}
英文:

Firstly good question but it is very difficult for others to reproduce the use case that you have. From next time try adding a reproducible code that people can use and quickly test their approaches and give the result. Like you have passed the root but how you have initialised it? If someone needs to help you out, they need to first build the tree. Generally this is inconvenient. Nevertheless let's get to the solution.

Directory structure

Input Dir

test-folder
├── folder1
│&#160;&#160; └── folder2
│&#160;&#160;     └── folder3
├── folder4
│&#160;&#160; ├── folder5
│&#160;&#160; └── joker
└── folder6
    └── file.txt

Expected Result

test-folder
└── folder6
    └── file.txt

Node definition

Firstly, I don't know how you have created the tree of dirs. If you have hardcoded it, then it's a different issue but the way an n-ary tree is usually populated, then you need to define the Node with the self referential pointer. Not with exact slice. So I would define the Node in the following way

type Node struct {
	Id       string
	Children []*Node
}

Helper Method

This is a helper method to check if a path is pointing to a directory

func ifDir(path string) bool {
	file, err := os.Open(path)
	if err != nil {
		panic(err)
	}
	defer file.Close()
	info, err := file.Stat()
	if err != nil {
		panic(err)
	}
	if info.IsDir() {
		return true
	}
	return false
}

How to populate the tree

This is a simple and iterative way to input an n-ary tree using a queue. Golang doesn't provide and queue implementation but golang channels are actually queues only. I have kept it to 500 because we can not create a dynamic buffered channel in golang. This number should be good for almost any scenarios IMHO.

func buildTreeFromDir(baseDir string) *Node {
	_, err := ioutil.ReadDir(baseDir)
	if err != nil {
		return nil
	}
	root := &amp;Node{
		Id: baseDir,
	}
	//////////
	queue := make(chan *Node, 500) // Consider that there can not be any dir with &gt; 500 depth
	queue &lt;- root
	for {
		if len(queue) == 0 {
			break
		}
		data, ok := &lt;-queue
		if ok {
			// Iterate all the contents in the dir
			curDir := (*data).Id
			if ifDir(curDir) {
				contents, _ := ioutil.ReadDir(curDir)

				data.Children = make([]*Node, len(contents))
				for i, content := range contents {
					node := new(Node)
					node.Id = filepath.Join(curDir, content.Name())
					data.Children[i] = node
					if content.IsDir() {
						queue &lt;- node
					}
				}
			}
		}
	}
	return root
}

Another helper method

This is just to print the dir tree. Just for debugging purpose.

func printDirTree(root *Node) {
	fmt.Println(root.Id)
	for _, each := range root.Children {
		printDirTree(each)
	}
	if len(root.Children) == 0 {
		fmt.Println(&quot;===&quot;)
	}

}

Finally your solution.

Pretty straightforward. Do let me know if you have any questions.

func recursiveEmptyDelete(root *Node) {
	// If the current root is not pointing to any dir
	if root == nil {
		return
	}
	for _, each := range root.Children {
		recursiveEmptyDelete(each)
	}
	if !ifDir(root.Id) {
		return
	} else if content, _ := ioutil.ReadDir(root.Id); len(content) != 0 {
		return
	}
	os.Remove(root.Id)
}

Here is the main()

func main() {
	root := buildTreeFromDir(&quot;test-folder&quot;)
	printDirTree(root)
	recursiveEmptyDelete(root)
}

答案2

得分: 0

让我看看我能否帮助你。首先,我想说这个解决方案肯定可以改进!顺便说一句,这是我能做到的最好的,希望你能找到一些实现你所需的见解。这里,我将展示给你所管理的场景。

场景1

输入:

  • folder1/folder2/folder3/folder4/file1.txt

输出:

  • folder1/file1.txt

场景2

输入:

  • folder1/folder2/folder3/folder4/file1.txt
  • folder1/folder5/

输出:

  • folder1/file1.txt

场景3

输入:

  • folder1/folder2/folder3/folder4/file1.txt
  • folder1/folder5/file2.txt

输出:

  • folder1/file1.txt
  • folder1/file2.txt

代码

这里,我将向你展示我解决方案的相关部分。

Node 结构体

type Node struct {
	name     string
	isDir    bool
	children []Node
}

getSafePath 函数

func getSafePath(parent Node, safePath string, foldersToDel []string) (string, []string) {
	if len(parent.children) == 1 && !parent.children[0].isDir {
		fileName := filepath.Base(parent.children[0].name)
		if err := os.Rename(parent.children[0].name, fmt.Sprintf("%v/%v", safePath, fileName)); err != nil {
			panic(err)
		}
		return safePath, foldersToDel
	}

	if len(parent.children) == 1 && parent.children[0].isDir {
		foldersToDel = append(foldersToDel, parent.children[0].name)
		newChildren := []Node{}
		newChildren = append(newChildren, parent.children[0].children...)
		parent.children = newChildren

		safePath, foldersToDel = getSafePath(parent, safePath, foldersToDel)
		return safePath, foldersToDel
	}

	for _, v := range parent.children {
		if !v.isDir {
			fileName := filepath.Base(v.name)
			if err := os.Rename(v.name, fmt.Sprintf("%v/%v", safePath, fileName)); err != nil {
				panic(err)
			}
		} else {
			foldersToDel = append(foldersToDel, v.name)
			safePath, foldersToDel = getSafePath(v, safePath, foldersToDel)
		}
	}

	return safePath, foldersToDel
}

这个函数负责将位于叶级目录中的文件移动到上一级目录。

main.go 文件

// 为了简洁起见,省略了部分代码
safeDir := "folder1"
foldersToDel := []string{}
_, foldersToDel = getSafePath(end, safeDir, foldersToDel)

for i := len(foldersToDel) - 1; i >= 0; i-- {
	if err := os.Remove(foldersToDel[i]); err != nil {
		panic(err)
	}
}

如果这对你有帮助,或者你还有其他需要处理的场景,请告诉我。

英文:

Let me see if I can help you. First I want to say that this solution can be improved for sure! BTW, it's the best that I was able to do, and hope you find some insights for achieving what you need. Here, I'm going to show you the scenarios managed.

Scenario 1

Input:

  • folder1/folder2/folder3/folder4/file1.txt

Output:

  • folder1/file1.txt

Scenario 2

Input:

  • folder1/folder2/folder3/folder4/file1.txt
  • folder1/folder5/

Output:

  • folder1/file1.txt

Scenario 3

Input:

  • folder1/folder2/folder3/folder4/file1.txt
  • folder1/folder5/file2.txt

Output:

  • folder1/file1.txt
  • folder1/file2.txt

Code

Here, I'm going to present you the relevant parts of my solution.

The Node struct

type Node struct {
	name     string
	isDir    bool
	children []Node
}

The getSafePath function

func getSafePath(parent Node, safePath string, foldersToDel []string) (string, []string) {
	if len(parent.children) == 1 &amp;&amp; !parent.children[0].isDir {
		fileName := filepath.Base(parent.children[0].name)
		if err := os.Rename(parent.children[0].name, fmt.Sprintf(&quot;%v/%v&quot;, safePath, fileName)); err != nil {
			panic(err)
		}
		return safePath, foldersToDel
	}

	if len(parent.children) == 1 &amp;&amp; parent.children[0].isDir {
		foldersToDel = append(foldersToDel, parent.children[0].name)
		newChildren := []Node{}
		newChildren = append(newChildren, parent.children[0].children...)
		parent.children = newChildren

		safePath, foldersToDel = getSafePath(parent, safePath, foldersToDel)
		return safePath, foldersToDel
	}

	for _, v := range parent.children {
		if !v.isDir {
			fileName := filepath.Base(v.name)
			if err := os.Rename(v.name, fmt.Sprintf(&quot;%v/%v&quot;, safePath, fileName)); err != nil {
				panic(err)
			}
		} else {
			foldersToDel = append(foldersToDel, v.name)
			safePath, foldersToDel = getSafePath(v, safePath, foldersToDel)
		}
	}

	return safePath, foldersToDel
}

This function is responsible for moving the files that are in a leaf-level directory up.

The main.go file

// code omitted for brevity
safeDir := &quot;folder1&quot;
foldersToDel := []string{}
_, foldersToDel = getSafePath(end, safeDir, foldersToDel)

for i := len(foldersToDel) - 1; i &gt;= 0; i-- {
	if err := os.Remove(foldersToDel[i]); err != nil {
		panic(err)
	}
}

Let me know if this helps or there are other scenarios that you've to manage.

huangapple
  • 本文由 发表于 2022年11月24日 18:09:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/74558954.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定