英文:
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
│   └── folder2
│   └── folder3
├── folder4
│   ├── folder5
│   └── 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 := &Node{
Id: baseDir,
}
//////////
queue := make(chan *Node, 500) // Consider that there can not be any dir with > 500 depth
queue <- root
for {
if len(queue) == 0 {
break
}
data, ok := <-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 <- 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("===")
}
}
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("test-folder")
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 && !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
}
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 := "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 know if this helps or there are other scenarios that you've to manage.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论