Chapter 4: Data Structures

Arrays and Slices give

Arrays

Arrays in Go are fixed-size sequences of elements of the same type. Once declared, their size cannot change.

Example 1: Declaring and Initializing an Array

go

package main

import "fmt"

func main() {
// Declare and initialize an array of integers
var numbers [5]int
numbers = [5]int{1, 2, 3, 4, 5}

// Accessing array elements
fmt.Println("First element:", numbers[0]) // Output: 1
fmt.Println("Last element:", numbers[4]) // Output: 5
}

Example 2: Iterating over an Array

go

package main

import "fmt"

func main() {
// Declare and initialize an array of strings
var fruits [3]string
fruits = [3]string{"Apple", "Orange", "Banana"}

// Iterating over array elements
for index, fruit := range fruits {
fmt.Printf("Fruit at index %d: %s\n", index, fruit)
}
}

Example 3: Multi-dimensional Array

go

package main

import "fmt"

func main() {
// Declare and initialize a 2D array
var matrix [3][3]int
matrix = [3][3]int{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}

// Accessing elements in a 2D array
fmt.Println("Element at (1,1):", matrix[1][1]) // Output: 5
}

Example 4: Passing Arrays to Functions

go

package main

import "fmt"

// Function that takes an array as a parameter
func sum(numbers [5]int) int {
total := 0
for _, num := range numbers {
total += num
}
return total
}

func main() {
// Declare and initialize an array
numbers := [5]int{1, 2, 3, 4, 5}

// Call function with array argument
result := sum(numbers)
fmt.Println("Sum of array elements:", result) // Output: 15
}

Example 5: Arrays as Values

In Go, arrays are values. Assigning one array to another creates a copy of the array.

go

package main

import "fmt"

func main() {
// Declare and initialize an array
arr1 := [3]int{1, 2, 3}

// Assign one array to another
arr2 := arr1

// Modify arr2
arr2[0] = 10

// Print both arrays
fmt.Println("Array 1:", arr1) // Output: [1 2 3]
fmt.Println("Array 2:", arr2) // Output: [10 2 3]
}

Slices

Slices are dynamically sized and flexible wrappers around arrays in Go. They provide more functionality compared to arrays.

Example 1: Declaring and Initializing a Slice

go

package main

import "fmt"

func main() {
// Declare and initialize a slice
var numbers []int
numbers = []int{1, 2, 3, 4, 5}

// Accessing slice elements
fmt.Println("First element:", numbers[0]) // Output: 1
fmt.Println("Last element:", numbers[4]) // Output: 5
}

Example 2: Iterating over a Slice

go

package main

import "fmt"

func main() {
// Declare and initialize a slice of strings
fruits := []string{"Apple", "Orange", "Banana"}

// Iterating over slice elements
for index, fruit := range fruits {
fmt.Printf("Fruit at index %d: %s\n", index, fruit)
}
}

Example 3: Modifying a Slice

go

package main

import "fmt"

func main() {
// Declare and initialize a slice
numbers := []int{1, 2, 3, 4, 5}

// Modify elements in the slice
numbers[0] = 10
numbers = append(numbers, 6)

// Print the modified slice
fmt.Println("Modified Slice:", numbers) // Output: [10 2 3 4 5 6]
}

Example 4: Slicing a Slice

go

package main

import "fmt"

func main() {
// Declare and initialize a slice
numbers := []int{1, 2, 3, 4, 5}

// Slicing a slice
sliced := numbers[1:4]

// Print the sliced slice
fmt.Println("Sliced Slice:", sliced) // Output: [2 3 4]
}

Example 5: Slices and Capacity

Slices have both length and capacity. Capacity represents the maximum number of elements that the slice can hold.

go

package main

import "fmt"

func main() {
// Declare and initialize a slice
numbers := []int{1, 2, 3, 4, 5}

// Print length and capacity of the slice
fmt.Printf("Length: %d, Capacity: %d\n", len(numbers), cap(numbers))
}

In conclusion, arrays and slices in Go offer different capabilities and flexibility in handling collections of data. Arrays are fixed in size and primarily used for situations where size is known beforehand and doesn’t change, while slices are dynamic, resizable, and provide more functionality for managing data collections.

Maps in Go

Maps in Go are unordered collections of key-value pairs where all keys are unique within the map. They provide an efficient way to look up values based on keys.

Example 1: Declaring and Initializing a Map

go

package main

import "fmt"

func main() {
// Declare and initialize a map using make function
var person map[string]int
person = make(map[string]int)

// Adding key-value pairs to the map
person["Alice"] = 30
person["Bob"] = 25
person["Eve"] = 35

// Accessing values from the map
fmt.Println("Age of Alice:", person["Alice"]) // Output: 30
}

In this example:

  • We declare a map person with keys of type string and values of type int.
  • We initialize the map using the make function to create an empty map.
  • We add key-value pairs to the map using square brackets and assignment (=) operator.
  • We retrieve and print the value associated with the key "Alice" using bracket notation.

Example 2: Initializing a Map with Literal Syntax

go

package main

import "fmt"

func main() {
// Initialize a map with literal syntax
person := map[string]int{
"Alice": 30,
"Bob": 25,
"Eve": 35,
}

// Accessing values from the map
fmt.Println("Age of Bob:", person["Bob"]) // Output: 25
}

In this example:

  • We directly initialize the person map using a map literal syntax.
  • Each key-value pair is separated by a colon (:), and pairs are enclosed in curly braces {}.
  • We access and print the age of "Bob" using bracket notation.

Example 3: Checking if a Key Exists in a Map

go

package main

import "fmt"

func main() {
// Initialize a map with literal syntax
person := map[string]int{
"Alice": 30,
"Bob": 25,
"Eve": 35,
}

// Check if key exists in the map
if age, ok := person["Charlie"]; ok {
fmt.Println("Age of Charlie:", age)
} else {
fmt.Println("Charlie's age not found")
}
}

In this example:

  • We attempt to retrieve the age of "Charlie" from the person map.
  • The expression person["Charlie"] returns two values: the value associated with the key "Charlie" and a boolean (ok) indicating if the key exists.
  • We use a conditional statement (ifelse) to check if the key exists (ok is true). If it exists, we print the age; otherwise, we print a message indicating the key wasn’t found.

Example 4: Deleting an Entry from a Map

go

package main

import "fmt"

func main() {
// Initialize a map with literal syntax
person := map[string]int{
"Alice": 30,
"Bob": 25,
"Eve": 35,
}

// Delete an entry from the map
delete(person, "Bob")

// Check if key exists after deletion
if age, ok := person["Bob"]; ok {
fmt.Println("Age of Bob:", age)
} else {
fmt.Println("Bob's age not found after deletion")
}
}

In this example:

  • We use the delete function to remove the entry with the key "Bob" from the person map.
  • After deletion, we check if the key "Bob" exists in the map. Since it doesn’t exist anymore, the ok value will be false, and we print a message indicating that "Bob"‘s age is not found after deletion.

Example 5: Iterating over a Map

go

package main

import "fmt"

func main() {
// Initialize a map with literal syntax
person := map[string]int{
"Alice": 30,
"Bob": 25,
"Eve": 35,
}

// Iterating over the map
for name, age := range person {
fmt.Printf("%s's age is %d\n", name, age)
}
}

In this example:

  • We use a for loop with range to iterate over each key-value pair in the person map.
  • Inside the loop, name holds the key, and age holds the corresponding value.
  • We print each person’s name and age using formatted printing (Printf).

Conclusion

Maps in Go provide a powerful way to manage collections of data with key-value pairs. They are versatile and essential in various scenarios, such as storing relationships between entities, counting occurrences, and more. Understanding how to declare, initialize, manipulate, and iterate over maps is fundamental for effective Go programming.

Structs in Go

Structs in Go are composite data types used to group together different types of data under a single name. They are analogous to classes in object-oriented programming languages but without inheritance. Structs are useful for organizing data into records or entities.

Example 1: Declaring and Initializing a Struct

go

package main

import "fmt"

// Define a struct type
type Person struct {
Name string
Age int
City string
Salary float64
}

func main() {
// Initialize a struct using a struct literal
person1 := Person{
Name: "Alice",
Age: 30,
City: "New York",
Salary: 55000.0,
}

// Accessing fields of the struct
fmt.Println("Name:", person1.Name)
fmt.Println("Age:", person1.Age)
fmt.Println("City:", person1.City)
fmt.Println("Salary:", person1.Salary)
}

In this example:

  • We define a Person struct type with fields Name, Age, City, and Salary.
  • We initialize a person1 variable of type Person using a struct literal with field names and values.
  • We access and print each field of the person1 struct using dot (.) notation.

Example 2: Struct Initialization with Implicit Field Names

go

package main

import "fmt"

// Define a struct type
type Point struct {
X int
Y int
}

func main() {
// Initialize a struct without field names (implicit order)
point1 := Point{10, 20}

// Accessing fields of the struct
fmt.Println("X coordinate:", point1.X)
fmt.Println("Y coordinate:", point1.Y)
}

In this example:

  • We define a Point struct type with fields X and Y.
  • We initialize a point1 variable of type Point without specifying field names, using the order defined in the struct definition.
  • We access and print the X and Y coordinates of point1 using dot (.) notation.

Example 3: Struct Embedding (Composition)

go

package main

import "fmt"

// Define a struct type
type Address struct {
City string
Country string
}

// Define a struct with embedded structs
type Person struct {
Name string
Age int
Address // Embedded struct
}

func main() {
// Initialize a struct with embedded structs
person1 := Person{
Name: "Bob",
Age: 28,
Address: Address{
City: "London",
Country: "UK",
},
}

// Accessing fields of the embedded struct
fmt.Println("Name:", person1.Name)
fmt.Println("Age:", person1.Age)
fmt.Println("City:", person1.City) // Accessing embedded struct field
fmt.Println("Country:", person1.Country) // Accessing embedded struct field
}

In this example:

  • We define an Address struct with fields City and Country.
  • We define a Person struct that embeds the Address struct.
  • We initialize a person1 variable of type Person with values for Name, Age, and Address (using struct literal for Address).
  • We access and print fields of both the Person struct (Name, Age) and the embedded Address struct (City, Country) using dot (.) notation.

Example 4: Struct Methods

go

package main

import "fmt"

// Define a struct type
type Rectangle struct {
Length float64
Width float64
}

// Method to calculate area of Rectangle
func (r Rectangle) Area() float64 {
return r.Length * r.Width
}

func main() {
// Initialize a struct
rect1 := Rectangle{Length: 10.5, Width: 5.5}

// Call the method on the struct instance
area := rect1.Area()

// Print the calculated area
fmt.Println("Area of rectangle:", area)
}

In this example:

  • We define a Rectangle struct type with fields Length and Width.
  • We define a method Area() for the Rectangle struct, which calculates and returns the area of the rectangle (Length * Width).
  • We initialize a rect1 variable of type Rectangle with specific values for Length and Width.
  • We call the Area() method on the rect1 instance to calculate the area and store the result in the area variable.
  • We print the calculated area of the rectangle.

Example 5: Working with Pointers to Structs

go

package main

import "fmt"

// Define a struct type
type Employee struct {
ID int
Name string
Salary float64
}

// Function to increase employee salary using a pointer to the struct
func increaseSalary(emp *Employee, percentage float64) {
emp.Salary *= (1 + percentage/100)
}

func main() {
// Initialize a struct
emp1 := Employee{ID: 101, Name: "Alice", Salary: 60000.0}

// Print initial salary
fmt.Println("Initial salary:", emp1.Salary)

// Call function to increase salary (passing pointer to struct)
increaseSalary(&emp1, 10.0)

// Print updated salary
fmt.Println("Updated salary:", emp1.Salary)
}

In this example:

  • We define an Employee struct type with fields ID, Name, and Salary.
  • We define a function increaseSalary() that takes a pointer to Employee (*Employee) and increases the Salary by a specified percentage.
  • We initialize an emp1 variable of type Employee with values for ID, Name, and Salary.
  • We print the initial Salary of emp1.
  • We call the increaseSalary() function with a pointer to emp1 (&emp1) to increase the salary by 10%.
  • We print the updated Salary of emp1.

Conclusion

Structs in Go are versatile and essential for organizing data into meaningful entities or records. They support composition, methods, and pointers, making them powerful constructs for building complex data structures and systems. Understanding how to declare, initialize, access fields, define methods, and work with pointers to structs is fundamental for effective Go programming.

Interfaces in Go

Interfaces in Go are defined as a set of method signatures. Any type that implements all the methods defined in an interface is said to satisfy that interface. Interfaces enable polymorphism and decouple the definition of objects from their implementation details.

Example 1: Basic Interface Definition

go

package main

import (
"fmt"
"math"
)

// Define an interface named Shape
type Shape interface {
Area() float64
Perimeter() float64
}

// Define a struct named Circle
type Circle struct {
Radius float64
}

// Define a method Area() for Circle to satisfy Shape interface
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}

// Define a method Perimeter() for Circle to satisfy Shape interface
func (c Circle) Perimeter() float64 {
return 2 * math.Pi * c.Radius
}

// Define a struct named Rectangle
type Rectangle struct {
Width float64
Height float64
}

// Define a method Area() for Rectangle to satisfy Shape interface
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}

// Define a method Perimeter() for Rectangle to satisfy Shape interface
func (r Rectangle) Perimeter() float64 {
return 2 * (r.Width + r.Height)
}

func main() {
// Declare variables of type Circle and Rectangle
circle := Circle{Radius: 5}
rectangle := Rectangle{Width: 10, Height: 5}

// Declare a variable of type Shape (interface)
var shape Shape

// Assign circle to shape (Circle satisfies the Shape interface)
shape = circle
fmt.Printf("Circle - Area: %.2f, Perimeter: %.2f\n", shape.Area(), shape.Perimeter())

// Assign rectangle to shape (Rectangle satisfies the Shape interface)
shape = rectangle
fmt.Printf("Rectangle - Area: %.2f, Perimeter: %.2f\n", shape.Area(), shape.Perimeter())
}

In this example:

  • We define an interface Shape with two methods: Area() and Perimeter().
  • We define two structs Circle and Rectangle that implement the Shape interface by providing their own implementations of Area() and Perimeter().
  • Both Circle and Rectangle structs implicitly satisfy the Shape interface because they implement all the methods defined in the interface.
  • In the main() function, we declare variables of type Circle and Rectangle.
  • We declare a variable shape of type Shape (interface).
  • We assign instances of Circle and Rectangle to shape, demonstrating polymorphism where the same interface (Shape) can hold different types (Circle and Rectangle).

Example 2: Interface Embedding

go

package main

import (
"fmt"
)

// Define an interface named Writer
type Writer interface {
Write(data string) error
}

// Define a struct named FileWriter
type FileWriter struct {
FilePath string
}

// Implement the Write method for FileWriter to satisfy Writer interface
func (fw FileWriter) Write(data string) error {
// Implementation to write data to file
fmt.Printf("Writing data '%s' to file: %s\n", data, fw.FilePath)
return nil
}

// Define a struct named ConsoleWriter
type ConsoleWriter struct{}

// Implement the Write method for ConsoleWriter to satisfy Writer interface
func (cw ConsoleWriter) Write(data string) error {
// Implementation to write data to console
fmt.Printf("Writing data '%s' to console\n", data)
return nil
}

func main() {
// Declare a variable of type Writer (interface)
var writer Writer

// Assign an instance of FileWriter to writer
writer = FileWriter{FilePath: "/path/to/file.txt"}
writer.Write("Hello, Go Interfaces!")

// Assign an instance of ConsoleWriter to writer
writer = ConsoleWriter{}
writer.Write("Hello, Go Interfaces!")
}

In this example:

  • We define an interface Writer with a Write method that accepts a string and returns an error.
  • We define two structs FileWriter and ConsoleWriter that implement the Writer interface by providing their own implementations of the Write method.
  • Both FileWriter and ConsoleWriter structs satisfy the Writer interface because they implement the Write method.
  • In the main() function, we declare a variable writer of type Writer (interface).
  • We assign an instance of FileWriter to writer, demonstrating how interfaces can be used to write data to a file.
  • We then assign an instance of ConsoleWriter to writer, showing how interfaces can be used to write data to the console.

Interfaces in Go provide flexibility and enable code reuse by allowing different types to be used interchangeably wherever the interface is accepted. They promote loosely coupled designs and make Go programs more modular and extensible.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *