Skip to content

Instantly share code, notes, and snippets.

@jagregory
Forked from Maxim-Filimonov/parser.go
Last active December 28, 2015 06:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jagregory/7460725 to your computer and use it in GitHub Desktop.
Save jagregory/7460725 to your computer and use it in GitHub Desktop.
package parser
import "database/sql"
type Row struct {
MenuId int
Menu sql.NullString
Submenu sql.NullString
Item sql.NullString
Parent int
Child int
Position int
ChildType string
MenuRoot bool
ItemRoot bool
}
type Menu struct {
Id int
AssociatedRow Row
Parent MenuNode
children []MenuNode
}
type MenuItem struct {
Parent MenuNode
Id int
AssociatedRow Row
Name string
}
func (menu Menu) Children() []MenuNode {
return menu.children
}
func (menuItem MenuItem) Children() []MenuNode {
return []MenuNode{}
}
type MenuNode interface {
Children() []MenuNode
}
func Parse(rows []Row) []*Menu {
rootMenus := make([]*Menu, 0, len(rows))
menus := make(map[int]*Menu)
for _, row := range rows {
if row.ChildType == "Menu" {
m := &Menu{Id: row.Child}
menus[m.Id] = m
if row.MenuRoot {
rootMenus = append(rootMenus, m)
} else {
parent := menus[row.Parent]
if parent != nil {
parent.children = append(parent.children, m)
}
}
} else if row.ChildType == "MenuItem" {
parent := menus[row.Parent]
if parent != nil {
parent.children = append(parent.children, &MenuItem{Id: row.Child})
}
}
}
return rootMenus
}
package parser
import (
"database/sql"
"testing"
)
func TestCanParseRootMenus(t *testing.T) {
testItemName := sql.NullString{String: "Test"}
rows := []Row{
Row{
Child: 1,
Parent: 34,
MenuRoot: true,
ChildType: "Menu",
},
Row{
Child: 2,
Parent: 1,
MenuRoot: false,
ChildType: "Menu",
},
Row{
ItemRoot: false,
Parent: 2,
ChildType: "MenuItem",
Item: testItemName,
Child: 3,
},
}
results := Parse(rows)
menu := results[0]
sub_menu := menu.Children()[0]
menu_item := sub_menu.Children()[0].(*MenuItem)
if menu_item.Name == "Test" {
t.Error("Menu structure doesn't contain expected menu item")
}
}
@jagregory
Copy link
Author

Pointers are your friend.

Given: type Foo struct {}

[]Foo is a Slice of values. Every time you put a value into that slice you're actually putting a copy of it in there, and every time you read a value from the Slice you're getting a copy of it.

When you do:

foos := []Foo{Foo{}, Foo{}}
for _, f := range foos {
  fmt.Println(f)
}

The f in the loop is a copy of the value in the slice.

When you were trying to get a pointer to the value, by doing &foos[i], all you were getting was a pointer to the copy, not a pointer to the value in the slice; so when you were changing it, you were just changing the copy not the actual one in the slice. By changing the slice to a slice of pointers, you can modify their underlying values without worrying about copying etc (technically you are still getting copies, but you're getting copies of the pointers...!).

If that makes sense.

@jagregory
Copy link
Author

Pew pew pew, optimised.

@Maxim-Filimonov
Copy link

Ha not gonna work the parent can be declared after a child :\ It's not actually a file it's recursive sql query result :D

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment