4
\$\begingroup\$

The Folder struct definition below is given:

type Folder struct { 
    ID int
    emailCount int
    childFolderIDs []int
}

Description of the fields are:

  • Id : folder id
  • emailCount : number of emails in the folder
  • childFolderIDS : if the folder has subfolders, this list contains folder IDS of the subfolders.

I am trying to write a function:

  • Function is taking two parameters:
    • First parameter is a list of all folders in the email application
        var folders = []Folder{  
              Folder{1, 30, []int{2, 4}},  
              Folder{2, 10, []int{3}},  
              Folder{4, 60, []int{}},  
              Folder{3, 20, []int{}},  
        }  
      
    • Second parameter is folder id of a folder
  • Function will return a total number of emails with ID=Folder Id and all of its children recursively.

For example:

If the second parameter is:

  • four, then the function should return 60 (there is no subfolder)

  • two, then the function should return 30

My code is below:

      package main

      import "fmt"

      type Folder struct {
          ID int
          emailCount int
          childFolderIDs []int
      }

      func InsertIntoMap(data_arr []Folder) map[int]Folder {
          var retval = make(map[int]Folder)
          var child_folders []int
          for _, elem := range data_arr {
            child_folders = elem.childFolderIDs
            var folder Folder 
            folder.ID = elem.ID
            folder.emailCount = elem.emailCount
            folder.childFolderIDs = child_folders
            retval[elem.ID] = folder
          }    

          return retval
      }

    func GetTotalEmailCount(p_map map[int]Folder, folder_id int) int {
          total := 0
          var child_folders []int
          child_folders = p_map[folder_id].childFolderIDs
          total = total + p_map[folder_id].emailCount

          if len(child_folders) == 0{
            return total
          }else{
            for e := range child_folders {
              return total + GetTotalEmailCount(p_map, child_folders[e])
              }          
          }

          return total
    }

      func main() {

        var folders = []Folder{
          Folder{1, 30, []int{2, 4}},
          Folder{2, 10, []int{3}},
          Folder{4, 60, []int{}},
          Folder{3, 20, []int{}},
      }

      var m = InsertIntoMap(folders)

      fmt.Println(GetTotalEmailCount(m,1))  // result is 60
      fmt.Println(GetTotalEmailCount(m,2))  // result is 30
      fmt.Println(GetTotalEmailCount(m,3))  // result is 20
      fmt.Println(GetTotalEmailCount(m,4))  // result is 60

      }
```
\$\endgroup\$
  • 1
    \$\begingroup\$ Welcome to CodeReview. The indentation of your code is inconsistent, and part of your example (Second parameter is:) ended up in a code block. You may want to fix this before reviewing starts. (Code in the question shall stay unchanged conce you got answers. Or serious time passed since asking (- 24 hours?)) \$\endgroup\$ – greybeard Aug 3 at 8:36
2
\$\begingroup\$

First, your code doesn't give correnct answers; for (m, 1) the answer should be 120 (30 in ./1, +10 in ./1/2, +20 in ./1/2/3, +60 in ./1/4; you never count more than a single entry in the sub-folder slice).

Also, you should run go fmt (or goimports) on your code (perhaps you did and that was lost when posting). Also, adding a Go Playground link to your original code in posts is helpful.

You mention that you wanted to create a function taking two parameters, the first being []Folder but your code instead converts the slice into a map and uses that as the first argument. Which did you intend? When you have a function or functions that are answering questions based on an argument you could consider making a method. For example, perhaps something like:

type Folders []Folder
// or
type Folders struct {
    list []Folder
    byID map[int]Folder
}

func (f Folders) EMailCount(folder int) int {
    // code that uses f and folder as arguments
}

You use snake_case for some of your identifiers and camelCase for others; idiomatic Go code uses camelCase for all identifiers.

In your InsertIntoMap function:

  • When you make the map you know how many entries you expect so you should include that number to make as a size hint (e.g. make(map[int]Folder, len(data))). If the size is large this avoids the having to dynamically re-size the map as you add entries.
  • You name the map retval, I'd instead name it based on what contains (a map of folders by ID) rather than it happens to be the return value. I'd call it just m or byID.
  • Within the loop you effectively copy the element elem to the variable folder. You can do this with just folder := elem but at that point the entire loop body can just be rewritten as retval[elem.ID] = elem.

In your GetTotalEmailCount function:

  • Normally in Go instead of:

    var foo []int
    foo = something[bar].foo
    

    you'll just see:

    foo := something[bar].foo
    

    not only is it shorter but it can make future code changes easier. In the latter if the field foo changes type you don't need to edit the type of the variable foo to match. (By the way, shorter isn't always better. Clarity is more important than conciseness).

  • Idiomatic Go code tends avoid indenting code by using early returns (see https://github.com/golang/go/wiki/CodeReviewComments#indent-error-flow). You return if len(…) == 0 so you should just drop the else clause and remove the indent (golint will suggest this). E.g, instead of:

    if someCondition {
        return something
    } else {
        // other code
        //     possibly with more conditional/loop indenting
    }
    

    it would be:

    if someCondition {
        return something
    }
    
    // other code
    //     possibly with more conditional/loop indenting
    
  • In this specific case, you don't even need the conditional since for range loops don't do anything on empty/nil slices the following return total line is sufficient.

  • It's in this for loop your your bug exists. On the first iteration you stop looping and return the total of this folder and it's first child without iterating to the next child.

    return total + GetTotalEmailCount(p_map, child_folders[e])
    

    should be:

    total += GetTotalEmailCount(p_map, child_folders[e])
    

Without changing the basic structure of your code, all the above gives something like this (Go Playground):

package main

import "fmt"

type Folder struct {
    ID             int
    emailCount     int
    childFolderIDs []int
}

func InsertIntoMap(data []Folder) map[int]Folder {
    m := make(map[int]Folder, len(data))
    for _, e := range data {
        m[e.ID] = e
    }
    return m
}

func GetTotalEmailCount(m map[int]Folder, folder int) int {
    children := m[folder].childFolderIDs
    total := m[folder].emailCount

    for e := range children {
        // BUG: doesn't detect infinite loops
        total += GetTotalEmailCount(m, children[e])
    }

    return total
}

func main() {
    var folders = []Folder{
        Folder{1, 30, []int{2, 4}},
        Folder{2, 10, []int{3}},
        Folder{4, 60, []int{}},
        Folder{3, 20, []int{}},
    }

    var m = InsertIntoMap(folders)
    fmt.Println(GetTotalEmailCount(m, 1)) // result is 120, 30+10+20+60
    fmt.Println(GetTotalEmailCount(m, 2)) // result is 30
    fmt.Println(GetTotalEmailCount(m, 3)) // result is 20
    fmt.Println(GetTotalEmailCount(m, 4)) // result is 60
}
\$\endgroup\$
  • 2
    \$\begingroup\$ This was my first Go code. Thank you for your valuable comments. \$\endgroup\$ – Code Name James Michael Aug 3 at 13:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.