Monday, April 19, 2021

Golang - Array

We frequently need to work with several values.

Before introducing Array, we must first understand how memory works.

Values in "Computer Memory" are stored in memory cells.

Each memory cell takes up one byte.


  | __ | __ | __ | __ | __ | __ | __ | __ | __ | __ |

            |cell| <= 1 byte


Variables can be assigned to different memory locations.


     1001 memory cell      1006 memory cell
                                    
  | __ | 0 | __ | __ | __ | __ | 0 | __ | __ | __ |
                                  
      var age1 byte          var age2 byte


An array's items are stored in contiguous memory cells.


  From 1001 to 1002, it is a single array value

       1001 1002 memory cell
            ↓   ↓
  | __ | 0 | 0 | __ | __ | __ | __ | __ | __ | __ |
         _______
            
          var ages [2]byte


Accessing values with contiguous memory cells is efficient for the CPU.

Let's look at the syntax.

The size of an array is indicated by the number enclosed in square brackets.

An array's size is equal to the sum of its items.


  [2]byte  => 2 * 1 byte = 2 bytes
  [2]int16  => 2 * 2 byte = 4 bytes

 
The most crucial thing to remember is that the array size is fixed.

We won't be able to update it later.

Uninitialized array elements will be set to "Zero Values" by Go.

Each array element is a variable with no name.

Furthermore, Go will not allow you to directly access the memory address.

Instead, Go provides a "index expression" that allows us to specify the element of an array we want to access.


  var ages [2]byte
  ages[0] = 5



Looping an array



"range" can be used to traverse across an array.
 

  for _g := range greetings {
    g += " has been changed!"
    fmt.Println(g)
  }
  
fmt.Printf("%q", greetings)

 
"range" will create a copy of the original array.

As a result, changing 'item' inside the range block has no effect on the original array.

You can alter the original array by using an index from the "range" array.

 
  for i_ := range greetings {
    greetings[i] += " has been changed!"
  }

  fmt.Printf("%q\n", greetings)

 

How to create an array?




  var greetings [3]string
  greetings[0] = "Hello"
  greetings[1] = "How are you"
  greetings[2] = "Good"


Is this the only method for creating and assigning elements to an array?

This method can be used to generate and initialize a new array with the specified values.


  var greetings = [3]string{
    "Hello",
    "How are you",
    "Good",
  }

  
We can even simplify it as seen below.

It is recommended that you use this format if you already have the element.


  greetings := [3]string{
    "Hello",
    "How are you",
    "Good",
  }


It is called "Array Literal" on the right side.

One of the composite literals is array literal.

Composite Literals are used to generate new composite values (like arrays) on the fly.

Format:

  type{
    item,
    item,
    item
  }


Go can automatically determine the length of an array by using 'ellipsis'.


  greetings := [...]string{
    "Hello",
    "How are you",
    "Good",
  }



Comparing arrays?



When the types of arrays are the same, they are similar.

Arrays are equal when their elements are equal, which means that Go will compare each array element one by one.

To illustrate the distinction, consider bellows.

Ex1:

  arr1 := [3]int{
    123,
  }
  arr2 := [3]int{
    123,
  }

  fmt.Println(arr1 == arr2)


Result:
            
true


Ex2:

  arr1 := [3]int{
    123,
  }
  arr2 := [3]int{
    143,
  }

  fmt.Println(arr1 == arr2)


Result:
            
false


Ex3:

  arr1 := [3]int{
    123,
  }
  arr2 := [4]int{
    123, 4,
  }

  fmt.Println(arr1 == arr2)


Error:
arr1 == arr2 (mismatched types [3]int and [4]int)



Assign an array to another one?



Only arrays of the same type can be assigned.

When we assign one array to another, Go creates a new memory location for the new array.

As a result, the old and new arrays are not linked.


  arr1 := [2]byte{
    12,
  }

  arr2 := arr1
  arr2[0] = 9

  fmt.Printf("%d\n", arr1)
  fmt.Printf("%d\n", arr2)


Result:

  [1 2]
  [9 2]


Memory cells:

  | __ | 1 | 2 | __ | __ | __ | 9 | 2 | __ | __ |
         _______                 _______
                                     
           arr1                    arr2



Array of array



Assume we want to calculate the sum of all the values in two arrays.
     

The simple version will look like:  

  sum := byte(0)
  cost1 := [3]byte{123}
  cost2 := [3]byte{456}

  for _c1 := range cost1 {
    sum += c1
  }

  for _c2 := range cost2 {
    sum += c2
  }

  fmt.Println(sum)

 
Go provides an additional structure for this circumstance.

It is made out of multidimensional arrays.

We referred to it as an array of arrays.

Consider the following example:

* The outer array is [2][3]bytes in size, with each element being an inside array.
* [3]btye is the value of the inner array.

    
  sum := byte(0)
  cost := [2][3]byte{
    {123},
    {456},
  }

  for _outer := range cost {
    for _inner := range outer {
      sum += inner
    }
  }

  fmt.Println(sum)



Get rid of the magic number



Take a look of this example below.

Ex:

  args := os.Args[1:]
  if len(args) != 1 {
    fmt.Println("[pass|fail]")
    return
  }

  mode := args[0]
  moods := [...][3]string{
    {
      "happy""good", "nice",
    },
    {
      "sad""bad", "terrible",
    },
  }

  rand.Seed(time.Now().UnixNano())
  n := rand.Intn(len(moods))

  switch mode {
  case "positive":
    fmt.Printf("%s\n", moods[0][n])
  case "negative":
    fmt.Printf("%s\n", moods[1][n])
  }


We found that there are two magic numbers in our program.

In our program, we discovered two magical numbers.

We should avoid utilizing magic numbers in best practice.

We can leverage Go's Keyed Element capability.

The index positions are described by keyed elements.

Each key corresponds to an array index.

    
args := os.Args[1:]
  if len(args) != 1 {
    fmt.Println("[pass|fail]")
    return
  }

  mode := args[0]
  moods := [...][3]string{
    {
      "happy""good", "nice",
    },
    {
      "sad""bad", "terrible",
    },
  }

  const (
    pass = iota
    fail
  )

  rand.Seed(time.Now().UnixNano())
  n := rand.Intn(len(moods))

  switch mode {
  case "positive":
    fmt.Printf("%s\n", moods[0][n])
  case "negative":
      fmt.Printf("%s\n", moods[fail][n])
  }


Saturday, April 17, 2021

Golang - Flow control and error handling

Short Circuit Evaluation



According to Go's documentation, the sentence "The right operand is evaluated conditionally" points out that Go will only examine the expression if it is required. 


  // Go will check the second expression
  fmt.Println(true && false)

  // Go will only check the first expression if it returns false, 
// and the rest such as second expressions are not meaningful to check.
  fmt.Println(false && true)



If Statement



Unlike other languages, "Go" does not require parenthesis (unless where absolutely necessary).

The code block of a "if statement" is executed only if its condition expression is true.

Variables specified in this code block are also visible exclusively in this code block (block scope).


Error Handling



Everything does not always go as planned.

As a result, we must decide what to do when errors occur.

As an example:

1. A network problem or a downed web server
2. Incorrect input from users (missing some arguments? or by providing the incorrect type of input)


nil is a predefined identifier, similar to true and false, and indicates that the value has not yet been initialized.

Other languages have the similar identifier like nil:

1. javascript -> null
2. python -> None
3. jave -> null

nil is the zero value for pointer-based types:

1. pointers
2. slices
3. maps
4. interfaces
5. channels

We can utilize nil in 'Go' to determine whether or not a function answer contains an error.


    valueerror = getValueAndError()

    if error != nil {
        // Instead of processing the value, handle the error.
        // Then terminate the program
        return
    }

    // If there is no error, keep going



Error value and example



To better understand why we should handle errors, consider the following example.


  // We use 'ParseInt' because we assume users will input an integer
  valueerr := strconv.ParseInt(os.Args[1], 1064)

  fmt.Println("value ", value)
  fmt.Println("err: ", err)

 
The result is coming if users input an integer 1:

  value  1
  err:  <nil>


The result is coming if users input a string "hello":

  value  0
  err:  strconv.ParseInt: parsing "hello": invalid syntax


According to this example, if we did not check the "err" and access the value directly. Then we will get the incorrect value such as 0 in this example.


Simple Statement (Short If)



The standard approach to utilize a "if statement" is shown below.


  intValueerr := strconv.ParseInt(os.Args[1], 1064)
  if err != nil {
    return
  }

  fmt.Println("result", intValue)

 
We can use Simple Statement (Short If) to adjust it.


  if intValueerr := strconv.ParseInt(os.Args[1], 1064); err == nil {
    fmt.Println("result ", intValue)
  }


More Details:

1. ;, which is the separator, separate the simple statement and the condition expression.
2. The condition expression can use the declared variables inside the simple statement.
3. Declared variables are only visible inside the if statement (and its branches)

Consider the following complicated example to better grasp the scope of a simple statement:

 
  if args := os.Args; len(args) == 1 {
    fmt.Println("Input should not be empty")
  } else if intVerr := strconv.ParseInt(args[1], 1064); err != nil {
    fmt.Println("Invalid input")
  } else {
    fmt.Println("result "intV)
  }

 
'args' can be used by 'else if', and 'intValue' from 'else if' can be used by 'else'.


Shadowing



Let's revise the preceding example:


  intV := 0

  if args := os.Args; len(args) == 1 {
    fmt.Println("Input should not be empty")
  } else if intVerr := strconv.ParseInt(args[1], 1064); err != nil {
    fmt.Println("Invalid input")
  } else {
    fmt.Println("result ", intV)
  }

  fmt.Println("intValue:", intV)


Result:

result  1
  intV: 0


Because "if statement" creates its own code block, and Go detects that the variable intValue has already been declared outside of this scope, Go will construct a variable with the same name but only visible within this scope.

It's known as variable shadowing.


Switch



The "switch statement" searches for an equal value.


  month := "January"
  switch month {
  case "January":
    fmt.Println("It is Jan")
  case "Feburary":
    fmt.Println("It is Feb")
  }


More Details:

1. 'month' is a condition expression
2. Switch's condition expression determines the type of the 'case condition'
3. Case clause creates an exclusive block only for itself
4. The default clause is executed when no cases match.

It equals the following code (using the "if statement") from the preceding example:


  month := "January"
  if month == "January" {
    fmt.Println("It is Jan")
  }

 
More Details:

Only one case code block will be executed in Go.

Unlike other programming languages, Go will automatically include a 'break' statement at the end of each case code block. Refer to this doc.

In Go, just one case code block will be run.


Multiple conditions



In the switch case, you can specify multiple conditions.


  age := 17
  switch age {
  case 161718:
    fmt.Println("Senior High School")
  case 131415:
    fmt.Println("Junior High School")
  case 789101112:
    fmt.Println("Elementary School")
  }



Bool Expression



Another switch format supported by Go is shown below.

The bool statement can be used as a switch condition statement.

   
 score := 60

  switch true {
  case score >= 85:
    fmt.Println("A")
  case score >= 75:
    fmt.Println("B")
  case score >= 75:
    fmt.Println("C")
  case score >= 55:
    fmt.Println("D")
  default:
    fmt.Println("F")
  }

  
Because the switch expression is boolean (true), the case clause can also utilize bool expression.

We can also eliminate the switch expression because Go will set it to 'true' by default.

  
 score := 60

  switch {
  case score >= 85:
    fmt.Println("A")
  case score >= 75:
    fmt.Println("B")
  case score >= 75:
    fmt.Println("C")
  case score >= 55:
    fmt.Println("D")
  default:
    fmt.Println("F")
  }

  

Fallthrough switch



Refer to this doc.

The "fallthrough" statement instructs the flow to proceed to the next case block without checking the condition.

  
  switch i := 15; {
  case i > 20:
      fmt.Println("Hi-1")
      fallthrough
  case i > 10:
      fmt.Println("Hi-2")
      fallthrough
  case i > 5:
      fmt.Println("Hi-3")
  default:
      fmt.Println("Hi-4")
  }

  
Result:

Hi-2 Hi-3



Short Switch



It is similar idea like 'Short If'.

The standard format is provided below.

To separate the simple statement (score:= 60) and switch condition (true), we need to add the separator ';'.

 
  switch score := 60; true {
  case score >= 85:
    fmt.Println("A")
  case score >= 75:
    fmt.Println("B")
  case score >= 75:
    fmt.Println("C")
  case score >= 55:
    fmt.Println("D")
  default:
    fmt.Println("F")
  }


We can use the simplified version of Go to remove the switch condition (true).


  switch score := 60; {
  case score >= 85:
    fmt.Println("A")
  case score >= 75:
    fmt.Println("B")
  case score >= 75:
    fmt.Println("C")
  case score >= 55:
    fmt.Println("D")
  default:
    fmt.Println("F")
  }


Keep in mind that the separator ';' tells Go that this is a short switch, not a standard switch.


If V.S. Switch



If your "if statement" contains hard to read/understand code, try changing it to a "switch statement."


Loops



A block of code is repeated as long as its condition is true.


  for i := 1; i < 5; i++ {
    fmt.Println(i)
  }


More Details:

1. 'i := 1' is the initial state, which will be executed only once.
2. 'i < 5' is a bool condition expression, which will be checked before entering each loop step.
3. 'i++' is the post statement, which will be run after each step of the loop.

While Go does not support "while" syntax, you can use the format below with "break".


  i := 1

  for {
    if i >= 5 {
      break
    }

    fmt.Println(i)
    i++
  }



Using continue



By using the continue statement, you can skip the rest of the current phase.


Label Statement



We may come across a circumstance where we need to break the nested loop from inner to outer.

Consider the following example, which contains two nested "for loops" (one outer and one inner).


  words := strings.Fields(corpus)
  query := os.Args[1:]

// Outer Loop
  for _q := range query {
// Inner Loop
    for _w := range words {
      if q == w {
        fmt.Println("Found it", w)
        break
      }
    }
  }


Our goal is to exit this nested for loop when a condition in the inner loop is met.

How are we going to do it?

We can label it as "labeled break" and refer to it as such.

"Labeled break" breaks from the labeled statement.

  
 ords := strings.Fields(corpus)
  query := os.Args[1:]

outerLoop:
  for _q := range query {
    for _w := range words {
      if q == w {
        fmt.Println("Found it", w)
        break outerLoop
      }
    }
  }


The same as "labeled break," we may use "labeled continue" to exist the inner loop while continuing to execute the outer loop.

 
ords := strings.Fields(corpus)
  query := os.Args[1:]

outerLoop:
  for _q := range query {
    for _w := range words {
      if q == w {
        fmt.Println("Found it", w)
        continue outerLoop
      }
    }
  }

 
If there is a switch inside a for loop, the break command will only exist as a switch code block, and the rest of the code inside the for loop will be performed continuously.

 
ords := strings.Fields(corpus)
  query := os.Args[1:]

outerLoop:
  for _q := range query {

    for _w := range words {
switch (w) {
 case "pass":   break; }
      if q == w {
        fmt.Println("Found it", w)
        continue outerLoop
      }
    }
  }

 

Then we can use labeled break to keep the remaining code in the loop.

 
ords := strings.Fields(corpus)
  query := os.Args[1:]

outerLoop:
  for _q := range query {
innerLoop:
    for _w := range words {
switch (w) {
 case "pass":   break innerLoop }
      if q == w {
        fmt.Println("Found it", w)
        continue outerLoop
      }
    }
  }

 

Goto



Navigate to a label.

It can hop practically anywhere inside the same function to any label.

Here's an example of how to use Goto to create a for loop.


  counter := 0

myLoop:
  if counter < 5 {
    counter++
    fmt.Println("Counter++")
    goto myLoop
  }

  fmt.Println("Final Counter", counter)