One place for hosting & domains

      Understanding

      Understanding Boolean Logic in Go


      The Boolean data type (bool) can be one of two values, either true or false. Booleans are used in programming to make comparisons and to control the flow of the program.

      Booleans represent the truth values that are associated with the logic branch of mathematics, which informs algorithms in computer science. Named for the mathematician George Boole, the word Boolean always begins with a capitalized B.

      The data type in Go for Boolean is bool, all lowercase. The values true and false will always be with a lowercase t and f respectively, as they are special values in Go.

      This tutorial will cover the basics you’ll need to understand how the bool data type works, including Boolean comparison, logical operators, and truth tables.

      Comparison Operators

      In programming, comparison operators are used to compare values and evaluate down to a single Boolean value of either true or false.

      The table below shows Boolean comparison operators.

      Operator What it means
      == Equal to
      != Not equal to
      < Less than
      > Greater than
      <= Less than or equal to
      >= Greater than or equal to

      To understand how these operators work, let’s assign two integers to two variables in a Go program:

      x := 5
      y := 8
      

      In this example, since x has the value of 5, it is less than y which has the value of 8.

      Using those two variables and their associated values, let’s go through the operators from the preceding table. In this program, you’ll ask Go to print out whether each comparison operator evaluates to either true or false. To help better understand this output, you’ll have Go also print a string to show you what it’s evaluating:

      package main
      
      import "fmt"
      
      func main() {
          x := 5
          y := 8
      
          fmt.Println("x == y:", x == y)
          fmt.Println("x != y:", x != y)
          fmt.Println("x < y:", x < y)
          fmt.Println("x > y:", x > y)
          fmt.Println("x <= y:", x <= y)
          fmt.Println("x >= y:", x >= y)
      }
      

      Output

      x == y: false x != y: true x < y: true x > y: false x <= y: true x >= y: false

      Following mathematical logic, Go has evaluated the following from the expressions:

      • Is 5 (x) equal to 8 (y)? false
      • Is 5 not equal to 8? true
      • Is 5 less than 8? true
      • Is 5 greater than 8? false
      • Is 5 less than or equal to 8? true
      • Is 5 not less than or equal to 8? false

      Although integers were used here, you could substitute them with float values.

      Strings can also be used with Boolean operators. They are case-sensitive unless you use an additional string method.

      You can look at how strings are compared in practice:

      Sammy := "Sammy"
      sammy := "sammy"
      
      fmt.Println("Sammy == sammy: ", Sammy == sammy)
      

      Output

      Sammy == sammy: false

      The string Sammy is not equal to the string sammy, because they are not exactly the same; one starts with an uppercase S and the other with a lowercase s. But, if you add another variable that is assigned the value of Sammy, then they will evaluate to equal:

      Sammy := "Sammy"
      sammy := "sammy"
      alsoSammy := "Sammy"
      
      fmt.Println("Sammy == sammy: ", Sammy == sammy)
      fmt.Println("Sammy == alsoSammy", Sammy == alsoSammy)
      

      Output

      Sammy == sammy: false Sammy == alsoSammy true

      You can also use the other comparison operators including > and < to compare two strings. Go will compare these strings lexicographically using the ASCII values of the characters.

      You can also evaluate Boolean values with comparison operators:

      t := true
      f := false
      
      fmt.Println("t != f: ", t != f)
      

      Output

      t != f: true

      The preceding code block evaluated that true is not equal to false.

      Note the difference between the two operators = and ==.

      x = y   // Sets x equal to y
      x == y  // Evaluates whether x is equal to y
      

      The first = is the assignment operator, which will set one value equal to another. The second, ==, is a comparison operator and will evaluate whether two values are equal.

      Logical Operators

      There are two logical operators that are used to compare values. They evaluate expressions down to Boolean values, returning either true or false. These operators are &&, ||, and !, and are defined in the list below:

      • && (x && y) is the and operator. It is true if both statements are true.
      • || (x || y) is the or operator. It is true if at least one statement is true.
      • ! (!x) is the not operator. It is true only if the statement is false.

      Logical operators are typically used to evaluate whether two or more expressions are true or not true. For example, they can be used to determine if the grade is passing and that the student is registered in the course, and if both cases are true, then the student will be assigned a grade in the system. Another example would be to determine whether a user is a valid active customer of an online shop based on whether they have store credit or have made a purchase in the past 6 months.

      To understand how logical operators work, let’s evaluate three expressions:

      fmt.Println((9 > 7) && (2 < 4))   // Both original expressions are true
      fmt.Println((8 == 8) || (6 != 6)) // One original expression is true
      fmt.Println(!(3 <= 1))            // The original expression is false
      

      Output

      true true true

      In the first case, fmt.Println((9 > 7) && (2 < 4)), both 9 > 7 and 2 < 4 needed to evaluate to true since the and operator was used.

      In the second case, fmt.Println((8 == 8) || (6 != 6)), since 8 == 8 evaluated to true, it did not make a difference that 6 != 6 evaluates to false because the or operator was used. If you had used the and operator, this would evaluate to false.

      In the third case, fmt.Println(!(3 <= 1)), the not operator negates the false value that 3 <=1 returns.

      Let’s substitute floats for integers and aim for false evaluations:

      fmt.Println((-0.2 > 1.4) && (0.8 < 3.1))  // One original expression is false
      fmt.Println((7.5 == 8.9) || (9.2 != 9.2)) // Both original expressions are false
      fmt.Println(!(-5.7 <= 0.3))               // The original expression is true
      

      In this example:

      • and must have at least one false expression evaluate to false.
      • or must have both expressions evaluate to false.
      • ! must have its inner expression be true for the new expression to evaluate to false.

      If these results seem unclear to you, go through some truth tables for further clarification.

      You can also write compound statements using &&, ||, and !:

      !((-0.2 > 1.4) && ((0.8 < 3.1) || (0.1 == 0.1)))
      

      Take a look at the inner-most expression first: (0.8 < 3.1) || (0.1 == 0.1). This expression evaluates to true because both mathematical statements are true.

      Next, Go takes the returned value true and combines it with the next inner expression: (-0.2 > 1.4) && (true). This example returns false because the mathematical statement -0.2 > 1.4 is false, and (false) and (true) returns false.

      Finally, we have the outer expression: !(false), which evaluates to true, so the final returned value if we print this statement out is:

      Output

      true

      The logical operators &&, ||, and ! evaluate expressions and return Boolean values.

      Truth Tables

      There is a lot to learn about the logic branch of mathematics, but you can selectively learn some of it to improve your algorithmic thinking when programming.

      The following are truth tables for the comparison operator ==, and each of the logic operators &&, || and !. While you may be able to reason them out, it can also be helpful to memorize them as that can make your programming decision-making process quicker.

      == (equal) Truth Table

      x == y Returns
      true == true true
      true == false false
      false == true false
      false == false true

      && (and) Truth Table

      x and y Returns
      true and true true
      true and false false
      false and true false
      false and false false

      || (or) Truth Table

      x or y Returns
      true or true true
      true or false true
      false or true true
      false or false false

      ! (not) Truth Table

      not x Returns
      not true false
      not false true

      Truth tables are common mathematical tables used in logic, and are useful to keep in mind when constructing algorithms (instructions) in computer programming.

      Using Boolean Operators for Flow Control

      To control the stream and outcomes of a program in the form of flow control statements, you can use a condition followed by a clause.

      A condition evaluates down to a Boolean value of true or false, presenting a point where a decision is made in the program. That is, a condition would tell you if something evaluates to true or false.

      The clause is the block of code that follows the condition and dictates the outcome of the program. That is, it is the “do this” part of the construction “If x is true, then do this.”

      The code block below shows an example of comparison operators working in tandem with conditional statements to control the flow of a Go program:

      if grade >= 65 {                 // Condition
          fmt.Println("Passing grade") // Clause
      } else {
          fmt.Println("Failing grade")
      }
      

      This program will evaluate whether each student’s grade is passing or failing. In the case of a student with a grade of 83, the first statement will evaluate to true, and the print statement of Passing grade will be triggered. In the case of a student with a grade of 59, the first statement will evaluate to false, so the program will move on to execute the print statement tied to the else expression: Failing grade.

      Boolean operators present conditions that can be used to decide the eventual outcome of a program through flow control statements.

      Conclusion

      This tutorial went through comparison and logical operators belonging to the Boolean type, as well as truth tables and using Booleans for program flow control.



      Source link

      Understanding Data Types in Go


      Introduction

      Data types specify the kinds of values that particular variables will store when you are writing a program. The data type also determines what operations can be performed on the data.

      In this article, we will go over the important data types native to Go. This is not an exhaustive investigation of data types, but will help you become familiar with what options you have available to you in Go. Understanding some basic data types will enable you to write clearer code that performs efficiently.

      Background

      One way to think about data types is to consider the different types of data that we use in the real world. An example of data in the real world are numbers: we may use whole numbers (0, 1, 2, …), integers (…, -1, 0, 1, …), and irrational numbers (π), for example.

      Usually, in math, we can combine numbers from different types, and get some kind of an answer. We may want to add 5 to π, for example:

      5 + π
      

      We can either keep the equation as the answer to account for the irrational number, or round π to a number with an abbreviated number of decimal places, and then add the numbers together:

      5 + π = 5 + 3.14 = 8.14 
      

      But, if we start to try to evaluate numbers with another data type, such as words, things start to make less sense. How would we solve for the following equation?

      shark + 8
      

      For computers, each data type is quite different—like words and numbers. As a result we have to be careful about how we use varying data types to assign values and how we manipulate them through operations.

      Integers

      Like in math, integers in computer programming are whole numbers that can be positive, negative, or 0 (…, -1, 0, 1, …). In Go, an integer is known as an int. As with other programming languages, you should not use commas in numbers of four digits or more, so when you write 1,000 in your program, write it as 1000.

      We can print out an integer in a simple way like this:

      fmt.Println(-459)
      

      Output

      -459

      Or, we can declare a variable, which in this case is a symbol of the number we are using or manipulating, like so:

      var absoluteZero int = -459
      fmt.Println(absoluteZero)
      

      Output

      -459

      We can do math with integers in Go, too. In the following code block, we will use the := assignment operator to declare and instantiate the variable sum:

      sum := 116 - 68
      fmt.Println(sum)
      

      Output

      48

      As the output shows, the mathematical operator - subtracted the integer 68 from 116, resulting in 48. You’ll learn more about variable declaration in the Declaring Data Types for Variables section.

      Integers can be used in many ways within Go programs. As you continue to learn about Go, you’ll have a lot of opportunities to work with integers and build upon your knowledge of this data type.

      Floating-Point Numbers

      A floating-point number or a float is used to represent real numbers that cannot be expressed as integers. Real numbers include all rational and irrational numbers, and because of this, floating-point numbers can contain a fractional part, such as 9.0 or -116.42. For the purposes of thinking of a float in a Go program, it is a number that contains a decimal point.

      Like we did with integers, we can print out a floating-point number in a simple way like this:

      fmt.Println(-459.67)
      

      Output

      -459.67

      We can also declare a variable that stands in for a float, like so:

      absoluteZero := -459.67
      fmt.Println(absoluteZero)
      

      Output

      -459.67

      Just like with integers, we can do math with floats in Go, too:

      var sum = 564.0 + 365.24
      fmt.Println(sum)
      

      Output

      929.24

      With integers and floating-point numbers, it is important to keep in mind that 3 ≠ 3.0, as 3 refers to an integer while 3.0 refers to a float.

      Sizes of Numeric Types

      In addition to the distinction between integers and floats, Go has two types of numeric data that are distinguished by the static or dynamic nature of their sizes. The first type is an architecture-independent type, which means that the size of the data in bits does not change, regardless of the machine that the code is running on.

      Most system architectures today are either 32 bit or 64 bit. For instance, you may be developing for a modern Windows laptop, on which the operating system runs on a 64-bit architecture. However, if you are developing for a device like a fitness watch, you may be working with a 32-bit architecture. If you use an architecture-independent type like int32, regardless of the architecture you compile for, the type will have a constant size.

      The second type is an implementation-specific type. In this type, the bit size can vary based on the architecture the program is built on. For instance, if we use the int type, when Go compiles for a 32-bit architecture, the size of the data type will be 32 bits. If the program is compiled for a 64-bit architecture, the variable will be 64 bits in size.

      In addition to data types having different sizes, types like integers also come in two basic types: signed and unsigned. An int8 is a signed integer, and can have a value from -128 to 127. A uint8 is an unsigned integer, and can only have a positive value of 0 to 255.

      The ranges are based on the bit size. For binary data, 8 bits can represent a total of 256 different values. Because an int type needs to support both positive and negative values, an 8-bit integer (int8) will have a range of -128 to 127, for a total of 256 unique possible values.

      Go has the following architecture-independent integer types:

      uint8       unsigned  8-bit integers (0 to 255)
      uint16      unsigned 16-bit integers (0 to 65535)
      uint32      unsigned 32-bit integers (0 to 4294967295)
      uint64      unsigned 64-bit integers (0 to 18446744073709551615)
      int8        signed  8-bit integers (-128 to 127)
      int16       signed 16-bit integers (-32768 to 32767)
      int32       signed 32-bit integers (-2147483648 to 2147483647)
      int64       signed 64-bit integers (-9223372036854775808 to 9223372036854775807)
      

      Floats and complex numbers also come in varying sizes:

      float32     IEEE-754 32-bit floating-point numbers
      float64     IEEE-754 64-bit floating-point numbers
      complex64   complex numbers with float32 real and imaginary parts
      complex128  complex numbers with float64 real and imaginary parts
      

      There are also a couple of alias number types, which assign useful names to specific data types:

      byte        alias for uint8
      rune        alias for int32
      

      The purpose of the byte alias is to make it clear when your program is using bytes as a common computing measurement in character string elements, as opposed to small integers unrelated to the byte data measurement. Even though byte and uint8 are identical once the program is compiled, byte is often used to represent character data in numeric form, whereas uint8 is intended to be a number in your program.

      The rune alias is a bit different. Where byte and uint8 are exactly the same data, a rune can be a single byte or four bytes, a range determined by int32. A rune is used to represent a Unicode character, whereas only ASCII characters can be represented solely by an int32 data type.

      In addition, Go has the following implementation-specific types:

      uint     unsigned, either 32 or 64 bits
      int      signed, either 32 or 64 bits
      uintptr  unsigned integer large enough to store the uninterpreted bits of a pointer value 
      

      Implementation-specific types will have their size defined by the architecture the program is compiled for.

      Picking Numeric Data Types

      Picking the correct size usually has more to do with performance for the target architecture you are programming for than the size of the data you are working with. However, without needing to know the specific ramifications of performance for your program, you can follow some of these basic guidelines when first starting out.

      As discussed earlier in this article, there are architecture-independent types, and implementation-specific types. For integer data, it’s common in Go to use the implementation types like int or uint instead of int64 or uint64. This will typically result in the fastest processing speed for your target architecture. For instance, if you use an int64 and compile to a 32-bit architecture, it will take at least twice as much time to process those values as it takes additional CPU cycles to move the data across the architecture. If you used an int instead, the program would define it as a 32-bit size for a 32-bit architecture, and would be significantly faster to process.

      If you know you won’t exceed a specific size range, then picking an architecture-independent type can both increase speed and decrease memory usage. For example, if you know your data won’t exceed the value of 100, and will only be a positive number, then choosing a uint8 would make your program more efficient as it will require less memory.

      Now that we have looked at some of the possible ranges for numeric data types, let’s look at what will happen if we exceed those ranges in our program.

      Overflow vs. Wraparound

      Go has the potential to both overflow a number and wraparound a number when you try to store a value larger than the data type was designed to store, depending on if the value is calculated at compile time or at runtime. A compile time error happens when the program finds an error as it tries to build the program. A runtime error happens after the program is compiled, while it is actually executing.

      In the following example, we set maxUint32 to its maximum value:

      package main
      
      import "fmt"
      
      func main() {
          var maxUint32 uint32 = 4294967295 // Max uint32 size
          fmt.Println(maxUint32)
      }
      

      It will compile and run with the following result:

      Output

      4294967295

      If we add 1 to the value at runtime, it will wraparound to 0:

      Output

      0

      On the other hand, let’s change the program to add 1 to the variable when we assign it, before compile time:

      package main
      
      import "fmt"
      
      func main() {
          var maxUint32 uint32 = 4294967295 + 1
          fmt.Println(maxUint32)
      
      }
      

      At compile time, if the compiler can determine a value will be too large to hold in the data type specified, it will throw an overflow error. This means that the value calculated is too large for the data type you specified.

      Because the compiler can determine it will overflow the value, it will now throw an error:

      Output

      prog.go:6:36: constant 4294967296 overflows uint32

      Understanding the boundaries of your data will help you avoid potential bugs in your program in the future.

      Now that we have covered numeric types, let’s look at how to store boolean values.

      Booleans

      The boolean data type can be one of two values, either true or false, and is defined as bool when declaring it as a data type. Booleans are used to represent the truth values that are associated with the logic branch of mathematics, which informs algorithms in computer science.

      The values true and false will always be with a lowercase t and f respectively, as they are pre-declared identifiers in Go.

      Many operations in math give us answers that evaluate to either true or false:

      • greater than
        • 500 > 100 true
        • 1 > 5 false
      • less than
        • 200 < 400 true
        • 4 < 2 false
      • equal
        • 5 = 5 true
        • 500 = 400 false

      Like with numbers, we can store a boolean value in a variable:

      myBool := 5 > 8
      

      We can then print the boolean value with a call to the fmt.Println() function:

      fmt.Println(myBool)
      

      Since 5 is not greater than 8, we will receive the following output:

      Output

      false

      As you write more programs in Go, you will become more familiar with how booleans work and how different functions and operations evaluating to either true or false can change the course of the program.

      Strings

      A string is a sequence of one or more characters (letters, numbers, symbols) that can be either a constant or a variable. Strings exist within either back quotes ` or double quotes " in Go and have different characteristics depending on which quotes you use.

      If you use the back quotes, you are creating a raw string literal. If you use the double quotes, you are creating an interpreted string literal.

      Raw String Literals

      Raw string literals are character sequences between back quotes, often called back ticks. Within the quotes, any character will appear just as it is displayed between the back quotes, except for the back quote character itself.

      a := `Say "hello" to Go!`
      fmt.Println(a)
      

      Output

      Say "hello" to Go!

      Usually, backslashes are used to represent special characters in strings. For example, in an interpreted string, n would represent a new line in a string. However, backslashes have no special meaning inside of raw string literals:

      a := `Say "hello" to Go!n`
      fmt.Println(a)
      

      Because the backslash has no special meaning in a string literal, it will actually print out the value of n instead of making a new line:

      Output

      Say "hello" to Go!n

      Raw string literals may also be used to create multiline strings:

      a := `This string is on 
      multiple lines
      within a single back 
      quote on either side.`
      fmt.Println(a)
      

      Output

      This string is on multiple lines within a single back quote on either side.

      In the preceding code blocks, the new lines were carried over literally from input to output.

      Interpreted String Literals

      Interpreted string literals are character sequences between double quotes, as in "bar". Within the quotes, any character may appear except newline and unescaped double quotes. To show double quotes in an interpreted string, you can use the backslash as an escape character, like so:

      a := "Say "hello" to Go!"
      fmt.Println(a)
      

      Output

      Say "hello" to Go!

      You will almost always use interpreted string literals because they allow for escape characters within them. For more on working with strings, check out An Introduction to Working with Strings in Go.

      Strings with UTF-8 Characters

      UTF-8 is an encoding scheme used to encode variable width characters into one to four bytes. Go supports UTF-8 characters out of the box, without any special setup, libaries, or packages. Roman characters such as the letter A can be represented by an ASCII value such as the number 65. However, with special characters such as an international character of , UTF-8 would be required. Go uses the rune alias type for UTF-8 data.

      a := "Hello, 世界"
      

      You can use the range keyword in a for loop to index through any string in Go, even a UTF-8 string. for loops and range will be covered in more depth later in the series; for now, it’s important to know that we can use this to count the bytes in a given string:

      package main
      
      import "fmt"
      
      func main() {
          a := "Hello, 世界"
          for i, c := range a {
              fmt.Printf("%d: %sn", i, string(c))
          }
          fmt.Println("length of 'Hello, 世界': ", len(a))
      }
      

      In the above code block, we declared the variable a and assigned the value of Hello, 世界 to it. The text assigned has UTF-8 characters in it.

      We then used a standard for loop as well as the range keyword. In Go, the range keyword will index through a string returning one character at a time, as well as the byte index the character is at in the string.

      Using the fmt.Printf function, we provided a format string of %d: %sn. %d is the print verb for a digit (in this case an integer), and %s is the print verb for a string. We then provided the values of i, which is the current index of the for loop, and c, which is the current character in the for loop.

      Finally, we printed the entire length of the variable a by using the builtin len function.

      Earlier, we mentioned that a rune is an alias for int32 and can be made up of one to four bytes. The character takes three bytes to define and the index moves accordingly when ranging through the UTF-8 string. This is the reason that i is not sequential when it is printed out.

      Output

      0: H 1: e 2: l 3: l 4: o 5: , 6: 7: 世 10: 界 length of 'Hello, 世界': 13

      As you can see, the length is longer than the number of times it took to range over the string.

      You won’t always be working with UTF-8 strings, but when you are, you’ll now understand why they are runes and not a single int32.

      Declaring Data Types for Variables

      Now that you know about the different primitive data types, we will go over how to assign these types to variables in Go.

      In Go, we can define a variable with the keyword var followed by the name of the variable and the data type desired.

      In the following example we will declare a variable called pi of type float64.

      The keyword var is the first thing declared:

      var pi float64
      

      Followed by the name of our variable, pi:

      var pi float64
      

      And finally the data type float64:

      var pi float64
      

      We can optionally specify an initial value as well, such as 3.14:

      var pi float64 = 3.14
      

      Go is a statically typed language. Statically typed means that each statement in the program is checked at compile time. It also means that the data type is bound to the variable, whereas in dynamically linked languages, the data type is bound to the value.

      For example, in Go, the type is declared when declaring a variable:

      var pi float64 = 3.14
      var week int = 7
      

      Each of these variables could be a different data type if you declared them differently.

      This is different from a language like PHP, where the data type is associated to the value:

      $s = "sammy";         // $s is automatically a string
      $s = 123;             // $s is automatically an integer
      

      In the preceding code block, the first $s is a string because it is assigned the value "sammy", and the second is an integer because it has the value 123.

      Next, let’s look at more complex data types like arrays.

      Arrays

      An array is an ordered sequence of elements. The capacity of an array is defined at creation time. Once an array has allocated its size, the size can no longer be changed. Because the size of an array is static, it means that it only allocates memory once. This makes arrays somewhat rigid to work with, but increases performance of your program. Because of this, arrays are typically used when optimizing programs. Slices, covered next, are more flexible, and constitute what you would think of as arrays in other languages.

      Arrays are defined by declaring the size of the array, then the data type with the values defined between curly brackets { }.

      An array of strings looks like this:

      [3]string{"blue coral", "staghorn coral", "pillar coral"}
      

      We can store an array in a variable and print it out:

      coral := [3]string{"blue coral", "staghorn coral", "pillar coral"}
      fmt.Println(coral)
      

      Output

      [blue coral staghorn coral pillar coral]

      As mentioned before, slices are similar to arrays, but are much more flexible. Let’s take a look at this mutable data type.

      Slices

      A slice is an ordered sequence of elements that can change in length. Slices can increase their size dynamically. When you add new items to a slice, if the slice does not have enough memory to store the new items, it will request more memory from the system as needed. Because a slice can be expanded to add more elements when needed, they are more commonly used than arrays.

      Slices are defined by declaring the data type preceded by an opening and closing square bracket [] and having values between curly brackets { }.

      A slice of integers looks like this:

      []int{-3, -2, -1, 0, 1, 2, 3}
      

      A slice of floats looks like this:

      []float64{3.14, 9.23, 111.11, 312.12, 1.05}
      

      A slice of strings looks like this:

      []string{"shark", "cuttlefish", "squid", "mantis shrimp"}
      

      Let’s define our slice of strings as seaCreatures:

      seaCreatures := []string{"shark", "cuttlefish", "squid", "mantis shrimp"}
      

      We can print them out by calling the variable:

      fmt.Println(seaCreatures)
      

      The output will look exactly like the list that we created:

      Output

      [shark cuttlefish squid mantis shrimp]

      We can use the append keyword to add an item to our slice. The following command will add the string value of seahorse to the slice:

      seaCreatures = append(seaCreatures, "seahorse")
      

      You can verify it was added by printing it out:

      fmt.Println(seaCreatures)
      

      Output

      [shark cuttlefish squid mantis shrimp seahorse]

      As you can see, if you need to manage an unknown size of elements, a slice will be much more versatile than an array.

      Maps

      The map is Go’s built-in hash or dictionary type. Maps use keys and values as a pair to store data. This is useful in programming to quickly look up values by an index, or in this case, a key. For instance, you may want to keep a map of users, indexed by their user ID. The key would be the user ID, and the user object would be the value. A map is constructed by using the keyword map followed by the key data type in square brackets [ ], followed by the value data type and the key value pairs in curly braces.

      map[key]value{}
      

      Typically used to hold data that are related, such as the information contained in an ID, a map looks like this:

      map[string]string{"name": "Sammy", "animal": "shark", "color": "blue", "location": "ocean"}
      

      You will notice that in addition to the curly braces, there are also colons throughout the map. The words to the left of the colons are the keys. Keys can be any comparable type in Go. Comparable types are primitive types like strings, ints, etc. A primitive type is defined by the language, and not built from combining any other types. While they can be user-defined types, it’s considered best practice to keep them simple to avoid programming errors. The keys in the dictionary above are: name, animal, color, and location.

      The words to the right of the colons are the values. Values can be comprised of any data type. The values in the dictionary above are: Sammy, shark, blue, and ocean.

      Let’s store the map inside a variable and print it out:

      sammy := map[string]string{"name": "Sammy", "animal": "shark", "color": "blue", "location": "ocean"}
      fmt.Println(sammy)
      

      Output

      map[animal:shark color:blue location:ocean name:Sammy]

      If we want to isolate Sammy’s color, we can do so by calling sammy["color"]. Let’s print that out:

      fmt.Println(sammy["color"])
      

      Output

      blue

      As maps offer key-value pairs for storing data, they can be important elements in your Go program.

      Conclusion

      At this point, you should have a better understanding of some of the major data types that are available for you to use in Go. Each of these data types will become important as you develop programming projects in the Go language.



      Source link

      Understanding Maps in Go


      Most modern programming languages have the concept of a dictionary or a hash type. These types are commonly used to store data in pairs with a key that maps to a value.

      In Go, the map data type is what most programmers would think of as the dictionary type. It maps keys to values, making key-value pairs that are a useful way to store data in Go. A map is constructed by using the keyword map followed by the key data type in square brackets [ ], followed by the value data type. The key-value pairs are then placed inside curly braces on either side { }:

      map[key]value{}
      

      You typically use maps in Go to hold related data, such as the information contained in an ID. A map with data looks like this:

      map[string]string{"name": "Sammy", "animal": "shark", "color": "blue", "location": "ocean"}
      

      In addition to the curly braces, there are also colons throughout the map that connect the key-value pairs. The words to the left of the colons are the keys. Keys can be any comparable type in Go, like strings, ints, and so on.

      The keys in the example map are:

      • "name"
      • "animal"
      • "color"
      • "location"

      The words to the right of the colons are the values. Values can be any data type. The values in the example map are:

      • "Sammy"
      • "shark"
      • "blue"
      • "ocean"

      Like the other data types, you can store the map inside a variable, and print it out:

      sammy := map[string]string{"name": "Sammy", "animal": "shark", "color": "blue", "location": "ocean"}
      fmt.Println(sammy)
      

      This would be your output:

      Output

      map[animal:shark color:blue location:ocean name:Sammy]

      The order of the key-value pairs may have shifted. In Go, the map data type is unordered. Regardless of the order, the key-value pairs will remain intact, enabling you to access data based on their relational meaning.

      Accessing Map Items

      You can call the values of a map by referencing the related keys. Since maps offer key-value pairs for storing data, they can be important and useful items in your Go program.

      If you want to isolate Sammy’s username, you can do so by calling sammy["name"]; the variable holding your map and the related key. Let’s print that out:

      fmt.Println(sammy["name"])
      

      And receive the value as output:

      Output

      Sammy

      Maps behave like a database; instead of calling an integer to get a particular index value as you would with a slice, you assign a value to a key and call that key to get its related value.

      By invoking the key "name" you receive the value of that key, which is "Sammy".

      Similarly you can call the remaining values in the sammy map using the same format:

      fmt.Println(sammy["animal"])
      // returns shark
      
      fmt.Println(sammy["color"])
      // returns blue
      
      fmt.Println(sammy["location"])
      // returns ocean
      

      By making use of the key-value pairs in map data types, you can reference keys to retrieve values.

      Keys and Values

      Unlike some programming languages, Go does not have any convenience functions to list out the keys or values of a map. An example of this would be Python’s .keys() method for dictionaries. It does, however, allow for iteration by using the range operator:

      for key, value := range sammy {
          fmt.Printf("%q is the key for the value %qn", key, value)
      }
      

      When ranging through a map in Go, it’ll return two values. The first value will be the key, and the second value will be the value. Go will create these variables with the correct data type. In this case, the map key was a string so key will also be a string. The value is also a string:

      Output

      "animal" is the key for the value "shark" "color" is the key for the value "blue" "location" is the key for the value "ocean" "name" is the key for the value "Sammy"

      To get a list of just the keys, you can use the range operator again. You can declare just one variable to only access the keys:

      keys := []string{}
      
      for key := range sammy {
          keys = append(keys, key)
      }
      fmt.Printf("%q", keys)
      

      The program begins by declaring a slice to store your keys in.

      The output will show only the keys of your map:

      Output

      ["color" "location" "name" "animal"]

      Again, the keys are not sorted. If you want to sort them, you use the sort.Strings function from the sort package:

      sort.Strings(keys)
      

      With this function, you’ll receive the following output:

      Output

      ["animal" "color" "location" "name"]

      You can use the same pattern to retrieve just the values in a map. In the next example, you pre-allocate the slice to avoid allocations, thus making the program more efficient:

      sammy := map[string]string{"name": "Sammy", "animal": "shark", "color": "blue", "location": "ocean"}
      
      items := make([]string, len(sammy))
      
      var i int
      
      for _, v := range sammy {
          items[i] = v
          i++
      }
      fmt.Printf("%q", items)
      

      First you declare a slice to store your keys in; since you know how many items you need, you can avoid potential memory allocations by defining the slice at the exact same size. You then declare your index variable. As you don’t want the key, you use the _ operator, when starting your loop, to ignore the key’s value. Your output would be the following:

      Output

      ["ocean" "Sammy" "shark" "blue"]

      To determine the number of items in a map, you can use the built-in len function:

      sammy := map[string]string{"name": "Sammy", "animal": "shark", "color": "blue", "location": "ocean"}
      fmt.Println(len(sammy))
      

      The output displays the number of items in your map:

      Output

      4

      Even though Go doesn’t ship with convenience functions to get keys and values, it only takes a few lines of code to retrieve the keys and values when needed.

      Checking Existence

      Maps in Go will return the zero value for the value type of the map when the requested key is missing. Because of this, you need an alternative way to differentiate a stored zero, versus a missing key.

      Let’s look up a value in a map that you know doesn’t exist and look at the value returned:

      counts := map[string]int{}
      fmt.Println(counts["sammy"])
      

      You’ll see the following output:

      Output

      0

      Even though the key sammy was not in the map, Go still returned the value of 0. This is because the value data type is an int, and because Go has a zero value for all variables, it returns the zero value of 0.

      In many cases, this is undesirable and would lead to a bug in your program. When looking up the value in a map, Go can return a second, optional value. This second value is a bool and will be true if the key was found, or false if the key was not found. In Go, this is referred to as the ok idiom. Even though you could name the variable that captures the second argument anything you want, in Go, you always name it ok:

      count, ok := counts["sammy"]
      

      If the key sammy exists in the counts map, then ok will be true. Otherwise ok will be false.

      You can use the ok variable to decide what to do in your program:

      if ok {
          fmt.Printf("Sammy has a count of %dn", count)
      } else {
          fmt.Println("Sammy was not found")
      }
      

      This would result in the following output:

      Output

      Sammy was not found

      In Go, you can combine variable declaration and conditional checking with an if/else block. This allows you to use a single statement for this check:

      if count, ok := counts["sammy"]; ok {
          fmt.Printf("Sammy has a count of %dn", count)
      } else {
          fmt.Println("Sammy was not found")
      }
      

      When retrieving a value from a map in Go, it’s always good practice to check for its existence as well to avoid bugs in your program.

      Modifying Maps

      Maps are a mutable data structure, so you can modify them. Let’s look at adding and deleting map items in this section.

      Adding and Changing Map Items

      Without using a method or function, you can add key-value pairs to maps. You do this using the maps variable name, followed by the key value in square brackets [ ], and using the equal = operator to set a new value:

      map[key] = value
      

      In practice, you can see this work by adding a key-value pair to a map called usernames:

      usernames := map[string]string{"Sammy": "sammy-shark", "Jamie": "mantisshrimp54"}
      
      usernames["Drew"] = "squidly"
      fmt.Println(usernames)
      

      The output will display the new Drew:squidly key-value pair in the map:

      Output

      map[Drew:squidly Jamie:mantisshrimp54 Sammy:sammy-shark]

      Because maps are returned unordered, this pair may occur anywhere in the map output. If you use the usernames map later in your program file, it will include the additional key-value pair.

      You can also use this syntax for modifying the value assigned to a key. In this case, you reference an existing key and pass a different value to it.

      Consider a map called followers that tracks followers of users on a given network. The user "drew" had a bump in followers today, so you need to update the integer value passed to the "drew" key. You’ll use the Println() function to check that the map was modified:

      followers := map[string]int{"drew": 305, "mary": 428, "cindy": 918}
      followers["drew"] = 342
      fmt.Println(followers)
      

      Your output will show the updated value for drew:

      Output

      map[cindy:918 drew:342 mary:428]

      You see that the number of followers jumped from the integer value of 305 to 342.

      You can use this method for adding key-value pairs to maps with user input. Let’s write a quick program called usernames.go that runs on the command line and allows input from the user to add more names and associated usernames:

      usernames.go

      package main
      
      import (
          "fmt"
          "strings"
      )
      
      func main() {
          usernames := map[string]string{"Sammy": "sammy-shark", "Jamie": "mantisshrimp54"}
      
          for {
              fmt.Println("Enter a name:")
      
              var name string
              _, err := fmt.Scanln(&name)
      
              if err != nil {
                  panic(err)
              }
      
              name = strings.TrimSpace(name)
      
              if u, ok := usernames[name]; ok {
                  fmt.Printf("%q is the username of %qn", u, name)
                  continue
              }
      
              fmt.Printf("I don't have %v's username, what is it?n", name)
      
              var username string
              _, err = fmt.Scanln(&username)
      
              if err != nil {
                  panic(err)
              }
      
              username = strings.TrimSpace(username)
      
              usernames[name] = username
      
              fmt.Println("Data updated.")
          }
      }
      

      In usernames.go you first define the original map. You then set up a loop to iterate over the names. You request your user to enter a name and declare a variable to store it in. Next, you check to see if you had an error; if so, the program will exit with a panic. Because Scanln captures the entire input, including the carriage return, you need to remove any space from the input; you do this with the strings.TrimSpace function.

      The if block checks whether the name is present in the map and prints feedback. If the name is present it then continues back to the top of the loop. If the name is not in the map, it provides feedback to the user and then will ask for a new username for the associated name. The program checks again to see if there is an error. With no error, it trims off the carriage return, assigns the username value to the name key, and then prints feedback that the data was updated.

      Let’s run the program on the command line:

      You'll see the following output:

      Output

      Enter a name: Sammy "sammy-shark" is the username of "Sammy" Enter a name: Jesse I don't have Jesse's username, what is it? JOctopus Data updated. Enter a name:

      When you're done testing, press CTRL + C to escape the program.

      This shows how you can modify maps interactively. With this particular program, as soon as you exit the program with CTRL + C you’ll lose all your data unless you implement a way to handle reading and writing files.

      To summarize, you can add items to maps or modify values with the map[key] = value syntax.

      Deleting Map Items

      Just as you can add key-value pairs and change values within the map data type, you can also delete items within a map.

      To remove a key-value pair from a map, you can use the built-in function delete(). The first argument is the map you are deleting from. The second argument is the key you are deleting:

      delete(map, key)
      

      Let's define a map of permissions:

      permissions := map[int]string{1: "read", 2: "write", 4: "delete", 8: "create", 16:"modify"}
      

      You no longer need the modify permission, so you'll remove it from your map. Then you'll print out the map to confirm it was removed:

      permissions := map[int]string{1: "read", 2: "write", 4: "delete", 8: "create", 16: "modify"}
      delete(permissions, 16)
      fmt.Println(permissions)
      

      The output will confirm the deletion:

      Output

      map[1:read 2:write 4:delete 8:create]

      The line delete(permissions, 16) removes the key-value pair 16:"modify" from the permissions map.

      If you would like to clear a map of all of its values, you can do so by setting it equal to an empty map of the same type. This will create a new empty map to use, and the old map will be cleared from memory by the garbage collector.

      Let’s remove all the items within the permissions map:

      permissions = map[int]string{}
      fmt.Println(permissions)
      

      The output shows that you now have an empty map devoid of key-value pairs:

      Output

      map[]

      Because maps are mutable data types, they can be added to, modified, and have items removed and cleared.

      Conclusion

      This tutorial explored the map data structure in Go. Maps are made up of key-value pairs and provide a way to store data without relying on indexing. This allows us to retrieve values based on their meaning and relation to other data types.



      Source link