Exercises

Map function with interfaces

  • Use the answer from the earlier map exercise but nowmake it generic using interfaces. Make it at least work forints and strings.

Answer

    1. package main

import "fmt"

// Define the empty interface as a type.type e interface{}

func mult2(f e) e { switch f.(type) { case int: return f.(int) * 2 case string: return f.(string) + f.(string) + f.(string) + f.(string) } return f}

func Map(n []e, f func(e) e) []e { m := make([]e, len(n)) for k, v := range n { m[k] = f(v) } return m}

func main() { m := []e{1, 2, 3, 4} s := []e{"a", "b", "c", "d"} mf := Map(m, mult2) sf := Map(s, mult2) fmt.Printf("%v\n", mf) fmt.Printf("%v\n", sf)}

Pointers

  • Suppose we have defined the following structure:
  1. type Person struct {
  2. name string
  3. age int
  4. }

What is the difference between the following two lines?

  1. var p1 Person
  2. p2 := new(Person)
  • What is the difference between the following two allocations?
  1. func Set(t *T) {
  2. x = t
  3. }

and

  1. func Set(t T) {
  2. x= &t
  3. }

Answer

  • The expression, var p1 Person allocates a Person-value to p1. The type of p1 is Person.The second line: p2 := new(Person) allocates memory and assigns a pointer to p2. The type of p2 is*Person.

  • In the first function, x points to the same thing that t does, which is the same thing that theactual argument points to. So in the second function, we have an “extra” variable containing a copy of theinteresting value. In the second function, x points to a new (heap-allocated) variable t which containsa copy of whatever the actual argument value is.

Linked List

  • Make use of the package container/list to createa (doubly) linked list. Push the values 1, 2 and 4 to the list and thenprint it.

  • Create your own linked list implementation. And perform the same actionsas above.

Answer

  • The following is the implementation of a program using doublylinked lists from container/list.
  1. package main
  2. import (
  3. "container/list"
  4. "fmt"
  5. )
  6. func main() {
  7. l := list.New()
  8. l.PushBack(1)
  9. l.PushBack(2)
  10. l.PushBack(4)
  11. for e := l.Front(); e != nil; e = e.Next() {
  12. fmt.Printf("%v\n", e.Value)
  13. }
  14. }
  • The following is a program implementing a simple doublylinked list supporting int values.
  1. package main
  2. import (
  3. "errors" 1
  4. "fmt"
  5. )
  6. type Value int 2
  7. type Node struct { 3
  8. Value
  9. prev, next *Node
  10. }
  11. type List struct {
  12. head, tail *Node
  13. }
  14. func (l *List) Front() *Node { 4
  15. return l.head
  16. }
  17. func (n *Node) Next() *Node {
  18. return n.next
  19. }
  20. func (l *List) Push(v Value) *List {
  21. n := &Node{Value: v} 5
  22. if l.head == nil { 6
  23. l.head = n
  24. } else {
  25. l.tail.next = n 7
  26. n.prev = l.tail 8
  27. }
  28. l.tail = n 9
  29. return l
  30. }
  31. var errEmpty = errors.New("List is empty")
  32. func (l *List) Pop() (v Value, err error) {
  33. if l.tail == nil { 10
  34. err = errEmpty
  35. } else {
  36. v = l.tail.Value 11
  37. l.tail = l.tail.prev 12
  38. if l.tail == nil {
  39. l.head = nil 13
  40. }
  41. }
  42. return v, err
  43. }
  44. func main() {
  45. l := new(List)
  46. l.Push(1)
  47. l.Push(2)
  48. l.Push(4)
  49. for n := l.Front(); n != nil; n = n.Next() {
  50. fmt.Printf("%v\n", n.Value)
  51. }
  52. fmt.Println()
  53. for v, err := l.Pop(); err == nil; v, err = l.Pop() {
  54. fmt.Printf("%v\n", v)
  55. }
  56. }

Import <1> the packages we will need. At <2> we declare a type for the value our list will contain,this is not strictly neccesary. And at <3> we declare a type for the each node in our list.At <4> we define the Front method for our list.When pushing, create a new Node <5> with the provided value. If the list is empty <6>,put the new node at the head. Otherwise <7> put it at the tail and make sure <8>the new node points back to the previously existing one. At <9> we re-adjust tailto the newly inserted node.

In the Pop 10 method, we return an error if the list is empty. If it is not empty 11we save the last value. And then 12 discard the last node from the list. Finally at 13we make sure the list is consistent if it becomes empty.

Cat

  • Write a program which mimics the Unix program cat.

  • Make it support the -n flag, where each line is numbered.

  • The solution to the above question given in contains a bug. Can you spot and fix it?

Answer

  • The following is implemention of cat which also supports a -n flag to number each line.
  1. package main
  2. import (
  3. "bufio"
  4. "flag"
  5. "fmt"
  6. "io" 1
  7. "os"
  8. )
  9. var numberFlag = flag.Bool("n", false, "number each line") // <<2>>
  10. func cat(r *bufio.Reader) { 3
  11. i := 1
  12. for {
  13. buf, e := r.ReadBytes('\n') 4
  14. if e == io.EOF { 5
  15. break
  16. }
  17. if *numberFlag { 6
  18. fmt.Fprintf(os.Stdout, "%5d %s", i, buf)
  19. i++
  20. } else { 7
  21. fmt.Fprintf(os.Stdout, "%s", buf)
  22. }
  23. }
  24. return
  25. }
  26. func main() {
  27. flag.Parse()
  28. if flag.NArg() == 0 {
  29. cat(bufio.NewReader(os.Stdin))
  30. }
  31. for i := 0; i < flag.NArg(); i++ {
  32. f, e := os.Open(flag.Arg(i))
  33. if e != nil {
  34. fmt.Fprintf(os.Stderr, "%s: error reading from %s: %s\n",
  35. os.Args[0], flag.Arg(i), e.Error())
  36. continue
  37. }
  38. cat(bufio.NewReader(f))
  39. }
  40. }

At 1 we include all the packages we need. Here 2 we define a new flag “n”, which defaults to off. Note that we get the help (-h) for free. Start the function 3 that actually reads the file’s contents and displays it; Read one line at the time at 4. And stop 5 if we hit the end. If we should number each line, print the line number and then the line itself 6. Otherwise 7 we could just print the line.

  • The bug show itself when the last line of the input does notcontain a newline. Or worse, when the input contains one line without aclosing newline nothing is shown at all. A better solution is the followingprogram.
  1. package main
  2. import (
  3. "bufio"
  4. "flag"
  5. "fmt"
  6. "io"
  7. "os"
  8. )
  9. var numberFlag = flag.Bool("n", false, "number each line")
  10. func cat(r *bufio.Reader) {
  11. i := 1
  12. for {
  13. buf, e := r.ReadBytes('\n')
  14. if e == io.EOF && string(buf) == "" {
  15. break
  16. }
  17. if *numberFlag {
  18. fmt.Fprintf(os.Stdout, "%5d %s", i, buf)
  19. i++
  20. } else {
  21. fmt.Fprintf(os.Stdout, "%s", buf)
  22. }
  23. }
  24. return
  25. }
  26. func main() {
  27. flag.Parse()
  28. if flag.NArg() == 0 {
  29. cat(bufio.NewReader(os.Stdin))
  30. }
  31. for i := 0; i < flag.NArg(); i++ {
  32. f, e := os.Open(flag.Arg(i))
  33. if e != nil {
  34. fmt.Fprintf(os.Stderr, "%s: error reading from %s: %s\n",
  35. os.Args[0], flag.Arg(i), e.Error())
  36. continue
  37. }
  38. cat(bufio.NewReader(f))
  39. }
  40. }

Method calls

  • Suppose we have the followingprogram. Note the package container/vector was once partof Go, but was removed when the append built-in was introduced.However, for this question this isn’t important. The package implementeda stack-like structure, with push and pop methods.
  1. package main
  2. import "container/vector"
  3. func main() {
  4. k1 := vector.IntVector{}
  5. k2 := &vector.IntVector{}
  6. k3 := new(vector.IntVector)
  7. k1.Push(2)
  8. k2.Push(3)
  9. k3.Push(4)
  10. }

What are the types of k1, k2 and k3?

  • Now, this program compiles and runs OK. All the Pushoperations work even though the variables are of a different type. Thedocumentation for Push says:

func (p *IntVector) Push(x int)Push appends x to the end of the vector.

So the receiver has to be of type *IntVector, why does the codeabove (the Push statements) work correctly then?

Answer

  • The type of k1 is vector.IntVector. Why? We usea composite literal (the {}), so we get a value of that typeback. The variable k2 is of vector.IntVector, because wetake the address (&) of the composite literal. And finallyk3 has also the type vector.IntVector, because newreturns a pointer to the type.

  • The answer is given in [go_spec] in the section “Calls”,where among other things it says:

A method call x.m() is valid if the method set of (the type of)xcontains m and the argument list can be assigned to the parameter listof m. If x is addressable and &x’s method setcontains m, x.m() is shorthand for (&x).m().

In other words because k1 is addressable and*vector.IntVector does have the Push method, thecall k1.Push(2) is translated by Go into(&k1).Push(2) which makes the type system happy again (andyou too – now you know this).14