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
}
CodePudding user response:
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)
}
CodePudding user response:
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.