Golang Code Examples

Doubly Linked List

23 Jul 2014

Description

This example demonstrates how you could implement a doubly linked list in go.

An embedded struct is used to store the Value for each Node in the list.

type Value struct {
    Name string
    MilesAway int
}
type Node struct {
    Value               // Embedded struct
    next, prev  *Node
}

A map is used to track which nodes have been processed:

processed := make(map[*Node]bool)

Be sure to read the Notes section for a short discussion on the practical implications of using a linked list.


Golang Features

This golang code sample demonstrates the following go language features:

  • home grown doubly linked list
  • basic error handling
  • iterating linked list
  • map indexed by Node to track
  • embedded struct
  • pointers
  • functions
  • string package

Code Example

package main
import (
    "fmt"
    "errors"
    "strings"
)
type Value struct {
    Name string
    MilesAway int
}
type Node struct {
    Value               // Embedded struct
    next, prev  *Node
}
type List struct {
    head, tail *Node
}
func (l *List) First() *Node {
    return l.head
}
func (n *Node) Next() *Node {
    return n.next
}
func (n *Node) Prev() *Node {
    return n.prev
}
// Create new node with value
func (l *List) Push(v Value) *List {
    n := &Node{Value: v}
    if l.head == nil {
        l.head = n      // First node
    } else {
        l.tail.next = n // Add after prev last node
        n.prev = l.tail // Link back to prev last node
    }
    l.tail = n          // reset tail to newly added node
    return l
}
func (l *List) Find(name string) *Node {
    found := false
    var ret *Node = nil
    for n := l.First(); n != nil && !found; n = n.Next() {
        if n.Value.Name == name {
            found = true
            ret = n
        }
    }
    return ret
}
func (l *List) Delete(name string) bool {
    success := false
    node2del := l.Find(name)
    if node2del != nil {
        fmt.Println("Delete - FOUND: ", name)
        prev_node := node2del.prev
        next_node := node2del.next
        // Remove this node
        prev_node.next = node2del.next
        next_node.prev = node2del.prev
        success = true
    }
    return success
}
var errEmpty = errors.New("ERROR - List is empty")
// Pop last item from list
func (l *List) Pop() (v Value, err error) {
    if l.tail == nil {
        err = errEmpty
    } else {
        v = l.tail.Value
        l.tail = l.tail.prev
        if l.tail == nil {
            l.head = nil
        }
    }
    return v, err
}

func main() {
    dashes := strings.Repeat("-", 50)
    l := new(List)  // Create Doubly Linked List

    l.Push(Value{Name: "Atlanta", MilesAway: 0})
    l.Push(Value{Name: "Las Vegas", MilesAway: 1961})
    l.Push(Value{Name: "New York", MilesAway: 881})

    processed := make(map[*Node]bool)

    fmt.Println("First time through list...")
    for n := l.First(); n != nil; n = n.Next() {
        fmt.Printf("%v\n", n.Value)
        if processed[n] {
            fmt.Printf("%s as been processed\n", n.Value)
        }
        processed[n] = true
    }
    fmt.Println(dashes)
    fmt.Println("Second time through list...")
    for n := l.First(); n != nil; n = n.Next() {
        fmt.Printf("%v", n.Value)
        if processed[n] {
            fmt.Println(" has been processed")
        } else { fmt.Println() }
        processed[n] = true
    }

    fmt.Println(dashes)
    var found_node *Node
    city_to_find := "New York"
    found_node = l.Find(city_to_find)
    if found_node == nil {
        fmt.Printf("NOT FOUND: %v\n", city_to_find)
    } else {
        fmt.Printf("FOUND: %v\n", city_to_find)
    }

    city_to_find = "Chicago"
    found_node = l.Find(city_to_find)
    if found_node == nil {
        fmt.Printf("NOT FOUND: %v\n", city_to_find)
    } else {
        fmt.Printf("FOUND: %v\n", city_to_find)
    }

    fmt.Println(dashes)
    city_to_remove := "Las Vegas"
    successfully_removed_city := l.Delete(city_to_remove)
    if successfully_removed_city {
        fmt.Printf("REMOVED: %v\n", city_to_remove)
    } else {
        fmt.Printf("DID NOT REMOVE: %v\n", city_to_remove)
    }

    city_to_remove = "Chicago"
    successfully_removed_city = l.Delete(city_to_remove)
    if successfully_removed_city {
        fmt.Printf("REMOVED: %v\n", city_to_remove)
    } else {
        fmt.Printf("DID NOT REMOVE: %v\n", city_to_remove)
    }

    fmt.Println(dashes)
    fmt.Println("* Pop each value off list...")
    for v, err := l.Pop(); err == nil; v, err = l.Pop() {
        fmt.Printf("%v\n", v)
    }
    fmt.Println(l.Pop())  // Generate error - attempt to pop from empty list
}

Notes

A map of boolean values can be used as a set-like data structure to detect cycles in the list as follows:

processed := make(map[*Node]bool)

for n := l.First(); n != nil; n = n.Next() {
   if processed[n] {
       fmt.Printf("%s as been processed\n", n.Value)
   }
   processed[n] = true
}

The expression processed[n] is true if n has been visited, or false if n is not present. There's no need to use the two-value form to test for the presence of n in the map; the zero value default does it for us.

You might notice that that iterations are performed using the for clause, rather than range; That is mainly because the range clause provides a way to iterate over a array, slice, string, map, or channel -- not for iterating over a home-grown doubly linked list.

Here's how we iterated using the for clause:

found := false
    var ret *Node = nil
    for n := l.First(); n != nil && !found; n = n.Next() {
        if n.Value.Name == name {
            found = true
            ret = n
        }
    }

Here's how to iterate using the range clause:

 for k, v := range myMap {
     log.Printf("key=%v, value=%v", k, v)
 }

 for v := range myChannel {
     log.Printf("value=%v", v)
 }

Note that slices can be utilized, rather than linked lists, to achieve similar goals in go.

There are valid reasons to use slices (rather than linked lists):

  • Slices are faster than linked lists
  • Linked lists can't be serialized via gob encoding due to its use private variables


Golang's container/list Package

This article shows how you might implement a home grown version of what comes out of the box with Go's container/list Package.

Here's what you get in the container/list Package:

  • type Element
  • func (e *Element) Next() *Element
  • func (e *Element) Prev() *Element
  • type List
  • func New() *List
  • func (l *List) Back() *Element
  • func (l *List) Front() *Element
  • func (l *List) Init() *List
  • func (l *List) InsertAfter(v interface{}, mark *Element) *Element
  • func (l *List) InsertBefore(v interface{}, mark *Element) *Element
  • func (l *List) Len() int
  • func (l *List) MoveAfter(e, mark *Element)
  • func (l *List) MoveBefore(e, mark *Element)
  • func (l *List) MoveToBack(e *Element)
  • func (l *List) MoveToFront(e *Element)
  • func (l *List) PushBack(v interface{}) *Element
  • func (l *List) PushBackList(other *List)
  • func (l *List) PushFront(v interface{}) *Element
  • func (l *List) PushFrontList(other *List)
  • func (l *List) Remove(e *Element) interface{}

Output

First time through list...
{Atlanta 0}
{Las Vegas 1961}
{New York 881}
--------------------------------------------------
%sSecond time through list...
{Atlanta 0} has been processed
{Las Vegas 1961} has been processed
{New York 881} has been processed
--------------------------------------------------
FOUND: New York
NOT FOUND: Chicago
--------------------------------------------------
Delete - FOUND:  Las Vegas
REMOVED: Las Vegas
DID NOT REMOVE: Chicago
--------------------------------------------------
* Pop each value off list...
{New York 881}
{Atlanta 0}
{ 0} ERROR - List is empty

Process finished with exit code 0

References

comments powered by Disqus
The content of this site is licensed under the Creative Commons 3.0License and code is licensed under a BSD license