Bhagvan Kommadi - Learn Data Structures and Algorithms With Golang - Level Up Your Go Programming Skills To Develop Faster and More Efficient Code PDF
Bhagvan Kommadi - Learn Data Structures and Algorithms With Golang - Level Up Your Go Programming Skills To Develop Faster and More Efficient Code PDF
Bhagvan Kommadi
BIRMINGHAM - MUMBAI
Learn Data Structures and Algorithms with
Golang
Copyright © 2019 Packt Publishing
All rights reserved. No part of this book may be reproduced, stored in a retrieval system, or transmitted in any form
or by any means, without the prior written permission of the publisher, except in the case of brief quotations
embedded in critical articles or reviews.
Every effort has been made in the preparation of this book to ensure the accuracy of the information presented.
However, the information contained in this book is sold without warranty, either express or implied. Neither the
author, nor Packt Publishing or its dealers and distributors, will be held liable for any damages caused or alleged to
have been caused directly or indirectly by this book.
Packt Publishing has endeavored to provide trademark information about all of the companies and products
mentioned in this book by the appropriate use of capitals. However, Packt Publishing cannot guarantee the accuracy
of this information.
ISBN 978-1-78961-850-1
www.packtpub.com
mapt.io
Mapt is an online digital library that gives you full access to over 5,000 books and videos, as
well as industry leading tools to help you plan your personal development and advance
your career. For more information, please visit our website.
Why subscribe?
Spend less time learning and more time coding with practical eBooks and Videos
from over 4,000 industry professionals
Improve your learning with Skill Plans built especially for you
Packt.com
Did you know that Packt offers eBook versions of every book published, with PDF and
ePub files available? You can upgrade to the eBook version at www.packt.com and as a print
book customer, you are entitled to a discount on the eBook copy. Get in touch with us at
customercare@packtpub.com for more details.
At www.packt.com, you can also read a collection of free technical articles, sign up for a
range of free newsletters, and receive exclusive discounts and offers on Packt books and
eBooks.
Contributors
He has developed Go-based blockchain solutions in the retail, education, banking, and
financial service sectors. He has experience of building high-transactional applications
using Java, Python, Go, Ruby, and JavaScript frameworks.
About the reviewer
Eduard Bondarenko is a software developer living in Kiev, Ukraine. He started
programming using Basic on a ZX Spectrum many, many years ago. Later, he worked in
the web development domain. He has used Ruby on Rails for over 8 years. Having used
Ruby for a long time, he discovered Clojure in early 2009, and liked the simplicity of the
language. Besides Ruby and Clojure, he is interested in Go and ReasonML development.
I want to thank my wonderful wife, my children, and my parents for all the love, support,
and help they have given me.
Arrays 53
Slices 54
The len function 55
Slice function 55
Two-dimensional slices 56
Maps 59
Database operations 60
The GetCustomer method 60
The InsertCustomer method 62
Variadic functions 63
The update operation 64
The delete operation 65
CRUD web forms 66
The defer and panic statements 69
The InsertCustomer method 70
The UpdateCustomer method 70
The DeleteCustomer method 71
CRM web application 71
The Create function 72
The Insert function 72
The Alter function 73
The Update function 73
The Delete function 74
The main method 74
The Header template 75
The Footer template 75
The Menu template 76
The Create template 76
The Update template 76
The View template 77
Summary 79
Questions 79
Further reading 80
[ ii ]
Table of Contents
[ iii ]
Table of Contents
[ iv ]
Table of Contents
[v]
Table of Contents
[ vi ]
Table of Contents
[ vii ]
Table of Contents
[ viii ]
Table of Contents
[ ix ]
Table of Contents
[x]
Preface
Learn Data Structures and Algorithms with Go covers topics related to simple and advanced
concepts in computer programming. The primary objective is to choose the correct
algorithm and data structures for a problem. This book explains the concepts for comparing
algorithm complexity and data structures in terms of code performance and efficiency.
Golang has been the buzzword for the last two years, with tremendous improvements
being seen in this area. Many developers and organizations are slowly migrating to Golang,
adopting its fast, lightweight, and inbuilt concurrency features. This means we need to have
a solid foundation in data structures and algorithms with this growing language.
This book is for anyone who wants to learn how to write efficient programs and use the
proper data structures and algorithms.
Chapter 2, Getting Started with Go for Data Structures and Algorithms, covers Go-specific data
structures, such as arrays, slices, two-dimensional slices, maps, structs, and channels.
Variadic functions, deferred function calls, and panic and recover operations are
introduced. Slicing operations, such as enlarging using append and copy, assigning parts,
appending a slice, and appending part of a slice, are also presented in this chapter.
Preface
Chapter 3, Linear Data Structures, covers linear data structures such as lists, sets, tuples,
stacks, and heaps. The operations related to these types, including insertion, deletion,
updating, reversing, and merging are shown with various code samples. In this chapter, we
present the complexity analysis of various data structure operations that display accessing,
search, insertion, and deletion times.
Chapter 4, Non-Linear Data Structures, covers non-linear data structures, such as trees,
tables, containers, and hash functions. Tree types, including binary tree, binary search tree,
T-tree, treap, symbol table, B- tree, and B+ tree, are explained with code examples and
complexity analysis. Hash function data structures are presented, along with examples in
cryptography for a variety of scenarios, such as open addressing, linear probing, universal
hashing, and double hashing.
Chapter 5, Homogeneous Data Structures, covers homogeneous data structures such as two-
dimensional and multi-dimensional arrays. Array shapes, types, literals, printing,
construction, indexing, modification, transformation, and views are presented together
with code examples and performance analysis. Matrix representation, multiplication,
addition, subtraction, inversion, and transpose scenarios are shown to demonstrate the
usage of multi-dimensional arrays.
Chapter 7, Dynamic Data Structures, covers dynamic data structures, such as dictionaries,
TreeSets, and sequences. Synchronized TreeSets and mutable TreeSets are covered in this
chapter along with Go code exhibits. Sequence types including Farey, Fibonacci, look-and-
say, and Thue-Morse, are discussed with Go programs. This chapter also explains the usage
anti-patterns of dictionaries, TreeSets, and sequences.
Chapter 9, Network and Sparse Matrix Representation, covers data structures such as graphs
and lists of lists. Different use cases from real-life applications, such as social network
representation, map layouts, and knowledge catalogs, are shown with code examples and
efficiency analysis.
[2]
Preface
Chapter 10, Memory Management, covers dynamic data structures, such as AVL trees and
stack frames. Garbage collection, cache management, and space allocation algorithms are
presented with code samples and efficiency analysis. Garbage collection algorithms, such as
simple/deferred/one-bit/weighted reference counting, mark and sweep, and generational
collection, are explained with an analysis of their advantages and disadvantages.
Appendix, Next Steps, shares the learning outcomes for the reader arising from the book. The
code repository links and key takeaways are presented. References are included for the
latest data structures and algorithms. Tips and techniques are provided to keep yourself
updated with the latest on data structures and algorithms.
Once the file is downloaded, please make sure that you unzip or extract the folder using the
latest version of:
[3]
Preface
The code bundle for the book is also hosted on GitHub at https://github.com/
PacktPublishing/Learn-Data-Structures-and-Algorithms-with-Golang. In case there's
an update to the code, it will be updated on the existing GitHub repository.
We also have other code bundles from our rich catalog of books and videos available
at https://github.com/PacktPublishing/. Check them out!
Conventions used
There are a number of text conventions used throughout this book.
CodeInText: Indicates code words in text, database table names, folder names, filenames,
file extensions, pathnames, dummy URLs, user input, and Twitter handles. Here is an
example: "Let's take a look at the len function in the next section."
[4]
Preface
Bold: Indicates a new term, an important word, or words that you see on screen.
Get in touch
Feedback from our readers is always welcome.
General feedback: If you have questions about any aspect of this book, mention the book
title in the subject of your message and email us at customercare@packtpub.com.
Errata: Although we have taken every care to ensure the accuracy of our content, mistakes
do happen. If you have found a mistake in this book, we would be grateful if you would
report this to us. Please visit www.packt.com/submit-errata, selecting your book, clicking
on the Errata Submission Form link, and entering the details.
Piracy: If you come across any illegal copies of our works in any form on the internet, we
would be grateful if you would provide us with the location address or website name.
Please contact us at copyright@packt.com with a link to the material.
If you are interested in becoming an author: If there is a topic that you have expertise in,
and you are interested in either writing or contributing to a book, please visit
authors.packtpub.com.
Reviews
Please leave a review. Once you have read and used this book, why not leave a review on
the site that you purchased it from? Potential readers can then see and use your unbiased
opinion to make purchase decisions, we at Packt can understand what you think about our
products, and our authors can see your feedback on their book. Thank you!
[5]
1
Section 1: Introduction to Data
Structures and Algorithms and
the Go Language
We will be introducing the abstract data types, definition, and classification of data
structures. Readers will be well-versed with performance analysis of algorithms and
choosing appropriate data structures for structural design patterns after reading this part.
In this chapter, we will focus on the definition of abstract datatypes, classifying data
structures into linear, nonlinear, homogeneous, heterogeneous, and dynamic types.
Abstract datatypes, such as Container, List, Set, Map, Graph, Stack, and Queue, are
presented in this chapter. We will also cover the performance analysis of data structures,
choosing the right data structures, and structural design patterns.
The reader can start writing basic algorithms using the right data structures in Go. Given a
problem, choosing the data structure and different algorithms will be the first step. After
this, doing performance analysis is the next step. Time and space analysis for different
algorithms helps compare them and helps you choose the optimal one. It is essential to
have basic knowledge of Go to get started.
Technical requirements
Install Go version 1.10 from https://golang.org/doc/install for your operating system.
The code files for this chapter can be found at the following GitHub URL: https://github.
com/PacktPublishing/Learn-Data-Structures-and-Algorithms-with-Golang/tree/
master/Chapter01.
[8]
Data Structures and Algorithms Chapter 1
Let's take a look at the classification of data structures and structural design patterns in the
next section.
If the situation requires various datatypes within a data structure, we can choose
heterogeneous data structures. Linked, ordered, and unordered lists are grouped as
heterogeneous data structures. Linear data structures are lists, sets, tuples, queues, stacks,
and heaps. Trees, tables, and containers are categorized as nonlinear data structures. Two-
dimensional and multidimensional arrays are grouped as homogeneous data structures.
Dynamic data structures are dictionaries, tree sets, and sequences.
[9]
Data Structures and Algorithms Chapter 1
Let's take a look at lists, tuples and heaps in the next sections.
Lists
A list is a sequence of elements. Each element can be connected to another with a link in a
forward or backward direction. The element can have other payload properties. This data
structure is a basic type of container. Lists have a variable length and developer can remove
or add elements more easily than an array. Data items within a list need not be contiguous
in memory or on disk. Linked lists were proposed by Allen Newell, Cliff Shaw, and Herbert
A. Simon at RAND Corporation.
[ 10 ]
Data Structures and Algorithms Chapter 1
To get started, a list can be used in Go, as shown in the following example; elements are
added through the PushBack method on the list, which is in the container/list
package:
//main package has examples shown
// in Hands-On Data Structures and algorithms with Go book
package main
// main method
func main() {
var intList list.List
intList.PushBack(11)
intList.PushBack(23)
intList.PushBack(34)
The list is iterated through the for loop, and the element's value is accessed through the
Value method.
[ 11 ]
Data Structures and Algorithms Chapter 1
Tuples
A tuple is a finite sorted list of elements. It is a data structure that groups data. Tuples are
typically immutable sequential collections. The element has related fields of different
datatypes. The only way to modify a tuple is to change the fields. Operators such as + and *
can be applied to tuples. A database record is referred to as a tuple. In the following
example, power series of integers are calculated and the square and cube of the integer is
returned as a tuple:
//main package has examples shown
// in Hands-On Data Structures and algorithms with Go book
package main
)
//gets the power series of integer a and returns tuple of square of a
// and cube of a
func powerSeries(a int) (int,int) {
The main method calls the powerSeries method with 3 as a parameter. The square and
cube values are returned from the method:
// main method
func main() {
[ 12 ]
Data Structures and Algorithms Chapter 1
The tuples can be named in the powerSeries function, as shown in the following code:
func powerSeries(a int) (square int, cube int) {
square = a*a
cube = square*a
return
If there is an error, it can be passed with tuples, as shown in the following code:
func powerSeries(a int) (int, int, error) {
square = a*a
cube = square*a
return square,cube,nil
Heaps
A heap is a data structure that is based on the heap property. The heap data structure is
used in selection, graph, and k-way merge algorithms. Operations such as finding,
merging, insertion, key changes, and deleting are performed on heaps. Heaps are part of
the container/heap package in Go. According to the heap order (maximum heap)
property, the value stored at each node is greater than or equal to its children.
[ 13 ]
Data Structures and Algorithms Chapter 1
IntegerHeap has a Push method that pushes the item with the interface:
// main method
func main() {
var intHeap *IntegerHeap = &IntegerHeap{1,4,5}
[ 14 ]
Data Structures and Algorithms Chapter 1
heap.Init(intHeap)
heap.Push(intHeap, 2)
fmt.Printf("minimum: %d\n", (*intHeap)[0])
for intHeap.Len() > 0 {
fmt.Printf("%d \n", heap.Pop(intHeap))
}
}
We will take a look at adapter and bridge design patterns in the next sections.
[ 15 ]
Data Structures and Algorithms Chapter 1
Adapter
The adapter pattern provides a wrapper with an interface required by the API client to link
incompatible types and act as a translator between the two types. The adapter uses the
interface of a class to be a class with another compatible interface. When requirements
change, there are scenarios where class functionality needs to be changed because of
incompatible interfaces.
The dependency inversion principle can be adhered to by using the adapter pattern, when a
class defines its own interface to the next level module interface implemented by an
adapter class. Delegation is the other principle used by the adapter pattern. Multiple
formats handling source-to-destination transformations are the scenarios where the adapter
pattern is applied.
The adapter pattern comprises the target, adaptee, adapter, and client:
Target is the interface that the client calls and invokes methods on the adapter
and adaptee.
The client wants the incompatible interface implemented by the adapter.
The adapter translates the incompatible interface of the adaptee into an interface
that the client wants.
Let's say you have an IProcessor interface with a process method, the Adapter class
implements the process method and has an Adaptee instance as an attribute. The
Adaptee class has a convert method and an adapterType instance variable. The
developer while using the API client calls the process interface method to invoke
convert on Adaptee. The code is as follows:
[ 16 ]
Data Structures and Algorithms Chapter 1
The Adapter class has a process method that invokes the convert method on adaptee:
//Adapter class method process
func (adapter Adapter) process() {
fmt.Println("Adapter process")
adapter.adaptee.convert()
}
//Adaptee Struct
type Adaptee struct {
adapterType int
}
// Adaptee class method convert
func (adaptee Adaptee) convert() {
fmt.Println("Adaptee convert method")
}
// main method
func main() {
var processor IProcess = Adapter{}
processor.process()
}
Bridge
Bridge decouples the implementation from the abstraction. The abstract base class can be
subclassed to provide different implementations and allow implementation details to be
modified easily. The interface, which is a bridge, helps in making the functionality of
concrete classes independent from the interface implementer classes. The bridge patterns
allow the implementation details to change at runtime.
[ 17 ]
Data Structures and Algorithms Chapter 1
The bridge pattern demonstrates the principle, preferring composition over inheritance. It
helps in situations where one should subclass multiple times orthogonal to each other.
Runtime binding of the application, mapping of orthogonal class hierarchies, and platform
independence implementation are the scenarios where the bridge pattern can be applied.
The bridge pattern components are abstraction, refined abstraction, implementer, and
concrete implementer. Abstraction is the interface implemented as an abstract class that
clients invoke with the method on the concrete implementer. Abstraction maintains a has-
a relationship with the implementation, instead of an is-a relationship. The has-
a relationship is maintained by composition. Abstraction has a reference of the
implementation. Refined abstraction provides more variations than abstraction.
Let's say IDrawShape is an interface with the drawShape method. DrawShape implements
the IDrawShape interface. We create an IContour bridge interface with
the drawContour method. The contour class implements the IContour interface. The
ellipse class will have a, b , r properties and drawShape (an instance of DrawShape). The
ellipse class implements the contour bridge interface to implement
the drawContour method. The drawContour method calls the drawShape method on
the drawShape instance.
drawShape method
The drawShape method draws the shape given the coordinates, as shown in the following
code:
// DrawShape struct has method draw Shape with float x and y coordinates
func (drawShape DrawShape) drawShape(x[5] float32, y[5] float32) {
fmt.Println("Drawing Shape")
}
[ 18 ]
Data Structures and Algorithms Chapter 1
//IContour interace
type IContour interface {
drawContour(x[5] float32 ,y[5] float32)
resizeByFactor(factor int)
}
//DrawContour struct
type DrawContour struct {
x[5] float32
y[5] float32
shape DrawShape
factor int
}
drawContour method
The drawContour method of the DrawContour class calls the drawShape method on the
shape instance, this is shown in the following code:
[ 19 ]
Data Structures and Algorithms Chapter 1
We will take a look at Composite, Decorator, Facade and Flyweight design patterns in the
next sections.
Composite
A composite is a group of similar objects in a single object. Objects are stored in a tree form
to persist the whole hierarchy. The composite pattern is used to change a hierarchical
collection of objects. The composite pattern is modeled on a heterogeneous collection. New
types of objects can be added without changing the interface and the client code. You can
use the composite pattern, for example, for UI layouts on the web, for directory trees, and
for managing employees across departments. The pattern provides a mechanism to access
the individual objects and groups in a similar manner.
The composite pattern comprises the component interface, component class, composite,
and client:
The component interface defines the default behavior of all objects and behaviors
for accessing the components of the composite.
The composite and component classes implement the component interface.
The client interacts with the component interface to invoke methods in the
composite.
Let's say there is an IComposite interface with the perform method and
BranchClass that implements IComposite and has the addLeaf, addBranch, and
perform methods. The Leaflet class implements IComposite with the perform
method. BranchClass has a one-to-many relationship with leafs and branches.
Iterating over the branch recursively, one can traverse the composite tree, as shown in the
following code:
//main package has examples shown
// in Hands-On Data Structures and algorithms with Go book
package main
// importing fmt package
[ 20 ]
Data Structures and Algorithms Chapter 1
import (
"fmt"
)
// IComposite interface
type IComposite interface {
perform()
}
// Leaflet struct
type Leaflet struct {
name string
}
// Leaflet class method perform
func (leaf *Leaflet) perform() {
fmt.Println("Leaflet " + leaf.name)
}
// Branch struct
type Branch struct {
leafs []Leaflet
name string
branches[]Branch
}
The perform method of the Branch class calls the perform method on branch and leafs,
as seen in the code:
// Branch class method perform
func (branch *Branch) perform() {
fmt.Println("Branch: " + branch.name)
for _, leaf := range branch.leafs {
leaf.perform()
}
for _, branch := range branch.branches {
branch.perform()
}
}
// Branch class method add leaflet
func (branch *Branch) add(leaf Leaflet) {
branch.leafs = append(branch.leafs, leaf)
}
As shown in the following code, the addBranch method of the Branch class adds a new
branch:
[ 21 ]
Data Structures and Algorithms Chapter 1
Decorator
In a scenario where class responsibilities are removed or added, the decorator pattern is
applied. The decorator pattern helps with subclassing when modifying functionality,
instead of static inheritance. An object can have multiple decorators and run-time
decorators. The single responsibility principle can be achieved using a
decorator. The decorator can be applied to window components and graphical object
modeling. The decorator pattern helps with modifying existing instance attributes and
adding new methods at run-time.
[ 22 ]
Data Structures and Algorithms Chapter 1
The decorator pattern participants are the component interface, the concrete component
class, and the decorator class:
Let 's say IProcess is an interface with the process method. ProcessClass implements
an interface with the process method. ProcessDecorator implements the process
interface and has an instance of ProcessClass. ProcessDecorator can add more
functionality than ProcessClass, as shown in the following code:
//main package has examples shown
// in Hands-On Data Structures and algorithms with Go book
package main
// importing fmt package
import (
"fmt"
)
// IProcess Interface
type IProcess interface {
process()
}
//ProcessClass struct
type ProcessClass struct{}
//ProcessClass method process
func (process *ProcessClass) process() {
fmt.Println("ProcessClass process")
}
//ProcessDecorator struct
type ProcessDecorator struct {
processInstance *ProcessClass
}
In the following code, the ProcessDecorator class process method invokes the process
method on the decorator instance of ProcessClass:
//ProcessDecorator class method process
func (decorator *ProcessDecorator) process() {
if decorator.processInstance == nil {
fmt.Println("ProcessDecorator process")
} else {
fmt.Printf("ProcessDecorator process and ")
decorator.processInstance.process()
}
[ 23 ]
Data Structures and Algorithms Chapter 1
}
//main method
func main() {
var process = &ProcessClass{}
var decorator = &ProcessDecorator{}
decorator.process()
decorator.processInstance = process
decorator.process()
}
Facade
Facade is used to abstract subsystem interfaces with a helper. The facade design pattern is
used in scenarios when the number of interfaces increases and the system gets complicated.
Facade is an entry point to different subsystems, and it simplifies the dependencies
between the systems. The facade pattern provides an interface that hides the
implementation details of the hidden code.
A loosely coupled principle can be realized with a facade pattern. You can use a facade to
improve poorly designed APIs. In SOA, a service facade can be used to incorporate changes
to the contract and implementation.
[ 24 ]
Data Structures and Algorithms Chapter 1
The facade pattern is made up of the facade class, module classes, and a client:
The facade delegates the requests from the client to the module classes. The
facade class hides the complexities of the subsystem logic and rules.
Module classes implement the behaviors and functionalities of the module
subsystem.
The client invokes the facade method. The facade class functionality can be
spread across multiple packages and assemblies.
For example, account, customer, and transaction are the classes that have account,
customer, and transaction creation methods. BranchManagerFacade can be used by the
client to create an account, customer, and transaction:
//main package has examples shown
// in Hands-On Data Structures and algorithms with Go book
package main
// importing fmt package
import (
"fmt"
)
//Account struct
type Account struct{
id string
accountType string
}
//Account class method create - creates account given AccountType
func (account *Account) create(accountType string) *Account{
fmt.Println("account creation with type")
account.accountType = accountType
return account
}
//Account class method getById given id string
func (account *Account) getById(id string) *Account {
fmt.Println("getting account by Id")
return account
}
The account class has the deleteById method, which is used to delete an account with a
given ID, as shown in the following code:
//Account class method deleteById given id string
func (account *Account) deleteById(id string)() {
fmt.Println("delete account by id")
}
//Customer struct
type Customer struct{
[ 25 ]
Data Structures and Algorithms Chapter 1
name string
id int
}
In the following code, the customer class has a method that creates a new customer with
name:
As shown in the following code, the transaction class has the create method for
creating a transaction:
//Transaction class method create Transaction
func (transaction *Transaction) create(srcAccountId string, destAccountId
string,amount float32) *Transaction {
fmt.Println("creating transaction")
transaction.srcAccountId = srcAccountId
transaction.destAccountId = destAccountId
transaction.amount = amount
return transaction
}
//BranchManagerFacade struct
type BranchManagerFacade struct {
account *Account
customer *Customer
transaction *Transaction
}
//method NewBranchManagerFacade
func NewBranchManagerFacade() *BranchManagerFacade {
return &BranchManagerFacade{ &Account{}, &Customer{}, &Transaction{}}
}
[ 26 ]
Data Structures and Algorithms Chapter 1
The main method calls the NewBranchManagerFacade method to create a facade. The
methods on facade are invoked to create customer and account:
//main method
func main() {
var facade = NewBranchManagerFacade()
var customer *Customer
var account *Account
customer, account = facade.createCustomerAccount("Thomas Smith",
"Savings")
fmt.Println(customer.name)
fmt.Println(account.accountType)
var transaction = facade.createTransaction("21456","87345",1000)
fmt.Println(transaction.amount)
}
[ 27 ]
Data Structures and Algorithms Chapter 1
Flyweight
Flyweight is used to manage the state of an object with high variation. The pattern allows
us to share common parts of the object state among multiple objects, instead of each object
storing it. Variable object data is referred to as extrinsic state, and the rest of the object state
is intrinsic. Extrinsic data is passed to flyweight methods and will never be stored within it.
Flyweight pattern helps reduce the overall memory usage and the object initializing
overhead. The pattern helps create interclass relationships and lower memory to a
manageable level.
Flyweight objects are immutable. Value objects are a good example of the flyweight pattern.
Flyweight objects can be created in a single thread mode, ensuring one instance per value.
In a concurrent thread scenario, multiple instances are created. This is based on the equality
criterion of flyweight objects.
The FlyWeight interface has a method through which flyweights can get and act
on the extrinsic state.
ConcreteFlyWeight implements the FlyWeight interface to represent
flyweight objects.
FlyweightFactory is used to create and manage flyweight objects. The client
invokes FlyweightFactory to get a flyweight object. UnsharedFlyWeight can
have a functionality that is not shared.
Client classes
[ 28 ]
Data Structures and Algorithms Chapter 1
return dto
}
// DataTransferObject interface
type DataTransferObject interface {
getId() string
}
//Customer struct
type Customer struct {
id string //sequence generator
name string
ssn string
}
// Customer class method getId
func (customer Customer) getId() string {
//fmt.Println("getting customer Id")
return customer.id
}
//Employee struct
type Employee struct {
id string
name string
}
[ 29 ]
Data Structures and Algorithms Chapter 1
[ 30 ]
Data Structures and Algorithms Chapter 1
We will take a look at Private class and Proxy data patterns in the next sections.
Account is a class with account details and a customer name. AccountDetails is the
private attribute of Account , and CustomerName is the public attribute. JSON marshaling
of Account has CustomerName as a public property. AccountDetails is the package
property in Go (modeled as private class data):
//main package has examples shown
// in Hands-On Data Structures and algorithms with Go book
package main
// importing fmt and encoding/json packages
import (
"encoding/json"
"fmt"
)
//AccountDetails struct
type AccountDetails struct {
id string
accountType string
}
//Account struct
[ 31 ]
Data Structures and Algorithms Chapter 1
As shown in the following code, the Account class has the getId method, which returns
the id private class attribute:
//Account class method getId
func (account *Account) getId() string{
return account.details.id
}
//Account class method getAccountType
func (account *Account) getAccountType() string{
return account.details.accountType
}
The main method calls the Account initializer with CustomerName. The details of the
account are set details with the setDetails method:
// main method
func main() {
var account *Account = &Account{CustomerName: "John Smith"}
account.setDetails("4532","current")
jsonAccount, _ := json.Marshal(account)
fmt.Println("Private Class hidden",string(jsonAccount))
fmt.Println("Account Id",account.getId())
fmt.Println("Account Type",account.getAccountType())
}
[ 32 ]
Data Structures and Algorithms Chapter 1
Proxy
The proxy pattern forwards to a real object and acts as an interface to others. The proxy
pattern controls access to an object and provides additional functionality. The additional
functionality can be related to authentication, authorization, and providing rights of access
to the resource-sensitive object. The real object need not be modified while providing
additional logic. Remote, smart, virtual, and protection proxies are the scenarios where this
pattern is applied. It is also used to provide an alternative to extend functionality with
inheritance and object composition. A proxy object is also referred to as a surrogate, handle,
or wrapper.
The proxy pattern comprises the subject interface, the RealSubject class, and the Proxy
class:
[ 33 ]
Data Structures and Algorithms Chapter 1
}
//VirtualProxy struct
type VirtualProxy struct {
realObject *RealObject
}
//VirtualProxy class method performAction
func (virtualProxy *VirtualProxy) performAction() {
if virtualProxy.realObject == nil {
virtualProxy.realObject = &RealObject{}
}
fmt.Println("Virtual Proxy performAction()")
virtualProxy.realObject.performAction()
}
// main method
func main() {
var object VirtualProxy = VirtualProxy{}
object.performAction()
}
Now that we know the classification of data structures and the design patterns used, let's
go ahead and take a look at the representation of algorithms.
[ 34 ]
Data Structures and Algorithms Chapter 1
Representation of algorithms
A flow chart and pseudo code are methods of representing algorithms. An algorithm shows
the logic of how a problem is solved. A flow chart has different representation symbols
such as Entry, Exit, Task, Input/Output, Decision Point, and Inter Block. A structured
program consists of a series of these symbols to perform a specific task. Pseudo code has
documentation, action, and flow control keywords to visualize an algorithm. The
documentation keywords are TASK and REM. SET, PUT, and GET are the action
keywords.
Let's take a look at the different representations of algorithms, that is, flow charts and
Pseudo code in the next sections.
Flow chart
The flow control keywords are SET, LOOP, (WHILE, UNTIL), REP, and POST. The
following flow chart shows a formula or an algorithm to calculate the dividend given a
number of shares, the face value, and the dividend percentage. The start and end are the
Entry and Exit symbols. The input number of shares, share face value, and dividend
percentage use the Input symbol. The compute dividend and output dividend use the Task
symbol and Output symbol respectively:
[ 35 ]
Data Structures and Algorithms Chapter 1
In the next section, we'll take a look at pseudo code, representation of algorithms.
Pseudo code
Pseudo code is a high-level design of a program or algorithm. Sequence and selection are
two constructs used in pseudo code. Pseudo code is easier than a flow chart visualizes the
algorithm while pseudo code can be easily modified and updated. Errors in design can be
caught very early in pseudo code. This saves the cost of fixing defects later.
To give an example, we want to find the max value in an array of length n. The pseudo code
will be written as follows:
maximum(arr) {
n <- len(arr)
max <- arr[0]
for k <- 0,n do {
If arr[k] > max {
max <- arr[k]
}
}
return max
}
Now that we know the different ways to represent the algorithm, let's take a look at how
we can monitor its complexity and performance in the next section.
[ 36 ]
Data Structures and Algorithms Chapter 1
Let's say an algorithm iterates through an array, m, of size 10 and update the elements to
the sum of index and 200. The computational time will be 10*t, where t is the time taken to
add two integers and update them to an array. The next step will be printing them after
iterating over an array. The t time parameter will vary with the hardware of the computer
used. Asymptotically, the computational time grows as a factor of 10, as shown in the
following code:
//main package has examples shown
// in Hands-On Data Structures and algorithms with Go book
package main
// importing fmt package
import (
"fmt"
)
// main method
func main() {
var m [10]int
var k int
for k = 0; k < 10; k++ {
m[k] = k + 200
fmt.Printf("Element[%d] = %d\n", k, m[k] )
}
}
[ 37 ]
Data Structures and Algorithms Chapter 1
Let's take a look at the different complexity types in the next sections.
Big O notation
The T(n) time function represents the algorithm complexity based on Big O notation. T(n) =
O(n) states that an algorithm has a linear time complexity. Using Big O notation, the
constant time, linear time, logarithmic time, cubic time, and quadratic time complexity are
different complexity types for an algorithm.
Linear time, O(n), is used as a measure of complexity in scenarios such as linear search,
traversing, and finding the minimum and maximum number of array elements. ArrayList
and queue are data structures that have these methods. An algorithm that has logarithmic
time, O(log n), is a binary search in a tree data structure. Bubble sort, selection sort, and
insertion sort algorithms have complexity of quadratic time, O(n2). Big Omega Ω and big
Theta Θ are notations for the lower and upper bounds for a particular algorithm.
The worst case, best case, average case, and amortized run-time complexity is used for
analysis of algorithms. Amortized run-time complexity is referred to as 2n. Asymptotically,
it will tend to O(1).
Big O notation is also used to determine how much space is consumed by the algorithm.
This helps us find the best and worst case scenarios, relative to space and time.
[ 38 ]
Data Structures and Algorithms Chapter 1
Linear complexity
An algorithm is of linear complexity if the processing time or storage space is directly
proportional to the number of input elements to be processed. In Big O notation, linear
complexity is presented as O(n). String matching algorithms such as the Boyer-Moore and
Ukkonen have linear complexity.
[ 39 ]
Data Structures and Algorithms Chapter 1
Quadratic complexity
An algorithm is of quadratic complexity if the processing time is proportional to the square
of the number of input elements. In the following case, the complexity of the algorithm is
10*10 = 100. The two loops have a maximum of 10. The quadratic complexity for a
2
multiplication table of n elements is O(n ).
[ 40 ]
Data Structures and Algorithms Chapter 1
Cubic complexity
In the case of cubic complexity, the processing time of an algorithm is proportional to the
cube of the input elements. The complexity of the following algorithm is 10*10*10 = 1,000.
The three loops have a maximum of 10. The cubic complexity for a matrix update is O(n3).
[ 41 ]
Data Structures and Algorithms Chapter 1
Logarithmic complexity
An algorithm is of logarithmic complexity if the processing time is proportional to the
logarithm of the input elements. The logarithm base is typically 2. The following tree is a
binary tree with LeftNode and RightNode. The insert operation is of O(log n) complexity,
where n is the number of nodes.
[ 42 ]
Data Structures and Algorithms Chapter 1
As shown in the following code, the Tree class has the insert method, which inserts the
element given m is the integer element:
// Tree insert method for inserting at m position
func (tree *Tree) insert( m int) {
if tree != nil {
if tree.LeftNode == nil {
tree.LeftNode = &Tree{nil,m,nil}
} else {
if tree.RightNode == nil {
tree.RightNode = &Tree{nil,m,nil}
} else {
if tree.LeftNode != nil {
tree.LeftNode.insert(m)
} else {
tree.RightNode.insert(m)
}
}
}
} else {
tree = &Tree{nil,m,nil}
}
}
//print method for printing a Tree
func print(tree *Tree) {
if tree != nil {
fmt.Println(" Value",tree.Value)
fmt.Printf("Tree Node Left")
print(tree.LeftNode)
fmt.Printf("Tree Node Right")
print(tree.RightNode)
} else {
[ 43 ]
Data Structures and Algorithms Chapter 1
fmt.Printf("Nil\n")
}
}
The main method calls the insert method on tree to insert the 1, 3, 5, and 7 elements, as
shown in the following code:
// main method
func main() {
var tree *Tree = &Tree{nil,1,nil}
print(tree)
tree.insert(3)
print(tree)
tree.insert(5)
print(tree)
tree.LeftNode.insert(7)
print(tree)
}
[ 44 ]
Data Structures and Algorithms Chapter 1
Now that we know about the complexities in algorithms and analyzing their performance,
let's take a look at brute force algorithms in the next section.
Brute Force algorithms are known for wide applicability and simplicity in solving complex
problems. Searching, string matching, and matrix multiplication are some scenarios where
they are used. Single computational tasks can be solved using brute force algorithms. They
do not provide efficient algorithms. The algorithms are slow and non-performant.
Representation of a brute force algorithm is shown in the following code:
//main package has examples shown
//in Hands-On Data Structures and algorithms with Go book
package main
// importing fmt package
import (
"fmt"
)
//findElement method given array and k element
func findElement(arr[10] int, k int) bool {
var i int
for i=0; i< 10; i++ {
if arr[i]==k {
return true
}
}
return false
}
// main method
func main() {
var arr = [10]int{1,4,7,8,3,9,2,4,1,8}
var check bool = findElement(arr,10)
fmt.Println(check)
var check2 bool = findElement(arr,9)
fmt.Println(check2)
}
[ 45 ]
Data Structures and Algorithms Chapter 1
After brute force algorithms, let's cover divide and conquer algorithms in the next section.
Recursion, quick sort, binary search, fast Fourier transform, and merge sort are good
examples of divide and conquer algorithms. Memory is efficiently used with these
algorithms. Performance is sometimes an issue in the case of recursion. On multiprocessor
machines, these algorithms can be executed on different processors after breaking them
down into sub-problems. A divide and conquer algorithm is shown in the following code:
//main package has examples shown
// in Hands-On Data Structures and algorithms with Go book
package main
// importing fmt package
import (
"fmt"
)
As shown in the following code, the Fibonacci method takes the k integer parameter and
returns the Fibonacci number for k. The method uses recursion to calculate the Fibonacci
numbers. The recursion algorithm is applied by dividing the problem into the k-1 integer
and the k-2 integer:
// fibonacci method given k integer
func fibonacci(k int) int {
if k<=1{
[ 46 ]
Data Structures and Algorithms Chapter 1
return 1
}
return fibonacci(k-1)+fibonacci(k-2)
}
// main method
func main() {
var m int = 5
for m=0; m < 8; m++ {
var fib = fibonacci(m)
fmt.Println(fib)
}
}
Let's take a look at what backtracking algorithms are in the next section.
Backtracking algorithms
A backtracking algorithm solves a problem by constructing the solution incrementally.
Multiple options are evaluated, and the algorithm chooses to go to the next component of
the solution through recursion. Backtracking can be a chronological type or can traverse the
paths, depending on the problem that you are solving.
[ 47 ]
Data Structures and Algorithms Chapter 1
Backtracking is an algorithm that finds candidate solutions and rejects a candidate on the
basis of its feasibility and validity. Backtracking is useful in scenarios such as finding a
value in an unordered table. It is faster than a brute force algorithm, which rejects a large
number of solutions in an iteration. Constraint satisfaction problems such as parsing, rules
engine, knapsack problems, and combinatorial optimization are solved using backtracking.
[ 48 ]
Data Structures and Algorithms Chapter 1
findElementsWithSum(arr,combinations,10,addedSum,0,0,0)
//fmt.Println(check)//var check2 bool = findElement(arr,9)
//fmt.Println(check2)
}
[ 49 ]
Data Structures and Algorithms Chapter 1
Summary
This chapter covered the definition of abstract datatypes, classifying data structures into
linear, nonlinear, homogeneous, heterogeneous, and dynamic types. Abstract datatypes
such as container, list, set, map, graph, stack, and queue were presented in this chapter. The
chapter covered the performance analysis of data structures and structural design patterns.
We looked at the classification of data structures and structural design patterns. You can
use algorithms such as brute force, divide and conquer, and backtracking by calculating the
complexity and performance analysis. The choice of algorithm and the use of design
patterns and data structures are the key takeaways.
In the next chapter, we will discuss data structures in Go. The following data structures will
be covered:
Arrays
Slices
Two-dimensional slices
Maps
[ 50 ]
Data Structures and Algorithms Chapter 1
8. A rules engine uses backtracking to identify the rules affected by the change.
Show an example where backtracking identifies the affected rules.
9. Draw a flow chart for the algorithm of the calculation of profit-loss given the cost
price, selling price, and quantity.
10. Write the pseudo code for an algorithm that compares the strings and identifies
the substring within a string.
Further reading
The following books are recommended if you want to find out more about Gang of Four
design patterns, algorithms, and data structures:
Design Patterns, by Erich Gamma, Richard Helm, Ralph Johnson, and John
Vlissides
Introduction to Algorithms – Third Edition, by Thomas H. Cormen, Charles E.
Leiserson, Ronald L. Rivest, and Clifford Stein
Data structures and Algorithms: An Easy Introduction, by Rudolph Russell
[ 51 ]
2
Getting Started with Go for
Data Structures and Algorithms
The Go programming language has been rapidly adopted by developers for building web
applications. With its impressive performance and ease of development, Go enjoys the
support of a wide variety of open source frameworks for building scalable and highly
performant web services and apps. The migration to Golang has taken place mainly
because of its fast, lightweight, and inbuilt concurrency features. This brings with it the
need to learn data structures and algorithms with this growing language.
In data structures, a collection of elements of a single type is called an array. Slices are
similar to arrays except that they have unusual properties. Slice operations such as
enlarging a slice using append and copy methods, assigning parts of a slice, appending a
slice, and appending a part of a slice are presented with code samples. Database operations
and CRUD web forms are the scenarios in which Go data structures and algorithms are
demonstrated.
Arrays
Slices
Two-dimensional slices
Maps
Database operations
Variadic functions
CRUD web forms
Getting Started with Go for Data Structures and Algorithms Chapter 2
Technical requirements
Install Go Version 1.10 at https://golang.org/doc/install, depending on your operating
system.
The code files for this chapter can be found at the following GitHub URL: https://github.
com/PacktPublishing/Learn-Data-Structures-and-Algorithms-with-Golang/tree/
master/Chapter02.
Arrays
Arrays are the most famous data structures in different programming languages. Different
data types can be handled as elements in arrays such as int, float32, double, and others.
The following code snippet shows the initialization of an array (arrays.go):
var arr = [5]int {1,2,4,5,6}
An array's size can be found with the len() function. A for loop is used for accessing all
the elements in an array, as follows:
var i int
for i=0; i< len(arr); i++ {
fmt.Println("printing elements ",arr[i]
}
In the following code snippet, the range keyword is explained in detail. The range
keyword can be used to access the index and value for each element:
var value int
for i, value = range arr{
fmt.Println(" range ",value)
}
[ 53 ]
Getting Started with Go for Data Structures and Algorithms Chapter 2
The _ blank identifier is used if the index is ignored. The following code shows how a _
blank identifier can be used:
for _, value = range arr{
fmt.Println("blank range",value)
}
Go arrays are not dynamic but have a fixed size. To add more elements than the size, a
bigger array needs to be created and all the elements of the old one need to be copied. An
array is passed as a value through functions by copying the array. Passing a big array to a
function might be a performance issue.
Now that we have covered what arrays are, let's take a look at slices in the next section.
Slices
Go Slice is an abstraction over Go Array. Multiple data elements of the same type are
allowed by Go arrays. The definition of variables that can hold several data elements of the
same type are allowed by Go Array, but it does not have any provision of inbuilt methods
to increase its size in Go. This shortcoming is taken care of by Slices. A Go slice can be
appended to elements after the capacity has reached its size. Slices are dynamic and can
double the current capacity in order to add more elements.
[ 54 ]
Getting Started with Go for Data Structures and Algorithms Chapter 2
Slice function
Slices are passed by referring to functions. Big slices can be passed to functions without
impacting performance. Passing a slice as a reference to a function is demonstrated in the
code as follows (slices.go):
//twiceValue method given slice of int type
func twiceValue(slice []int) {
var i int
var value int
for i, value = range slice {
slice[i] = 2*value
}
}
// main method
func main() {
[ 55 ]
Getting Started with Go for Data Structures and Algorithms Chapter 2
Now that we know what slices are, let's move on to two-dimensional slices in the next
section.
Two-dimensional slices
Two-dimensional slices are descriptors of a two-dimensional array. A two-dimensional
slice is a contiguous section of an array that is stored away from the slice itself. It holds
references to an underlying array. A two-dimensional slice will be an array of arrays, while
the capacity of a slice can be increased by creating a new slice and copying the contents of
the initial slice into the new one. This is also referred to as a slice of slices. The following is
an example of a two-dimensional array. A 2D array is created and the array elements are
initialized with values.
[ 56 ]
Getting Started with Go for Data Structures and Algorithms Chapter 2
func main() {
var TwoDArray [8][8]int
TwoDArray[3][6] = 18
TwoDArray[7][4] = 3
fmt.Println(TwoDArray)
}
For dynamic allocation, we use slice of slices. In the following code, slice of slices is
explained as two-dimensional slices—twodslices.go:
// in Go Data Structures and algorithms book
package main
// importing fmt package
import (
"fmt"
)
// main method
func main() {
var rows int
var cols int
rows = 7
cols = 9
var twodslices = make([][]int, rows)
var i int
for i = range twodslices {
twodslices[i] = make([]int,cols)
}
fmt.Println(twodslices)
}
[ 57 ]
Getting Started with Go for Data Structures and Algorithms Chapter 2
The append method on the slice is used to append new elements to the slice. If the slice
capacity has reached the size of the underlying array, then append increases the size by
creating a new underlying array and adding the new element. slic1 is a sub slice of arr
starting from zero to 3 (excluded), while slic2 is a sub slice of arr starting from 1
(inclusive) to 5 (excluded). In the following snippet, the append method calls on slic2 to
add a new 12 element (append_slice.go):
var arr = [] int{5,6,7,8,9}
var slic1 = arr[: 3]
fmt.Println("slice1",slic1)
var slic2 = arr[1:5]
fmt.Println("slice2",slic2)
var slic3 = append(slic2, 12)
fmt.Println("slice3",slic3)
Now that we have covered what two-dimensional slices are, let's take a look at maps in the
next section.
[ 58 ]
Getting Started with Go for Data Structures and Algorithms Chapter 2
Maps
Maps are used to keep track of keys that are types, such as integers, strings, float, double,
pointers, interfaces, structs, and arrays. The values can be of different types. In the
following example, the language of the map type with a key integer and a value string is
created (maps.go):
var languages = map[int]string {
3: “English”,
4: “French”,
5: “Spanish”
}
Maps can be created using the make method, specifying the key type and the value type.
Products of a map type with a key integer and value string are shown in the following code
snippet:
var products = make(map[int]string)
products[1] = “chair”
products[2] = “table”
A for loop is used for iterating through the map. The languages map is iterated as follows:
var i int
var value string
for i, value = range languages {
fmt.Println("language",i, “:",value)
}
fmt.Println("product with key 2",products[2])
Retrieving value and deleting slice operations using the products map is shown in the
following code:
fmt.Println(products[2])
delete(products,”chair”)
fmt.Println("products",products)
[ 59 ]
Getting Started with Go for Data Structures and Algorithms Chapter 2
Database operations
In this section, we will take a look at some of database operations using appropriate
examples.
// Customer Class
type Customer struct {
CustomerId int
[ 60 ]
Getting Started with Go for Data Structures and Algorithms Chapter 2
CustomerName string
SSN string
}
// GetConnection method which returns sql.DB
func GetConnection() (database *sql.DB) {
databaseDriver := "mysql"
databaseUser := "newuser"
databasePass := "newuser"
databaseName := "crm"
database, error := sql.Open(databaseDriver,
databaseUser+":"+databasePass+"@/"+databaseName)
if error != nil {
panic(error.Error())
}
return database
}
// GetCustomers method returns Customer Array
func GetCustomers() []Customer {
var database *sql.DB
database = GetConnection()
defer database.Close()
[ 61 ]
Getting Started with Go for Data Structures and Algorithms Chapter 2
return customers
}
//main method
func main() {
[ 62 ]
Getting Started with Go for Data Structures and Algorithms Chapter 2
defer database.Close()
Variadic functions
A function in which we pass an infinite number of arguments, instead of passing them one
at a time, is called a variadic function. The type of the final parameter is preceded by an
ellipsis (...), while declaring a variadic function; this shows us that the function might be
called with any number of arguments of this type.
[ 63 ]
Getting Started with Go for Data Structures and Algorithms Chapter 2
[ 64 ]
Getting Started with Go for Data Structures and Algorithms Chapter 2
[ 65 ]
Getting Started with Go for Data Structures and Algorithms Chapter 2
Now that we are done with variadic functions, let's go ahead and look at CRUD web forms
in the next section.
To start a basic HTML page with the Go net/http package, the web forms example is as
follows (webforms.go). This has a welcome greeting in main.html:
//main package has examples shown
// in Hands-On Data Structures and algorithms with Go book
package main
// importing fmt, database/sql, net/http, text/template package
import (
"net/http"
"text/template"
"log")
// Home method renders the main.html
func Home(writer http.ResponseWriter, reader *http.Request) {
var template_html *template.Template
template_html = template.Must(template.ParseFiles("main.html"))
template_html.Execute(writer,nil)
}
// main method
func main() {
log.Println("Server started on: http://localhost:8000")
http.HandleFunc("/", Home)
[ 66 ]
Getting Started with Go for Data Structures and Algorithms Chapter 2
http.ListenAndServe(":8000", nil)
}
[ 67 ]
Getting Started with Go for Data Structures and Algorithms Chapter 2
As shown in the following code, the GetCustomerById method takes the customerId
parameter to look up in the customer database. The GetCustomerById method returns the
customer object:
//GetCustomerById with parameter customerId returns Customer
func GetCustomerById(customerId int) Customer {
var database *sql.DB
database = GetConnection()
var error error
var rows *sql.Rows
rows, error = database.Query("SELECT * FROM Customer WHERE
CustomerId=?",customerId)
if error != nil {
panic(error.Error())
}
var customer Customer
customer = Customer{}
for rows.Next() {
var customerId int
var customerName string
var SSN string
error = rows.Scan(&customerId, &customerName, &SSN)
if error != nil {
panic(error.Error())
}
customer.CustomerId = customerId
customer.CustomerName = customerName
customer.SSN = SSN
[ 68 ]
Getting Started with Go for Data Structures and Algorithms Chapter 2
Now that we have covered CRUD web forms, let's move on to defer and panic in the next
section.
[ 69 ]
Getting Started with Go for Data Structures and Algorithms Chapter 2
[ 70 ]
Getting Started with Go for Data Structures and Algorithms Chapter 2
Let's take a look at the CRM web application in the next section.
[ 71 ]
Getting Started with Go for Data Structures and Algorithms Chapter 2
customers = GetCustomers()
log.Println(customers)
template_html.ExecuteTemplate(writer,"Home",customers)
Let's take a look at the Create, Insert, Alter, Update, and Delete functions, as well as
the main method in the following sections.
template_html.ExecuteTemplate(writer,"Create",nil)
}
[ 72 ]
Getting Started with Go for Data Structures and Algorithms Chapter 2
template_html.ExecuteTemplate(writer,"Update",customer)
[ 73 ]
Getting Started with Go for Data Structures and Algorithms Chapter 2
}
// View - execute Template
func View(writer http.ResponseWriter, request *http.Request) {
var customerId int
var customerIdStr string
customerIdStr = request.FormValue("id")
fmt.Sscanf(customerIdStr, "%d", &customerId)
var customer Customer
customer = GetCustomerById(customerId)
fmt.Println(customer)
var customers []Customer
customers= []Customer{customer}
customers.append(customer)
template_html.ExecuteTemplate(writer,"View",customers)
// main method
func main() {
log.Println("Server started on: http://localhost:8000")
http.HandleFunc("/", Home)
[ 74 ]
Getting Started with Go for Data Structures and Algorithms Chapter 2
http.HandleFunc("/alter", Alter)
http.HandleFunc("/create", Create)
http.HandleFunc("/update", Update)
http.HandleFunc("/view", View)
http.HandleFunc("/insert", Insert)
http.HandleFunc("/delete", Delete)
http.ListenAndServe(":8000", nil)
}
Let's take a look at the Header, Footer, Menu, Create, Update, and View templates in the
following sections.
{{ define "Header" }}
<!DOCTYPE html>
<html>
<head>
<title>CRM</title>
<meta charset="UTF-8" />
</head>
<body>
<h1>Customer Management – CRM</h1>
{{ end }}
[ 75 ]
Getting Started with Go for Data Structures and Algorithms Chapter 2
[ 76 ]
Getting Started with Go for Data Structures and Algorithms Chapter 2
{{ template "Menu" }}
<br>
<h1>Update Customer</h1>
<br>
<br>
<form method="post" action="/alter">
<input type="hidden" name="id" value="{{ .CustomerId }}" />
Customer Name: <input type="text" name="customername"
placeholder="customername" value="{{ .CustomerName }}" autofocus>
<br>
<br>
SSN: <input type="text" name="ssn" value="{{ .SSN }}"
placeholder="ssn"/>
<br>
<br>
<input type="submit" value="Update Customer"/>
</form>
{{ template "Footer" }}
{{ end }}
[ 77 ]
Getting Started with Go for Data Structures and Algorithms Chapter 2
[ 78 ]
Getting Started with Go for Data Structures and Algorithms Chapter 2
Summary
This chapter introduced database operations and web forms. Now, you will be able to build
web applications that can store data in databases. Arrays, slices, two-dimensional slices,
and maps were covered with code samples. Array methods such as len, iterating through
an array using for, and range were explained in this chapter using code snippets. Two-
dimensional arrays and slice of slices were discussed in the Slices section.
Maps were explained with various scenarios such as adding keys and values, as well as
retrieving and deleting values. Maps of different types, such as strings and integers, were
also discussed in this chapter. Furthermore, variadic functions, deferred function calls, and
panic and recover operations were demonstrated in the Database operations and CRUD web
forms sections.
The CRM application was built as a web application with data persisted in the MySQL
database. Database operations for adding, deleting, updating, and retrieving data were
shown in code snippets. In addition, web forms for creating, updating, deleting, and
viewing customer data were presented using web forms with templates. MySQL driver and
its installation details were provided in the Technical requirements section of this chapter.
How to create a web application using Go was demonstrated with execution details.
The next chapter will have topics related to linear data structures such as lists, sets, tuples,
and stacks.
Questions
1. What is the name of the method to get the size of an array?
2. How do you find the capacity of the slice?
3. How do you initialize the 2D slice of a string type?
4. How do you add an element to the slice?
5. Using code, can you demonstrate how to create a map of key strings and value
strings? Initialize the map with keys and values in the code, iterate them in a
loop, and print the keys and values in the code.
[ 79 ]
Getting Started with Go for Data Structures and Algorithms Chapter 2
Further reading
To read more about arrays, maps and slices, the following links are recommended:
[ 80 ]
2
Section 2: Basic Data
Structures and Algorithms using
Go
We will talk about data structures, including linear, non-linear, homogeneous,
heterogeneous, and dynamic types, as well as classic algorithms. This section covers
different types of lists, trees, arrays, sets, dictionaries, tuples, heaps, queues, and stacks,
along with sorting, recursion, searching, and hashing algorithms.
In this chapter, we will discuss these data structures by giving examples of various
procedures involving them. We will discuss the various operations related to these data
structures, such as insertion, deletion, updating, traversing (of lists), reversing, and
merging with various code samples.
Lists
Sets
Tuples
Stacks
Technical requirements
Install Go version 1.10 at https://golang.org/doc/install, depending on your operating
system.
The code files for this chapter can be found at the following GitHub URL: https://github.
com/PacktPublishing/Learn-Data-Structures-and-Algorithms-with-Golang/tree/
master/Chapter03.
Linear Data Structures Chapter 3
Lists
A list is a collection of ordered elements that are used to store list of items. Unlike array
lists, these can expand and shrink dynamically.
Lists also be used as a base for other data structures, such as stack and queue. Lists can be
used to store lists of users, car parts, ingredients, to-do items, and various other such
elements. Lists are the most commonly used linear data structures. These were introduced
in the lisp programming language. In this chapter, linked list and doubly linked list are the
lists we will cover in the Go language.
Let's discuss the operations related to add, update, remove, and lookup on linked list and
doubly linked list in the following section.
LinkedList
LinkedList is a sequence of nodes that have properties and a reference to the next node in
the sequence. It is a linear data structure that is used to store data. The data structure
permits the addition and deletion of components from any node next to another node. They
are not stored contiguously in memory, which makes them different arrays.
The following sections will look at the structures and methods in a linked list.
[ 83 ]
Linear Data Structures Chapter 3
When the node with the 1 property is added to the head, adding the 1 property to the head
of linkedList sets headNode to currentNode with a value of 1, as you can see in the
following screenshot:
[ 84 ]
Linear Data Structures Chapter 3
Let's execute this command using the main method. Here, we have created an instance of a
LinkedList class and added the 1 and 3 integer properties to the head of this instance.
The linked list's headNode property is printed after adding the elements, as follows:
// main method
func main() {
var linkedList LinkedList
linkedList = LinkedList{}
linkedList.AddToHead(1)
linkedList.AddToHead(3)
fmt.Println(linkedList.headNode.property)
}
[ 85 ]
Linear Data Structures Chapter 3
In the following screenshot, the AddToEnd method is invoked when the node with a
property value of 5 is added to the end. Adding the property through this method creates a
node with a value of 5. The last node of the list has a property value of 5. The
nextNode property of lastNode is nil. The nextNode of lastNode is set to the node with
a value of 5:
[ 86 ]
Linear Data Structures Chapter 3
//AddAfter method adds a node with nodeProperty after node with property
[ 87 ]
Linear Data Structures Chapter 3
You then get the following output when the AddAfter method is invoked when the node
with a property value of 7 is added after the node with a value of 1. The
nextNode property of the node with a property value of 1 is nil. The nextNode property of
the node with a property value of 1 is set to the node with a value of 5:
The main method adds 1 and 3 to the head of the linked list. 5 is added to the end. 7 is
added after 1. The linked list will be 3, 1, 7, and 5.
[ 88 ]
Linear Data Structures Chapter 3
[ 89 ]
Linear Data Structures Chapter 3
The example output after the node between the values method was invoked with 1 and 5 is
shown in the following screenshot. The nextNode of the lastNode is set to the node with a
value of 5. The node with a property value of 7 is between the nodes with property values
of 1 and 5:
[ 90 ]
Linear Data Structures Chapter 3
The example output after the AddToHead method was invoked with property 3 is as
follows. A node with property 3 is created. The headNode property of the list has a
property value of 1. The current node with property 3 has a nextNode property of nil. The
nextNode property of the current node is set to headNode with a property value of 1. The
previous node of the headNode property is set to the current node:
AddAfter method
The AddAfter method adds a node after a specific node to a double linked list.
The AddAfter method of the double LinkedList class searches the node whose value is
equal to nodeProperty. The found node is set as the previousNode of the node that was
added with property. The nextNode of the added node will be the nodeWith property's
nextNode. The previousNode of the added node will be the node that was found with
value equal to nodeProperty. The nodeWith node will be updated to the current node. In
the following code, the AddAfter method is shown:
//AddAfter method of LinkedList
func (linkedList *LinkedList) AddAfter(nodeProperty int,property int) {
var node = &Node{}
node.property = property
node.nextNode = nil
var nodeWith *Node
nodeWith = linkedList.NodeWithValue(nodeProperty)
if nodeWith != nil {
node.nextNode = nodeWith.nextNode
node.previousNode = nodeWith
nodeWith.nextNode = node
}
}
[ 91 ]
Linear Data Structures Chapter 3
The example output after the AddAfter method is invoked with property 7 is as follows. A
node with property value 7 is created. The nextNode property of the created node is nil.
The nextNode property of the created node is set to headNode with property value 1. The
previousNode property of headNode is set to the current node:
lastNode.nextNode = node
node.previousNode = lastNode
}
}
[ 92 ]
Linear Data Structures Chapter 3
The example output after the AddToEnd method was invoked with property 5 is as follows.
A node with property value 5 is created. The lastNode of the list has property value 1. The
nextNode property of the lastNode is nil. The nextNode of the lastNode is set to the
node with property value 5. The previousNode of the created node is set to the node with
property value 1:
The main method creates a linked list. The nodes are added to the head and end. The node
between values 1 and 5 is searched and its property is printed.
[ 93 ]
Linear Data Structures Chapter 3
The next section talks about sets, which are linear data structures.
Sets
A Set is a linear data structure that has a collection of values that are not repeated. A set can
store unique values without any particular order. In the real world, sets can be used to
collect all tags for blog posts and conversation participants in a chat. The data can be of
Boolean, integer, float, characters, and other types. Static sets allow only query operations,
which means operations related to querying the elements. Dynamic and mutable sets allow
for the insertion and deletion of elements. Algebraic operations such as union, intersection,
difference, and subset can be defined on the sets. The following example shows
the Set integer with a map integer key and bool as a value:
//main package has examples shown
// in Hands-On Data Structures and algorithms with Go book
package main
// importing fmt package
import (
"fmt"
)
//Set class
type Set struct {
integerMap map[int]bool
}
//create the map of integer and bool
func (set *Set) New(){
set.integerMap = make(map[int]bool)
}
[ 94 ]
Linear Data Structures Chapter 3
The example output after invoking the AddElement method with parameter 2 is as
follows. The check is done if there is an element with value 2. If there is no element, the
map is set to true with the key as 2:
[ 95 ]
Linear Data Structures Chapter 3
[ 96 ]
Linear Data Structures Chapter 3
The example output after invoking the intersect with the parameter of another Set is as
follows. A new intersectSet is created. The current set is iterated and every value is
checked to see if it is in another set. If the value is in another set, it is added to
the set intersect:
[ 97 ]
Linear Data Structures Chapter 3
unionSet.New()
var value int
for(value,_ = range set.integerMap){
unionSet.AddElement(value)
}
return unionSet
}
The example output after invoking the union method with the anotherSet parameter is as
follows. A new unionSet is created. The current set and another set values are iterated.
Every value is added to the union set:
[ 98 ]
Linear Data Structures Chapter 3
anotherSet = &Set{}
anotherSet.New()
anotherSet.AddElement(2)
anotherSet.AddElement(4)
anotherSet.AddElement(5) fmt.Println(set.Intersect(anotherSet))
fmt.Println(set.Union(anotherSet))
}
The main method takes two sets and finds the intersection and union of the sets.
The next section talks about tuples, which are finite ordered sequences of objects.
Tuples
Tuples are finite ordered sequences of objects. They can contain a mixture of other data
types and are used to group related data into a data structure. In a relational database, a
tuple is a row of a table. Tuples have a fixed size compared to lists, and are also faster. A
finite set of tuples in the relational database is referred to as a relation instance. A tuple can
be assigned in a single statement, which is useful for swapping values. Lists usually contain
values of the same data type, while tuples contain different data. For example, we can store
a name, age, and favorite color of a user in a tuple. Tuples were covered in Chapter 1, Data
Structures and Algorithms. The following sample shows a multi-valued expression from a
function's call (tuples.go):
//main package has examples shown
// in Hands-On Data Structures and algorithms with Go book
package main
// importing fmt package
import (
"fmt"
)
[ 99 ]
Linear Data Structures Chapter 3
The main function calls the h function with the g function as its parameter. The g function
returns the tuple x and y integers.
The next section talks about queues, which are linear data structures.
Queues
A queue consists of elements to be processed in a particular order or based on priority. A
priority-based queue of orders is shown in the following code, structured as a heap.
Operations such as enqueue, dequeue, and peek can be performed on queue. A queue is a
linear data structure and a sequential collection. Elements are added to the end and are
removed from the start of the collection. Queues are commonly used for storing tasks that
need to be done, or incoming HTTP requests that need to be processed by a server. In real
life, handling interruptions in real-time systems, call handling, and CPU task scheduling
are good examples for using queues.
[ 100 ]
Linear Data Structures Chapter 3
The following code shows the queue of Orders and how the Queue type is defined:
// Queue—Array of Orders Type
type Queue []*Order
// Order class
type Order struct {
priority int
quantity int
product string
customerName string
}
The following sections in the chapter discuss the New, Add, and main methods of queue.
[ 101 ]
Linear Data Structures Chapter 3
appended = false
var i int
var addedOrder *Order
for i, addedOrder = range *queue {
if order.priority > addedOrder.priority {
*queue = append((*queue)[:i], append(Queue{order}, (*queue)[i:]...)...)
appended = true
break
}
}
if !appended {
*queue = append(*queue, order)
}
}
}
The example output after the add method is invoked with the order parameter is as
follows. The order is checked to see whether or not it exists in the queue. The order is then
appended to the queue:
[ 102 ]
Linear Data Structures Chapter 3
queue.Add(order2)
var i int
for i=0; i< len(queue); i++ {
fmt.Println(queue[i])
}
}
Synchronized queue
A synchronized queue consists of elements that need to be processed in a particular
sequence. Passenger queue and ticket processing queues are types of synchronized queues,
as follows:
//main package has examples shown
// in Hands-On Data Structures and algorithms with Go book
package main
// importing fmt package
import (
"fmt"
"time"
"math/rand"
)
// constants
const (
messagePassStart = iota
messageTicketStart
[ 103 ]
Linear Data Structures Chapter 3
messagePassEnd
messageTicketEnd
)
//Queue class
type Queue struct {
waitPass int
waitTicket int
playPass bool
playTicket bool
queuePass chan int
queueTicket chan int
message chan int
}
We will discuss the different methods of synchronized queue in the following sections.
In the following code example, the Go routine handles selecting the message based on the
type of message and the respective queue to process it:
go func() {
var message int
for {
select {
case message = <-queue.message:
switch message {
case messagePassStart:
queue.waitPass++
case messagePassEnd:
queue.playPass = false
case messageTicketStart:
queue.waitTicket++
case messageTicketEnd:
queue.playTicket = false
}
if queue.waitPass > 0 && queue.waitTicket > 0 && !queue.playPass &&
[ 104 ]
Linear Data Structures Chapter 3
!queue.playTicket {
queue.playPass = true
queue.playTicket = true
queue.waitTicket--
queue.waitPass--
queue.queuePass <- 1
queue.queueTicket <- 1
}
}
}
}()
}
[ 105 ]
Linear Data Structures Chapter 3
[ 106 ]
Linear Data Structures Chapter 3
[ 107 ]
Linear Data Structures Chapter 3
select {}
}
The next section talks about Stacks, which are linear data structures.
Stacks
A stack is a last in, first out structure in which items are added from the top. Stacks are used
in parsers for solving maze algorithms. Push, pop, top, and get size are the typical
operations that are allowed on stack data structures. Syntax parsing, backtracking, and
compiling time memory management are some real-life scenarios where stacks can be used.
An example of stack implementation is as follows (stack.go):
//main package has examples shown
// in Hands-On Data Structures and algorithms with Go book
package main
// importing fmt package
import (
"fmt"
"strconv"
[ 108 ]
Linear Data Structures Chapter 3
)
//Element class
type Element struct {
elementValue int
}
// String method on Element class
func (element *Element) String() string {
return strconv.Itoa(element.elementValue)
}
The Element class has elementValue as an attribute. The String method returns the
element's elementValue.
Stacks methods, such as New, Push, Pop, and main are presented in the following sections.
[ 109 ]
Linear Data Structures Chapter 3
The example output after the push method is invoked with parameter elements as follows.
The element with the value 7 is pushed to the stack. The count of the elements before
pushing to the stack is 2, and, after pushing to the stack, this figure is 3:
The example output after the Pop method is invoked is as follows. The element value 5 is
passed and added to the Pop method. The count of elements before invoking the Pop
method is 2. The count of the elements after calling the Pop method is 1:
[ 110 ]
Linear Data Structures Chapter 3
Summary
This chapter covered the definition of LinkedList, double LinkedList, Tuples, Sets,
Queues, and Stacks. The LinkedList methods – AddToHead, AddToEnd, LastNode, and
iterateList—were also covered in this chapter. In addition, a priority queue was
modeled as a heap of orders to be processed, sync queue was presented as passenger and
ticket processing queues, and tuples were explained in a context in which a function returns
a multivalued expression. The new, push, pop, and string methods for Stack were
explained with code samples.
[ 111 ]
Linear Data Structures Chapter 3
In the next chapter, we will cover areas such as the Trees, Tables, Containers, and Hash
functions.
Questions
1. Where can you use double linked list? Please provide an example.
2. Which method on linked list can be used for printing out node values?
3. Which queue was shown with channels from the Go language?
4. Write a method that returns multiple values. What data structure can be used for
returning multiple values?
5. Can set have duplicate elements?
6. Write a code sample showing the union and intersection of two sets.
7. In a linked list, which method is used to find the node between two values?
8. We have elements that are not repeated and unique. What is the correct data
structure that represents the collection?
9. In Go, how do you generate a random integer between the values 3 and 5?
10. Which method is called to check if an element of value 5 exists in the Set?
Further reading
To read more about LinkedLists, Sets, Tuples, and Stacks, consult the following
sources:
Design Patterns, by Erich Gamma, Richard Helm, Ralph Johnson, and John
Vlissides
Introduction to Algorithms – Third Edition, by Thomas H. Cormen, Charles E.
Leiserson, Ronald L. Rivest, and Clifford Stein
Data structures and Algorithms: An Easy Introduction, by Rudolph Russell
[ 112 ]
4
Non-Linear Data Structures
Non-linear data structures are used in cryptography and other areas. A non-linear data
structure is an arrangement in which an element is connected to many elements. These
structures use memory quickly and efficiently. Free contiguous memory is not required for
adding new elements.
The length of the data structures is not important before adding new elements. A non-linear
data structure has multiple levels and a linear one has a single level. The values of the
elements are not organized in a non-linear data structure. The data elements in a non-linear
data structure cannot be iterated in one step. The implementation of these data structures is
complicated.
Tree types such as binary search trees, treaps, and symbol tables are explained in this
chapter.
Trees
Tables
Containers
Hash functions
Technical requirements
Install Go version 1.10 from https://golang.org/doc/install for your OS.
The GitHub URL for the code in this chapter is as follows: https://github.com/
PacktPublishing/Learn-Data-Structures-and-Algorithms-with-Golang/tree/master/
Chapter04.
Non-Linear Data Structures Chapter 4
Trees
A tree is a non-linear data structure. Trees are used for search and other use cases. A binary
tree has nodes that have a maximum of two children. A binary search tree consists of nodes
where the property values of the left node are less than the property values of the right
node. The root node is at level zero of a tree. Each child node could be a leaf.
Trees and binary trees were introduced in Chapter 1, Data Structures and Algorithms, while
we were discussing logarithmic complexity. Let's take a closer look at them in the next
section.
A key integer
A value integer
The leftNode and rightNode instances of TreeNode
In the next section, the BinarySearchTree class implementation is discussed. For this
section, please refer to the binary_search_tree.go file.
[ 114 ]
Non-Linear Data Structures Chapter 4
Now that we know what BinarySearchTree is, let's take a look at its different methods in
the next section.
// InsertElement method
func (tree *BinarySearchTree) InsertElement(key int, value int) {
tree.lock.Lock()
defer tree.lock.Unlock()
var treeNode *TreeNode
treeNode= &TreeNode{key, value, nil, nil}
if tree.rootNode == nil {
tree.rootNode = treeNode
} else {
insertTreeNode(tree.rootNode, treeNode)
}
}
The example output for inserting an element with key and value 3 is shown as follows. The
insert element method calls insertTreeNode with rootNode with key 8 and the new
treeNode with key 3:
[ 115 ]
Non-Linear Data Structures Chapter 4
[ 116 ]
Non-Linear Data Structures Chapter 4
// PreOrderTraverseTree method
func (tree *BinarySearchTree) PreOrderTraverseTree(function func(int)) {
tree.lock.Lock()
defer tree.lock.Unlock()
preOrderTraverseTree(tree.rootNode, function)
}
// preOrderTraverseTree method
func preOrderTraverseTree(treeNode *TreeNode, function func(int)) {
if treeNode != nil {
function(treeNode.value)
preOrderTraverseTree(treeNode.leftNode, function)
preOrderTraverseTree(treeNode.rightNode, function)
}
[ 117 ]
Non-Linear Data Structures Chapter 4
// PostOrderTraverseTree method
func (tree *BinarySearchTree) PostOrderTraverseTree(function func(int)) {
tree.lock.Lock()
defer tree.lock.Unlock()
postOrderTraverseTree(tree.rootNode, function)
}
// postOrderTraverseTree method
func postOrderTraverseTree(treeNode *TreeNode, function func(int)) {
if treeNode != nil {
postOrderTraverseTree(treeNode.leftNode, function)
postOrderTraverseTree(treeNode.rightNode, function)
function(treeNode.value)
}
}
// MinNode method
func (tree *BinarySearchTree) MinNode() *int {
[ 118 ]
Non-Linear Data Structures Chapter 4
tree.lock.RLock()
defer tree.lock.RUnlock()
var treeNode *TreeNode
treeNode = tree.rootNode
if treeNode == nil {
//nil instead of 0
return (*int)(nil)
}
for {
if treeNode.leftNode == nil {
return &treeNode.value
}
treeNode = treeNode.leftNode
}
}
[ 119 ]
Non-Linear Data Structures Chapter 4
[ 120 ]
Non-Linear Data Structures Chapter 4
tree.lock.Lock()
defer tree.lock.Unlock()
removeNode(tree.rootNode, key)
}
[ 121 ]
Non-Linear Data Structures Chapter 4
[ 122 ]
Non-Linear Data Structures Chapter 4
[ 123 ]
Non-Linear Data Structures Chapter 4
The following sections talks about the KeyValue interface definition and the TreeNode
class. For this section, please refer to the avl_tree.go file.
Now, let's take a look at the different methods of the TreeNode class.
[ 124 ]
Non-Linear Data Structures Chapter 4
// double rotation
func doubleRotation(rootNode *TreeNode, nodeValue int) *TreeNode {
var saveNode *TreeNode
saveNode =
rootNode.LinkedNodes[opposite(nodeValue)].LinkedNodes[nodeValue]
rootNode.LinkedNodes[opposite(nodeValue)].LinkedNodes[nodeValue] =
saveNode.LinkedNodes[opposite(nodeValue)]
saveNode.LinkedNodes[opposite(nodeValue)] =
rootNode.LinkedNodes[opposite(nodeValue)]
rootNode.LinkedNodes[opposite(nodeValue)] = saveNode
saveNode = rootNode.LinkedNodes[opposite(nodeValue)]
rootNode.LinkedNodes[opposite(nodeValue)] =
[ 125 ]
Non-Linear Data Structures Chapter 4
saveNode.LinkedNodes[nodeValue]
saveNode.LinkedNodes[nodeValue] = rootNode
return saveNode
}
The implementation of this method is shown in The InsertNode method section, as follows.
[ 126 ]
Non-Linear Data Structures Chapter 4
if node.BalanceValue == balance {
rootNode.BalanceValue = 0
node.BalanceValue = 0
return singleRotation(rootNode, opposite(nodeValue))
}
adjustBalance(rootNode, nodeValue, balance)
return doubleRotation(rootNode, opposite(nodeValue))
}
//insertRNode method
func insertRNode(rootNode *TreeNode, key KeyValue) (*TreeNode, bool) {
if rootNode == nil {
return &TreeNode{KeyValue: key}, false
}
var dir int
dir = 0
if rootNode.KeyValue.LessThan(key) {
dir = 1
}
var done bool
rootNode.LinkedNodes[dir], done = insertRNode(rootNode.LinkedNodes[dir],
key)
if done {
return rootNode, true
}
rootNode.BalanceValue = rootNode.BalanceValue+(2*dir - 1)
switch rootNode.BalanceValue {
case 0:
return rootNode, true
case 1, -1:
return rootNode, false
}
return BalanceTree(rootNode, dir), true
}
[ 127 ]
Non-Linear Data Structures Chapter 4
The example output of the InsertNode method is shown in the following screenshot. The
InsertNode method calls the insertRNode method with the rootNode parameters and
node to be inserted. rootNode has a key value of 5 and the node to be inserted has a key
value of 6. The tree needs to be balanced.
Hence, the next call will be rootNode with key 8 and node to be inserted. The next step
calls rootnode with key value 7 and node to be inserted. The last call will be with
rootNode nil and node to be inserted. The balance value is checked and the balance tree
method returns the balanced tree:
// RemoveNode method
func RemoveNode(treeNode **TreeNode, key KeyValue) {
*treeNode, _ = removeRNode(*treeNode, key)
}
[ 128 ]
Non-Linear Data Structures Chapter 4
// removeBalance method
func removeBalance(rootNode *TreeNode, nodeValue int) (*TreeNode, bool) {
var node *TreeNode
node = rootNode.LinkedNodes[opposite(nodeValue)]
var balance int
balance = 2*nodeValue - 1
switch node.BalanceValue {
case -balance:
rootNode.BalanceValue = 0
node.BalanceValue = 0
return singleRotation(rootNode, nodeValue), false
case balance:
adjustBalance(rootNode, opposite(nodeValue), -balance)
return doubleRotation(rootNode, nodeValue), false
}
rootNode.BalanceValue = -balance
node.BalanceValue = balance
return singleRotation(rootNode, nodeValue), true
}
[ 129 ]
Non-Linear Data Structures Chapter 4
The example output of the removeRNode method is shown as follows. The RemoveNode
method calls the removeRNode method. The removeRNode method takes the parameters,
such as rootNode and KeyValue, of the node:
[ 130 ]
Non-Linear Data Structures Chapter 4
[ 131 ]
Non-Linear Data Structures Chapter 4
In the next section, B+ tree implementation is discussed and code snippets are presented.
[ 132 ]
Non-Linear Data Structures Chapter 4
B+ tree
The B+ tree contains a list of keys and pointers to the next-level nodes in trees. During a
search, recursion is used to search for an element by looking for the the adjacent node keys.
B+ trees are used to store data in filesystems. B+ trees require fewer I/O operations to search
for a node in the tree. Fan-out is defined as the number of nodes pointing to the child nodes
of a node in a B+ tree. B+ trees were first described in a technical paper by Rudolf Bayer and
Edward M. McCreight.
The block-oriented storage context in B+ trees helps with the storage and efficient retrieval
of data. The space efficiency of a B+ tree can be enhanced by using compression techniques.
B+ trees belong to a family of multiway search trees. For a b-order B+ tree, space usage is of
the order O(n). Inserting, finding, and removing operations are of the order O(logbn).
B-tree
The B-tree is a search tree with non-leaf nodes that only have keys, and the data is in the
leaves. B-trees are used to reduce the number of disk accesses. The B-tree is a self-adjusting
data structure that keeps data sorted. B-trees store keys in a sorted order for easy traversal.
They can handle multiple insertions and deletions.
Knuth initially came up with the concept of this data structure. B-trees consist of nodes that
have at most n children. Every non-leaf node in the tree has at least n/2 child nodes. Rudolf
Bayer and Edward M. McCreight were the first to implement this data structure in their
work. B-trees are used in HFS and Reiser4 filesystems to allow for quick access to any block
in a file. On average, space usage is in the order of O(n). Insert, search, and delete
operations are in the order of O(log n).
T-tree
The T-tree is a balanced data structure that has both the index and actual data in memory.
They are used in in-memory databases. T refers to the shape of the node. Each node consists
of pointers to the parent node and the left and right child nodes. Each node in the tree node
will have an ordered array of data pointers and extra control data.
[ 133 ]
Non-Linear Data Structures Chapter 4
Tables
As we already know, tables are used in data management and other areas. A table has a
name and a header with the column names. Let's take a look at the different classes in
tables such as the Table class, the Row class, the Column class, and the PrintTable
method in the following sections.
[ 134 ]
Non-Linear Data Structures Chapter 4
[ 135 ]
Non-Linear Data Structures Chapter 4
table.Rows = rows
fmt.Println(table)
printTable(table)
}
The next section talks about the symbol table data structure.
Symbol tables
A symbol table is present in memory during the program translation process. It can be
present in program binaries. A symbol table contains the symbol's name, location, and
address. In Go, the gosym package implements access to the Go symbol and line number
tables. Go binaries generated by the GC compilers have the symbol and line number tables.
A line table is a data structure that maps program counters to line numbers.
Containers
The containers package provides access to the heap, list, and ring functionalities in Go.
Containers are used in social networks, knowledge graphs, and other areas. Containers are
lists, maps, slices, channels, heaps, queues, and treaps. Lists were introduced in Chapter 1,
Data Structures and Algorithms. Maps and slices are built-in containers in Go. Channels in
Go are called queues. A heap is a tree data structure. This data structure satisfies the heap
property. A queue is modeled as a heap in Chapter 3, Linear Data Structures. A treap is a
mix of a tree and a heap. It is a binary tree with keys and values and a heap that maintains
priorities.
[ 136 ]
Non-Linear Data Structures Chapter 4
A ring is called a circular linked list and is presented in the next section. For this section,
please refer to the circular_list.go file.
The ring.New method with the len n as a parameter creates a circular list of length n. The
circular linked list is initialized with an integer array by moving through circular_list
with the Next method. The Do method of ring.Ring class takes the element as an
interface, and the element is printed as follows:
circular_list.Do(func(element interface{}) {
fmt.Print(element,",")
})
fmt.Println()
The reverse of the circular list is traversed using the Prev method, and the value is printed
in the following code:
// reverse of the circular list
for i = 0; i < circular_list.Len(); i++ {
fmt.Print(circular_list.Value,",")
circular_list = circular_list.Prev()
}
fmt.Println()
[ 137 ]
Non-Linear Data Structures Chapter 4
In the following code snippet, the circular list is moved two elements forward using the
Move method, and the value is printed:
The next section talks about the hash function data structure.
[ 138 ]
Non-Linear Data Structures Chapter 4
"crypto/sha256"
"encoding"
"fmt"
"log"
"hash"
)
The main method creates a binary marshaled hash of two example strings. The hashes of
the two strings are printed. The sum of the first hash is compared with the second hash
using the equals method on bytes. This is shown in the following code:
//main method
func main() {
const (
example1 = "this is a example "
example2 = "second example"
)
var firstHash hash.Hash
firstHash = sha256.New()
firstHash.Write([]byte(example1))
var marshaler encoding.BinaryMarshaler
var ok bool
marshaler, ok = firstHash.(encoding.BinaryMarshaler)
if !ok {
log.Fatal("first Hash is not generated by encoding.BinaryMarshaler")
}
var data []byte
var err error
data, err = marshaler.MarshalBinary()
if err != nil {
log.Fatal("failure to create first Hash:", err)
}
var secondHash hash.Hash
secondHash = sha256.New()
var unmarshaler encoding.BinaryUnmarshaler
unmarshaler, ok = secondHash.(encoding.BinaryUnmarshaler)
if !ok {
log.Fatal("second Hash is not generated by encoding.BinaryUnmarshaler")
}
if err := unmarshaler.UnmarshalBinary(data); err != nil {
log.Fatal("failure to create hash:", err)
}
firstHash.Write([]byte(example2))
secondHash.Write([]byte(example2))
fmt.Printf("%x\n", firstHash.Sum(nil))
fmt.Println(bytes.Equal(firstHash.Sum(nil), secondHash.Sum(nil)))
}
[ 139 ]
Non-Linear Data Structures Chapter 4
Summary
This chapter covered trees, binary search trees, and AVL trees. Treap, B-trees, and B+ trees
were explained briefly. Operations such as insertion, deletion, and updating elements in
trees were shown with various code examples. Tables, containers, and hash functions were
presented in the last section. The complexity in time and space for operations such as
insertion, deletion, and search were explained in each section.
In the next chapter, homogeneous data structures such as two-dimensional and multi-
dimensional arrays will be covered.
Questions
1. Can you give an example where you can use a binary search tree?
2. Which method is used to search for an element in a binary search tree?
3. Which techniques are used to adjust the balance in an AVL tree?
4. What is a symbol table?
[ 140 ]
Non-Linear Data Structures Chapter 4
5. Which class and method are called to generate a binary marshaled hash on the
hash class?
6. Which container in Go is used to model a circular linked list?
7. How do you create a JSON (indented) from a tree structure? Which class and
method are used?
8. How do you compare the sum of hashes?
9. What is the balance factor in an AVL tree?
10. How do you identify a row and column in a table?
Further reading
The following books are recommended if you want to know more about trees, binary
search trees, and AVL trees:
Design Patterns, by Erich Gamma, Richard Helm, Ralph Johnson, and John
Vlissides
Introduction to Algorithms – Third Edition, by Thomas H. Cormen, Charles E.
Leiserson, Ronald L. Rivest, and Clifford Stein
Data structures and Algorithms: An Easy Introduction, by Rudolph Russell
[ 141 ]
5
Homogeneous Data Structures
Homogeneous data structures contain similar types of data, such as integers or double
values. Homogeneous data structures are used in matrices, as well as tensor and vector
mathematics. Tensors are mathematical structures for scalars and vectors. A first-rank
tensor is a vector. A vector consists of a row or a column. A tensor with zero rank is a
scalar. A matrix is a two-dimensional cluster of numbers. They are all used in scientific
analysis.
Tensors are used in material science. They are used in mathematics, physics, mechanics,
electrodynamics, and general relativity. Machine learning solutions utilize a tensor data
structure. A tensor has properties such as position, shape, and a static size.
Two-dimensional arrays
Multi-dimensional arrays
The following scenarios are shown to demonstrate the usage of two-dimensional and multi-
dimensional arrays:
Matrix representation
Multiplication
Addition
Subtraction
Determinant calculation
Inversion
Transposition
Homogeneous Data Structures Chapter 5
Technical requirements
Install Go Version 1.10 from https://golang.org/doc/install for your OS.
The GitHub URL for the code in this chapter is as follows: https://github.com/
PacktPublishing/Learn-Data-Structures-and-Algorithms-with-Golang/tree/master/
Chapter05.
Two-dimensional arrays
Two-dimensional arrays were presented briefly in Chapter 2, Getting Started with Go for
Data Structures and Algorithms. To recap, for dynamic allocation, we use slice of slices,
which is a two-dimensional array. A two-dimensional array, is a list of single-dimensional
arrays. Every element in a two-dimensional array arr, is identified as arr[i][j], where
arr is the name of the array and i and j represent rows and columns, and their values
ranging from 0 to m and 0 to n, respectively. Traversing a two-dimensional array is of
O(m*n) complexity.
An element in a two-dimensional array is accessed with a row index and column index. In
the following example, the array's value in row 2 and column 3 is retrieved as an integer
value:
var value int = arr[2][3]
Arrays can store a sequential collection of data elements of the same type. Homogeneous
data structure arrays consist of contiguous memory address locations.
[ 143 ]
Homogeneous Data Structures Chapter 5
The order of a matrix is the number of rows, m, by the number of columns, n. A matrix with
rows m and columns n is referred to as an m x n matrix. There are multiple
types of matrices, such as a row matrix, column matrix, triangular matrix, null matrix, and
zero matrix; let's discuss them in the following sections.
Row matrix
A row matrix is a 1 x m matrix consisting of a single row of m elements, as shown here:
var matrix = [1][3] int{
{1, 2, 3}
}
The next section talks about the column matrix data structure.
Column matrix
A column matrix is an m x 1 matrix that has a single column of m elements. The following
code snippet shows how to create a column matrix:
var matrix = [4][1] int{
{1},
{2},
{3},
{4}
}
[ 144 ]
Homogeneous Data Structures Chapter 5
The next section talks about the lower triangular matrix data structure.
The next section talks about the upper triangular matrix data structure.
[ 145 ]
Homogeneous Data Structures Chapter 5
The next section talks about the null matrix data structure.
Null matrix
A null or a zero matrix is a matrix entirely consisting of zero values, as shown in the
following code snippet:
var matrix = [3][3] int{
{0,0,0},
{0,0,0},
{0,0,0}
}
The next section talks about the identity matrix data structure.
[ 146 ]
Homogeneous Data Structures Chapter 5
Identity matrix
An identity matrix is a unit matrix with ones are on the main diagonal and zeros are
elsewhere. The following code snippet creates an identity matrix:
///main package has examples shown
// in Go Data Structures and algorithms book
package main
The next section talks about the symmetric matrix data structure.
[ 147 ]
Homogeneous Data Structures Chapter 5
Symmetric matrix
A symmetric matrix is a matrix whose transpose is equal to itself. Symmetric matrices
include other types of matrices such as antimetric, centrosymmetric, circulant, covariance,
coxeter, hankel, hilbert, persymmetric, skew-symmetric, and toeplitz matrices. A
negative matrix is a matrix in which each element is a negative number.
The add, subtract, multiply, transpose, and inversion operations are presented in
the next few sections. For this section, please refer to the binary_search_tree.go file.
[ 148 ]
Homogeneous Data Structures Chapter 5
The sum between the two matrices is the result of calling the add method. The parameters
that are passed are the matrices to be added, as shown here:
var sum [2][2]int
sum = add(matrix1, matrix2)
The example output of the add method is as follows. Adding matrix1 and matrix2 gives
a sum matrix:
The difference between two matrices is the result of calling the subtract method. The
parameters that are passed are the matrices to be subtracted, as shown here:
var difference [2][2]int
difference = subtract(matrix1, matrix2)
[ 149 ]
Homogeneous Data Structures Chapter 5
The product of two matrices is calculated using the multiply method in the following
code snippet, which takes the two matrices as parameters:
var product [2][2]int
product = multiply(matrix1, matrix2)
The example output of the multiply method is as follows. The product of matrix1 and
matrix2 is the product matrix:
[ 150 ]
Homogeneous Data Structures Chapter 5
[ 151 ]
Homogeneous Data Structures Chapter 5
invmatrix[0][0] = matrix[1][1]/det
invmatrix[0][1] = -1*matrix[0][1]/det
invmatrix[1][0] = -1*matrix[1][0]/det
invmatrix[1][1] = matrix[0][0]/det
return invmatrix
}
The next section talks about the zig-zag matrix data structure.
Zig-zag matrix
A zig-zag matrix is a square arrangement of n x n integers. The integers are arranged on
anti-diagonals in sequentially increasing order. The following code explains how to create a
zig-zag matrix and also how to traverse it. The PrintZigZag method creates the matrix in
a zig-zag fashion with the elements in a sequentially increasing order. The method takes the
integer n as a parameter and returns the integer array, which is the zig-zag matrix:
///main package has examples shown
// in Go Data Structures and algorithms book
package main
// importing fmt package
import (
"fmt"
)
//prints the matrix in zig-zag fashion
func PrintZigZag(n int) []int {
var zigzag []int
zigzag = make([]int, n*n)
var i int
i = 0
var m int
m = n * 2
var p int
[ 152 ]
Homogeneous Data Structures Chapter 5
The main method invokes the PrintZigZag method, which takes the parameter n and
prints the matrix first from left to right, then from right to left for the second level, and so
on. The number of integers is 5 and the field width is 2:
// main method
func main() {
var n int
n = 5
var length int
length = 2
var i int
var sketch int
for i, sketch = range PrintZigZag(n) {
fmt.Printf("%*d ", length, sketch)
if i%n == n-1 {
fmt.Println("")
}
}
}
[ 153 ]
Homogeneous Data Structures Chapter 5
The next section talks about the spiral matrix data structure.
Spiral matrix
A spiral matrix is an arrangement of n x n integers in which integers are arranged spirally
in sequentially increasing order. A spiral matrix is an old toy algorithm. The spiral order is
maintained using four loops, one for each corner of the matrix. The PrintSpiral method
in the following code snippet creates a matrix with elements arranged spirally in increasing
order. The method takes a parameter, n, and returns an integer array:
///main package has examples shown
// in Go Data Structures and algorithms book
package main
// importing fmt package
import (
"fmt"
)
//PrintSpiral method
func PrintSpiral(n int) []int {
left =0
top =0
right = n-1
bottom = n-1
var size int
[ 154 ]
Homogeneous Data Structures Chapter 5
size = n * n
var s []int
s = make([]int, size)
var i int
i = 0
for left < right {
var c int
for c = left; c <= right; c++ {
s[top*n+c] = i
i++
}
top++
var r int
for r = top; r <= bottom; r++ {
s[r*n+right] = i
i++
}
right--
if top == bottom {
break
}
for c = right; c >= left; c-- {
s[bottom*n+c] = i
i++
}
bottom--
for r = bottom; r >= top; r-- {
s[r*n+left] = i
i++
}
left++
}
s[top*n+left] = i
return s
}
In the following code snippet, the main method invokes the PrintSpiral method, which
takes the integer n and prints the integer values of the matrix spirally. The values returned
from the PrintSpiral method are printed as fields with a width of 2:
func main() {
var n int
n = 5
var length int
length = 2
var i int
[ 155 ]
Homogeneous Data Structures Chapter 5
The next section talks about the Boolean matrix data structure.
Boolean matrix
A Boolean matrix is a matrix that consists of elements in the mth row and the nth column with
a value of 1. A matrix can be modified to be a Boolean matrix by making the values in
the mth row and the nth column equal to 1. In the following code, the Boolean matrix
transformation and print methods are shown in detail. The changeMatrix method
transforms the input matrix in to a Boolean matrix by changing the row and column values
from 0 to 1 if the cell value is 1. The method takes the input matrix as the parameter and
returns the changed matrix, as shown in the following code:
///main package has examples shown
// in Go Data Structures and algorithms book
package main
[ 156 ]
Homogeneous Data Structures Chapter 5
}
}
}
}
return matrixChanged
The example output of the change matrix method is shown the following screenshot. The
elements with 1 in the row or column are checked and the row elements are updated to 1:
Let's take a look at the printMatrix method and the main method.
[ 157 ]
Homogeneous Data Structures Chapter 5
fmt.Printf("%d",matrix[i][j])
}
fmt.Printf("\n")
}
//main method
func main() {
printMatrix(matrix)
matrix = changeMatrix(matrix)
printMatrix(matrix)
[ 158 ]
Homogeneous Data Structures Chapter 5
Multi-dimensional arrays
An array is a homogeneous collection of data elements. An array's indexes range from
index 0 to index m-1, where m is the fixed length of the array. An array with multiple
dimensions is an array of an array. The following code initializes a multi-dimensional
array. A three-dimensional array is printed:
///main package has examples shown
// in Go Data Structures and algorithms book
package main
var i int
var j int
var k int
[ 159 ]
Homogeneous Data Structures Chapter 5
threedarray[i][j][k] = rand.Intn(3)
}
}
}
fmt.Println(threedarray)
}
Tensors
A tensor is a multi-dimensional array of components that are spatial coordinates. Tensors
are used extensively in physics and biological studies in topics such as electromagnetism
and diffusion tensor imaging. William Rowan Hamilton was the first to come up with the
term tensor. Tensors play a basic role in abstract algebra and algebraic topology.
The tensor order is the sum of the order of its arguments, plus the order of the result tensor.
For example, an inertia matrix is a second-order tensor. Spinors are also multi-dimensional
arrays, but the values of their elements change via coordinate transformations.
The initialization of a tensor is shown here. The array is initialized with integer values
ranging from 0 to 3:
var array [3][3][3]int
var i int
var j int
[ 160 ]
Homogeneous Data Structures Chapter 5
var k int
for i=0; i < 3; i++ {
for j=0; j < 3; j++ {
for k=0; k < 3; k++ {
array[i][j][k] = rand.Intn(3)
}
}
}
Unfolding a tensor is done along the first dimension. Rearranging the tensor mode's n
vectors is referred to as mode n-unfolding of a tensor. 0-mode unfolding of a tensor array is
shown here:
for j=0; j < 3; j++ {
for k=0; k < 3; k++ {
fmt.Printf("%d ",array[0][j][k])
}
fmt.Printf("\n")
}
1-mode unfolding of a tensor array is shown here. The array's first dimension index is set to
1:
for j=0; j < 3; j++ {
for k=0; k < 3; k++ {
fmt.Printf("%d ",array[1][j][k])
}
fmt.Printf("\n")
}
The 2-mode unfolding of a tensor array is shown here. The array's first dimension row
index is set to 2:
for j=0; j < 3; j++ {
for k=0; k < 3; k++ {
fmt.Printf("%d ",array[2][j][k])
}
fmt.Printf("\n")
}
[ 161 ]
Homogeneous Data Structures Chapter 5
Summary
This chapter covered homogeneous data structures such as two-dimensional arrays and
multi-dimensional arrays. Matrix operations such as sum, subtraction, multiplication,
inverse, and determinant have been explained with code examples. Spiral matrices, zig-zag
matrices, and Boolean matrices have been explained using two-dimensional arrays. Tensors
and operations such as folding were also covered.
In the next chapter, heterogeneous data structures such as linked lists, ordered lists, and
unordered lists will be covered.
Questions
1. What is 2-mode unfolding of a tensor array?
2. Write a two-dimensional array of strings and initialize it. Print the strings.
3. Give an example of a multi-dimensional array and traverse through it.
4. For a 3 x 3 matrix, write code that calculates the determinant of the matrix.
5. What is a transpose of a 3 x 3 matrix?
6. What is a zig-zag matrix?
7. Write code with an example of a spiral matrix.
8. Which dimension is typically unfolded for tensor arrays?
9. How do you define a Boolean matrix?
10. Choose two 3 x 3 matrices and find the product of the matrices.
[ 162 ]
Homogeneous Data Structures Chapter 5
Further reading
The following books are recommended if you want to learn more about arrays, matrices,
and tensors:
[ 163 ]
6
Heterogeneous Data Structures
Heterogeneous data structures are data structures that contain diverse types of data, such
as integers, doubles, and floats. Linked lists and ordered lists are good examples of these
data structures. They are used for memory management. A linked list is a chain of elements
that are associated together by means of pointers. Each element's pointer links to the
following item, which connects the chain together. Linked lists don't have to take up a
block of memory. The memory that they utilize can be allocated dynamically. It comprises a
progression of nodes, which are the components of the list. Ordered lists and unordered
lists from HTML are shown to demonstrate the usage of lists and storage management. We
will cover linked lists, ordered lists, and unordered lists in this chapter and show the
implementation of their methods with appropriate examples. This chapter covers the
following heterogeneous data structures:
Linked lists
Ordered lists
Unordered lists
We covered singly linked lists and doubly linked lists with code examples in Chapter 3,
Linear Data Structures. Circular-linked lists were covered in Chapter 4, Non-Linear Data
Structures.
Technical requirements
Install Go Version 1.10 for your OS from the following link: https://golang.org/doc/
install.
The GitHub URL for the code in this chapter is as follows: https://github.com/
PacktPublishing/Learn-Data-Structures-and-Algorithms-with-Golang/tree/master/
Chapter06.
Heterogeneous Data Structures Chapter 6
Linked lists
A linked list is a linear collection of elements with information. The linked list shrinks or
expands based on whether the components are to be included or removed. This list can be
small or enormous, yet, regardless of the size, the elements that make it up are
straightforward. Linked lists were covered in Chapter 3, Linear Data Structures. They
consume more memory than arrays. Reverse traversing is a problem for singly linked lists
because a singly linked list points to the next node forward. The next section explains how
to reverse a singly linked list with a code example.
Reversing a singly linked list is shown in this section. The methods that are explained in
this section are a part of the linked_list.go file that's provided in the code bundle.
The Node class is defined in this snippet with a node pointer, nextNode, and a rune
property:
//main package has examples shown
// in Go Data Structures and algorithms book
package main
// Node struct
type Node struct {
nextNode *Node
property rune
}
[ 165 ]
Heterogeneous Data Structures Chapter 6
The methods of singly linked lists are discussed in the following sections.
The example output for the CreateLinkedList method is shown as follows. The
headNode is created with a value of 97. The linked list is created with nodes starting from a
to z:
The following code snippet shows how the linked list is reversed:
// Reverse List method
func ReverseLinkedList(nodeList *Node) *Node {
var currNode *Node
currNode = nodeList
var topNode *Node = nil
for {
if currNode == nil {
[ 166 ]
Heterogeneous Data Structures Chapter 6
break
}
var tempNode *Node
tempNode = currNode.nextNode
currNode.nextNode = topNode
topNode = currNode
currNode = tempNode
}
return topNode
}
The example output for the reverse linked list method is as follows. The method takes the
parameter of a linked string starting from a to z. The reversed list is from z to a nodes:
The next section talks about the doubly linked list data structure.
[ 167 ]
Heterogeneous Data Structures Chapter 6
// main method
func main() {
var linkedList *list.List
linkedList = list.New()
var element *list.Element
element = linkedList.PushBack(14)
var frontElement *list.Element
frontElement = linkedList.PushFront(1)
linkedList.InsertBefore(6, element)
linkedList.InsertAfter(5, frontElement)
[ 168 ]
Heterogeneous Data Structures Chapter 6
The next section talks about the circular-linked list data structure.
Circular-linked lists
A circular-linked list is a collection of nodes in which the last node is connected to the first
node. Circular-linked lists were briefly covered in Chapter 4, Non-Linear Data Structures.
Circular-linked lists are used to create a circular queue.
In the following section, a circular queue struct is defined and implemented. The methods
that are explained in this section are part of the circular_queue.go file given in the code
bundle.
//Circular Queue
type CircularQueue struct {
size int
nodes []interface{}
head int
last int
}
[ 169 ]
Heterogeneous Data Structures Chapter 6
Let's discuss the different methods of the CircularQueue class in the following sections.
[ 170 ]
Heterogeneous Data Structures Chapter 6
The example output for the Add method is as follows. The Add method takes the element
with value 1 and updates the queue:
//MoveOneStep method
func (circularQueue *CircularQueue) MoveOneStep() (element interface{}) {
if circularQueue.IsUnUsed() {
return nil
}
element = circularQueue.nodes[circularQueue.head]
circularQueue.head = (circularQueue.head + 1) % circularQueue.size
return
}
[ 171 ]
Heterogeneous Data Structures Chapter 6
circularQueue = NewQueue(5)
circularQueue.Add(1)
circularQueue.Add(2)
circularQueue.Add(3)
circularQueue.Add(4)
circularQueue.Add(5)
fmt.Println(circularQueue.nodes)
In the following sections, ordered lists and unordered lists are explained with code
examples.
Ordered lists
Lists in Go can be sorted in two ways:
Ordered list: By creating a group of methods for the slice data type and calling
sort
Unordered list: The other way is to invoke sort.Slice with a custom less
function
The only difference between an ordered list and an unordered list is that, in an ordered list,
the order in which the items are displayed is mandatory.
An ordered list in HTML starts with an <ol> tag. Each item in the list is written in <li>
tags. Here's an example:
<ol>
<li>Stones</li>
<li>Branches</li>
<li>Smoke</li>
</ol>
[ 172 ]
Heterogeneous Data Structures Chapter 6
An example of an ordered list using Golang is shown in the following code snippet. The
Employee class has Name, ID, SSN, and Age properties:
// class Employee
type Employee struct {
Name string
ID string
SSN int
Age int
}
The methods that are explained in the following sections are a part of the linked_list.go
file that's provided in the code bundle.
[ 173 ]
Heterogeneous Data Structures Chapter 6
The main method initializes the employees array and sorts the array by age:
func main() {
var employees = []Employee{
{"Graham","231",235643,31},
{"John", "3434",245643,42},
{"Michael","8934",32432, 17},
{"Jenny", "24334",32444,26},
}
fmt.Println(employees)
sort.Sort(SortByAge(employees))
fmt.Println(employees)
sort.Slice(employees, func(i int, j int) bool {
return employees[i].Age > employees[j].Age
})
fmt.Println(employees)
}
[ 174 ]
Heterogeneous Data Structures Chapter 6
An ordered list is sorted using the sort criteria as follows. The sort_keys.go code snippet
shows how things are sorted by various criteria, such as name, mass, and distance. The
Mass and Miles units are defined as float64:
// Thing class
type Thing struct {
name string
mass Mass
distance Miles
meltingpoint int
freezingpoint int
}
[ 175 ]
Heterogeneous Data Structures Chapter 6
The next section talks about the implementation of the len, swap, and less methods.
[ 176 ]
Heterogeneous Data Structures Chapter 6
// Swap method
func (ThingSorter *ThingSorter) Swap(i int, j int) {
ThingSorter.Things[i], ThingSorter.Things[j] = ThingSorter.Things[j],
ThingSorter.Things[i]
}
// Less method
func (ThingSorter *ThingSorter) Less(i int, j int) bool {
return ThingSorter.byFactor(&ThingSorter.Things[i],
&ThingSorter.Things[j])
}
[ 177 ]
Heterogeneous Data Structures Chapter 6
ByFactor(name).Sort(Things)
fmt.Println("By name:", Things)
ByFactor(mass).Sort(Things)
fmt.Println("By mass:", Things)
ByFactor(distance).Sort(Things)
fmt.Println("By distance:", Things)
ByFactor(decreasingDistance).Sort(Things)
fmt.Println("By decreasing distance:", Things)
}
[ 178 ]
Heterogeneous Data Structures Chapter 6
The different methods of the multiSorter class are discussed in the following sections.
[ 179 ]
Heterogeneous Data Structures Chapter 6
// Len method
func (multiSorter *multiSorter) Len() int {
return len(multiSorter.Commits)
}
[ 180 ]
Heterogeneous Data Structures Chapter 6
var p *Commit
var q *Commit
p = &multiSorter.Commits[i]
q = &multiSorter.Commits[j]
var k int
for k = 0; k < len(multiSorter.lessFunction)-1; k++ {
less := multiSorter.lessFunction[k]
switch {
case less(p, q):
return true
case less(q, p):
return false
}
}
return multiSorter.lessFunction[k](p, q)
}
//main method
func main() {
var Commits = []Commit{
{"james", "Javascript", 110},
{"ritchie", "python", 250},
{"fletcher", "Go", 300},
{"ray", "Go", 400},
{"john", "Go", 500},
{"will", "Go", 600},
{"dan", "C++", 500},
{"sam", "Java", 650},
{"hayvard", "Smalltalk", 180},
}
var user func(*Commit, *Commit) bool
user = func(c1 *Commit, c2 *Commit) bool {
[ 181 ]
Heterogeneous Data Structures Chapter 6
[ 182 ]
Heterogeneous Data Structures Chapter 6
The next section talks about the HTML unordered list data structure.
Unordered lists
An unordered list is implemented as a linked list. In an unordered list, the relative
positions of items in contiguous memory don't need to be maintained. The values will be
placed in a random fashion.
An unordered list starts with a <ul> tag in HTML 5.0. Each list item is coded with <li>
tags. Here's an example:
<ul>
<li> First book </li>
<li> Second book </li>
<li> Third book </li>
</ul>
The following is an example of an unordered list in Golang. The Node class has a property
and a nextNode pointer, as shown in the following code. The linked list will have a set of
nodes with a property attribute. The unordered list is presented in the script called
unordered_list.go:
//Node class
type Node struct {
property int
nextNode *Node
}
[ 183 ]
Heterogeneous Data Structures Chapter 6
The next section discusses the AddtoHead method and the IterateList method of the
UnOrderedList struct.
[ 184 ]
Heterogeneous Data Structures Chapter 6
Run the following command to execute the unordered_list.go file from the code
bundle:
go run unordered_list.go
Summary
This chapter covered heterogeneous data structures such as ordered lists and unordered
lists with code examples. The Ordered lists section covered sorting slices by single key,
multiple keys, and sort.Slice. Slices are sorted by making the array of struct elements
implement the sort.Sort interface. Unordered lists were described as linked lists with
values that are not ordered.
The next chapter will cover dynamic data structures such as dictionaries, TreeSets,
sequences, synchronized TreeSets, and mutable TreeSets.
[ 185 ]
Heterogeneous Data Structures Chapter 6
Questions
1. Which method of the sort.Sort interface returns the size of the elements to be
sorted?
2. Which function needs to be passed to the sort.Slice method to sort a slice?
3. What does the swap method do to the elements at the i and j indices?
4. What is the default order for sorting elements using sort.Sort?
5. How do you implement ascending and descending sorting with sort.Slice?
6. How do you sort an array and keep the original order of the elements?
7. Which interface is used to reverse the order of the data?
8. Show an example of sorting a slice.
9. Which method is called to add elements to an unordered list?
10. Write a code example of an unordered list of floats.
Further reading
The following books are recommended if you want to know more about heterogeneous
data structures:
Design Patterns, by Erich Gamma, Richard Helm, Ralph Johnson, and John
Vlissides
Introduction to Algorithms – Third Edition, by Thomas H. Cormen, Charles E.
Leiserson, Ronald L. Rivest, and Clifford Stein
Data structures and Algorithms: An Easy Introduction, by Rudolph Russell
[ 186 ]
7
Dynamic Data Structures
A dynamic data structure is a set of elements in memory that has the adaptability to
expand or shrink. This ability empowers a software engineer to control precisely how much
memory is used. Dynamic data structures are used for handling generic data in a key-value
store. They can be used in distributed caching and storage management. Dynamic data
structures are valuable in many circumstances in which dynamic addition or deletion of
elements occur. They are comparable in capacity to a smaller relational database or an in-
memory database. These data structures are used in marketing and customer relationship
management applications. Dictionaries, TreeSets, and sequences are examples of dynamic
data structures.
In this chapter, we will explain what dictionaries, TreeSets, and sequences are and show
you how they are implemented with the help of code examples.
Dictionaries
TreeSets:
Synchronized TreeSets
Mutable TreeSets
Sequences:
Farey
Fibonacci
Look-and-say
Thue–Morse
Dynamic Data Structures Chapter 7
Technical requirements
Install Go Version 1.10 from https://golang.org/doc/install for your OS.
The GitHub URL for the code in this chapter is as follows: https://github.com/
PacktPublishing/Learn-Data-Structures-and-Algorithms-with-Golang/tree/master/
Chapter07.
Dictionaries
A dictionary is a collection of unique key and value pairs. A dictionary is a broadly useful
data structure for storing a set of data items. It has a key, and each key has a solitary item
associated with it. When given a key, the dictionary will restore the item associated with
that key. These keys can be of any type: strings, integers, or objects. Where we need to sort
a list, an element value can be retrieved utilizing its key. Add, remove, modify, and lookup
operations are allowed in this collection. A dictionary is similar to other data structures,
such as hash, map, and HashMap. The key/value store is used in distributed caching and in
memory databases. Arrays differ from dictionaries in how the data is accessed. A set has
unique items, whereas a dictionary can have duplicate values.
Phone directories
Router tables in networking
Page tables in operating systems
Symbol tables in compilers
Genome maps in biology
The following code shows how to initialize and modify a dictionary. In this snippet, the
dictionary has the key DictKey and is a string:
//main package has examples shown
// in Go Data Structures and algorithms book
package main
// DictKey type
[ 188 ]
Dynamic Data Structures Chapter 7
The following sections talk about the type and methods in dictionaries.
DictVal type
The dictionary has the value DictVal of type string mapped to DictKey:
// DictVal type
type DictVal string
Dictionary class
The dictionary in the following code is a class with dictionary elements, with DictKey
as the key and DictVal as the value. It has a sync.RWMutex property, lock:
// Dictionary class
type Dictionary struct {
elements map[DictKey]DictVal
lock sync.RWMutex
}
The Put, Remove, Contain, Find, Rest, NumberofElements, GetKeys, GetValues, and
Main methods are discussed in the following sections.
Put method
A has a Put method, as shown in the following example, that takes the key and value
parameters of the DictKey and DictVal types respectively. The Lock method of the
dictionary's lock instance is invoked, and the Unlock method is deferred. If there are
empty map elements in the dictionary, elements are initialized using make.
The map elements are set with a key and a value if they are not empty:
// Put method
func (dict *Dictionary) Put(key DictKey, value DictVal) {
dict.lock.Lock()
defer dict.lock.Unlock()
if dict.elements == nil {
dict.elements = make(map[DictKey]DictVal)
}
dict.elements[key] = value
}
[ 189 ]
Dynamic Data Structures Chapter 7
The example output of the put method is as follows. The put method takes the key 1 and
value 1. The map is updated with key and value:
Remove method
A dictionary has a remove method, as shown in the following code, which has a key
parameter of the DictKey type. This method returns a bool value if the value associated
with Dictkey is removed from the map:
// Remove method
func (dict *Dictionary) Remove(key DictKey) bool {
dict.lock.Lock()
defer dict.lock.Unlock()
var exists bool
_, exists = dict.elements[key]
if exists {
delete(dict.elements, key)
}
return exists
}
Contains method
In the following code, the Contains method has an input parameter, key, of the
DictKey type, and returns bool if key exists in the dictionary:
// Contains method
func (dict *Dictionary) Contains(key DictKey) bool {
dict.lock.RLock()
defer dict.lock.RUnlock()
var exists bool
_, exists = dict.elements[key]
return exists
}
[ 190 ]
Dynamic Data Structures Chapter 7
Find method
The Find method takes the key parameter of the DictKey type and returns the
DictVal type associated with the key. The following code snippet explains the Find
method:
// Find method
func (dict *Dictionary) Find(key DictKey) DictVal {
dict.lock.RLock()
defer dict.lock.RUnlock()
return dict.elements[key]
}
Reset method
The Reset method of the Dictionary class is presented in the following snippet. The
Lock method of the dictionary's lock instance is invoked and Unlock is deferred. The
elements map is initialized with a map of the DictKey key and the DictVal value:
// Reset method
func (dict *Dictionary) Reset() {
dict.lock.Lock()
defer dict.lock.Unlock()
dict.elements = make(map[DictKey]DictVal)
}
NumberOfElements method
The NumberOfElements method of the Dictionary class returns the length of the
elements map. The RLock method of the lock instance is invoked. The RUnlock method
of the lock instance is deferred before returning the length; this is shown in the following
code snippet:
// NumberOfElements method
func (dict *Dictionary) NumberOfElements() int {
dict.lock.RLock()
defer dict.lock.RUnlock()
return len(dict.elements)
}
[ 191 ]
Dynamic Data Structures Chapter 7
GetKeys method
The GetKeys method of the Dictionary class is shown in the following code snippet. The
method returns the array of the DictKey elements. The RLock method of the lock instance
is invoked, and the RUnlock method is deferred. The dictionary keys are returned by
traversing the element's map:
// GetKeys method
func (dict *Dictionary) GetKeys() []DictKey {
dict.lock.RLock()
defer dict.lock.RUnlock()
var dictKeys []DictKey
dictKeys = []DictKey{}
var key DictKey
for key = range dict.elements {
dictKeys = append(dictKeys, key)
}
return dictKeys
}
GetValues method
The GetValues method of the Dictionary class returns the array of the DictVal
elements. In the following code snippet, the RLock method of the lock instance is invoked
and the RUnlock method is deferred. The array of dictionary values is returned after
traversing the element's map:
// GetValues method
func (dict *Dictionary) GetValues() []DictVal {
dict.lock.RLock()
defer dict.lock.RUnlock()
var dictValues []DictVal
dictValues = []DictVal{}
var key DictKey
for key = range dict.elements {
dictValues = append(dictValues, dict.elements[key])
}
return dictValues
}
[ 192 ]
Dynamic Data Structures Chapter 7
Let's take a look at the TreeSet data structure in the following section.
TreeSets
TreeSets are used in marketing and customer relationship management applications.
TreeSet is a set that has a binary tree with unique elements. The elements are sorted in a
natural order. In the following code snippet, TreeSet creation, insertion, search, and
stringify operations are presented. TreeSet allows only one null value if the set is
empty. The elements are sorted and stored as elements. The add, remove, and contains
functions cost log(n) on TreeSets:
///main package has examples shown
// in Go Data Structures and algorithms book
package main
// TreeSet class
type TreeSet struct {
bst *BinarySearchTree
[ 193 ]
Dynamic Data Structures Chapter 7
InsertTreeNode method
The InsertTreeNode method of the TreeSet class takes treeNodes variable arguments
of the TreeNode type. In the following code, the elements with the key and value are
inserted in the binary search tree of TreeSet:
// InsertTreeNode method
func (treeset *TreeSet) InsertTreeNode(treeNodes ...TreeNode) {
var treeNode TreeNode
for _, treeNode = range treeNodes {
treeset.bst.InsertElement(treeNode.key, treeNode.value)
}
}
[ 194 ]
Dynamic Data Structures Chapter 7
Delete method
The Delete method of the TreeSet class is shown in the following code snippet. In this
method, treeNodes with the provided key are removed:
// Delete method
func (treeset *TreeSet) Delete(treeNodes ...TreeNode) {
var treeNode TreeNode
for _, treeNode = range treeNodes {
treeset.bst.RemoveNode(treeNode.key)
}
}
InOrderTraverseTree method
The InOrderTraverseTree method of the BinarySearchTree class takes function as a
parameter. The RLock method of the lock instance is invoked. The RUnlock method of the
tree's lock instance is deferred. InOrderTraverseTree is invoked with the rootNode of
the tree and function as parameters:
//InOrderTraverseTree method
func (tree *BinarySearchTree) InOrderTraverseTree(function func(int)) {
tree.lock.RLock()
defer tree.lock.RUnlock()
inOrderTraverseTree(tree.rootNode, function)
}
[ 195 ]
Dynamic Data Structures Chapter 7
PreOrderTraverseTree method
The PreOrderTraverseTree method of the BinarySearchTree class takes the function
as its parameter. The Lock method on the tree's lock instance is invoked first, and the
Unlock method is deferred. The PreOrderTraverseTree method is called with the
rootNode of the tree and function as parameters:
// PreOrderTraverse method
func (tree *BinarySearchTree) PreOrderTraverseTree(function func(int)) {
tree.lock.Lock()
defer tree.lock.Unlock()
preOrderTraverseTree(tree.rootNode, function)
}
// preOrderTraverseTree method
func preOrderTraverseTree(treeNode *TreeNode, function func(int)) {
if treeNode != nil {
function(treeNode.value)
preOrderTraverseTree(treeNode.leftNode, function)
preOrderTraverseTree(treeNode.rightNode, function)
}
}
Search method
The Search method of the TreeSet class takes a variable argument named treeNodes of
the TreeNode type and returns true if one of those treeNodes exists; otherwise, it returns
false. The code following snippet outlines the Search method:
// Search method
func (treeset *TreeSet) Search(treeNodes ...TreeNode) bool {
var treeNode TreeNode
var exists bool
for _, treeNode = range treeNodes {
[ 196 ]
Dynamic Data Structures Chapter 7
[ 197 ]
Dynamic Data Structures Chapter 7
The next section talks about the synchronized TreeSet data structure.
Synchronized TreeSets
Operations that are performed on synchronized TreeSets are synchronized across multiple
calls that access the elements of TreeSets. Synchronization in TreeSets is achieved using a
sync.RWMutex lock. The lock method on the tree's lock instance is invoked, and the
unlock method is deferred before the tree nodes are inserted, deleted, or updated:
// InsertElement method
func (tree *BinarySearchTree) InsertElement(key int, value int) {
tree.lock.Lock()
defer tree.lock.Unlock()
var treeNode *TreeNode
treeNode = &TreeNode{key, value, nil, nil}
if tree.rootNode == nil {
tree.rootNode = treeNode
} else {
insertTreeNode(tree.rootNode, treeNode)
}
}
[ 198 ]
Dynamic Data Structures Chapter 7
Mutable TreeSets
Mutable TreeSets can use add, update, and delete operations on the tree and its nodes.
insertTreeNode updates the tree by taking the rootNode and treeNode parameters to
be updated. The following code snippet shows how to insert a TreeNode with a given
rootNode and TreeNode:
// insertTreeNode method
func insertTreeNode(rootNode *TreeNode, newTreeNode *TreeNode) {
if newTreeNode.key < rootNode.key {
if rootNode.leftNode == nil {
rootNode.leftNode = newTreeNode
} else {
insertTreeNode(rootNode.leftNode, newTreeNode)
}
} else {
if rootNode.rightNode == nil {
rootNode.rightNode = newTreeNode
} else {
insertTreeNode(rootNode.rightNode, newTreeNode)
}
}
}
RemoveNode method
The RemoveNode method of a BinarySearchTree is as follows:
// RemoveNode method
func (tree *BinarySearchTree) RemoveNode(key int) {
tree.lock.Lock()
defer tree.lock.Unlock()
removeNode(tree.rootNode, key)
}
[ 199 ]
Dynamic Data Structures Chapter 7
Treeset.bst
The TreeNode's can be updated by accessing treeset.bst and traversing the binary
search tree from the rootNode and the left and right nodes of rootNode, as shown here:
var treeset *TreeSet = &TreeSet{}
treeset.bst = &BinarySearchTree{}
var node1 TreeNode = TreeNode{8, 8, nil, nil}
var node2 TreeNode = TreeNode{3, 3, nil, nil}
var node3 TreeNode = TreeNode{10, 10, nil, nil}
var node4 TreeNode = TreeNode{1, 1, nil, nil}
var node5 TreeNode = TreeNode{6, 6, nil, nil}
treeset.InsertTreeNode(node1, node2, node3, node4, node5)
treeset.String()
Sequences
A sequence is a set of numbers that are grouped in a particular order. The number of
elements in the stream can be infinite, and these sequences are called streams. A
subsequence is a sequence that's created from another sequence. The relative positions of
the elements in a subsequence will remain the same after deleting some of the elements in a
sequence.
In the following sections, we will take a look at different sequences such as the Farey
sequence, Fibonacci sequence, look-and-say, and Thue–Morse.
Farey sequence
A Farey sequence consists of reduced fractions with values between zero and one. The
denominators of the fractions are less than or equal to m, and organized in ascending order.
This sequence is called a Farey series. In the following code, reduced fractions are
displayed:
///main package has examples shown
// in Go Data Structures and algorithms book
package main
// importing fmt package
import (
"fmt"
)
[ 200 ]
Dynamic Data Structures Chapter 7
// fraction class
type fraction struct {
numerator int
denominator int
}
String method
The fraction class has the numerator and denominator integer properties. The String
method of the fraction class, as shown in the following snippet, returns a string version
of fraction:
// string method of fraction class
func (frac fraction) String() string {
return fmt.Sprintf("%d/%d", frac.numerator, frac.denominator)
}
The g method
The g method takes two fractions and prints the series of reduced fractions. The g
function takes an l or an r fraction, and num int as arguments to print the reduced fraction
as a series. The following code snippet shows the g method:
// g method
func g(l fraction, r fraction, num int) {
var frac fraction
frac = fraction{l.numerator + r.numerator, l.denominator + r.denominator}
if frac.denominator <= num {
g(l, frac, num)
fmt.Print(frac, " ")
g(frac, r, num)
}
}
[ 201 ]
Dynamic Data Structures Chapter 7
The next section talks about the Fibonacci sequence data structure.
[ 202 ]
Dynamic Data Structures Chapter 7
Fibonacci sequence
The Fibonacci sequence consists of a list of numbers in which every number is the sum of
the two preceding numbers. Pingala, in 200 BC, was the first to come up with Fibonacci
numbers. The Fibonacci sequence is as follows:
A Fibonacci prime is a Fibonacci number that is a prime number. The Fibonacci prime
series is as follows:
Computer algorithms such as the Fibonacci search technique, heap, and cubes are popular
applications of Fibonacci numbers. Pseudorandom number generators use Fibonacci
numbers.
The following code snippet shows the Fibonacci sequence and recursive Fibonacci number
calculation. The Series function is presented as well. The Series function calculates the
Fibonacci numbers in the sequence:
///main package has examples shown
// in Go Data Structures and algorithms book
package main
// Series method
func Series(n int) int {
var f []int
f = make([]int, n+1, n+2)
if n < 2 {
[ 203 ]
Dynamic Data Structures Chapter 7
f = f[0:2]
}
f[0] = 0
f[1] = 1
var i int
for i = 2; i <= n; i++ {
f[i] = f[i-1] + f[i-2]
}
return f[n]
}
The different methods of the Fibonacci sequence are discussed in the following sections.
FibonacciNumber method
The FibonacciNumber method takes the integer n and, by recursion, calculates the
Fibonacci numbers. The following code snippet shows this recursion:
// FibonacciNumber method
func FibonacciNumber(n int) int {
if n <= 1 {
return n
}
return FibonacciNumber(n-1) + FibonacciNumber(n-2)
}
Main method
The main method in the following code snippet shows how the Fibonacci sequence is
calculated:
// main method
func main() {
var i int
for i = 0; i <= 9; i++ {
fmt.Print(strconv.Itoa(Series(i)) + " ")
}
fmt.Println("")
for i = 0; i <= 9; i++ {
fmt.Print(strconv.Itoa(FibonacciNumber(i)) + " ")
}
fmt.Println("")
}
[ 204 ]
Dynamic Data Structures Chapter 7
Look-and-say
The look-and-say sequence is a sequence of integers:
The sequence is generated by counting the digits of the previous number in the group. John
Conway initially coined the term look-and-say sequence.
The look-and-say sequence is shown in the following code. The look_say method takes a
string as a parameter and returns a look-and-say sequence of integers:
//main package has examples shown
// in Go Data Structures and algorithms book
package main
// look_say method
func look_say(str string) (rstr string) {
var cbyte byte
cbyte = str[0]
var inc int
inc = 1
[ 205 ]
Dynamic Data Structures Chapter 7
var i int
for i = 1; i < len(str); i++ {
var dbyte byte
dbyte = str[i]
if dbyte == cbyte {
inc++
continue
}
rstr = rstr + strconv.Itoa(inc) + string(cbyte)
cbyte = dbyte
inc = 1
}
return rstr + strconv.Itoa(inc) + string(cbyte)
}
The main method initializes the string and invokes the look_say method. The look-and-
say sequence that is returned from the method is printed:
// main method
func main() {
var str string
str = "1"
fmt.Println(str)
var i int
for i = 0; i < 8; i++ {
str = look_say(str)
fmt.Println(str)
}
}
[ 206 ]
Dynamic Data Structures Chapter 7
Thue–Morse
The Thue–Morse sequence is a binary sequence starting at zero that appends the Boolean
complement of the current sequence.
The Thue–Morse sequence was applied by Eugene Prophet and used by Axel Thue in the
study of combinatorics on words. The Thue–Morse sequence is used in the area of fractal
curves, such as Koch snowflakes.
The following code snippet creates the Thue–Morse sequence. The ThueMorseSequence
function takes a bytes.Buffer instance buffer and modifies the buffer to the Thue–Morse
sequence by applying the complement operation on the bytes:
//main package has examples shown
// in Go Data Structures and algorithms book
package main
// ThueMorseSequence method
func ThueMorseSequence(buffer *bytes.Buffer) {
var b int
var currLength int
var currBytes []byte
for b, currLength, currBytes = 0, buffer.Len(), buffer.Bytes(); b <
currLength; b++ {
if currBytes[b] == '1' {
buffer.WriteByte('0')
} else {
buffer.WriteByte('1')
}
}
}
[ 207 ]
Dynamic Data Structures Chapter 7
The main method initializes the sequence number as 0. The ThueMorseSequence method
takes the pointer to the bytes.Buffer and modifies it by invoking the
ThueMorseSequence method. The resulting sequence is printed on the Terminal:
// main method
func main() {
var buffer bytes.Buffer
// initial sequence member is "0"
buffer.WriteByte('0')
fmt.Println(buffer.String())
var i int
for i = 2; i <= 7; i++ {
ThueMorseSequence(&buffer)
fmt.Println(buffer.String())
}
}
[ 208 ]
Dynamic Data Structures Chapter 7
Summary
This chapter covered the contains, put, remove, find, reset, NumberOfElements,
getKeys, and getValues methods of the dictionary data structure. The InsertTreeNode,
Delete, Search, and stringify TreeSet operations have been explained in detail, and
code examples were provided. The BinarySearchTree structure has been presented in
code, along with the InsertElement, InOrderTraversal, PreOrderTraverseTree,
SearchNode, and RemoveNode functions.
The next chapter covers algorithms such as sorting, searching, recursion, and hashing.
Questions
1. How do you ensure a BinarySearchTree is synchronized?
2. Which method is called to postpone the invocation of a function?
3. How do you define dictionary keys and values with custom types?
4. How do you find the length of a map?
5. What keyword is used to traverse a list of treeNodes in a tree?
6. In a Farey sequence, what are the real numbers in the series called?
7. What is a Fibonacci number?
8. How do you convert an integer into a string?
9. What method is used to convert a byte into a string?
10. What method is called to add elements to a dictionary?
[ 209 ]
Dynamic Data Structures Chapter 7
Further reading
The following books are recommended if you want to learn more about dynamic data
structures:
Design Patterns, by Erich Gamma, Richard Helm, Ralph Johnson, and John
Vlissides
Introduction to Algorithms – Third Edition, by Thomas H. Cormen, Charles E.
Leiserson, Ronald L. Rivest, and Clifford Stein
Data structures and Algorithms: An Easy Introduction, by Rudolph Russell
[ 210 ]
8
Classic Algorithms
Classic algorithms are used in the areas of data search and cryptography. Sorting,
searching, recursing, and hashing algorithms are good examples of classic
algorithms. Sorting algorithms are used to order elements into either an ascending or
descending key arrangement. These algorithms are frequently used to canonicalize data
and to create readable content. Search algorithms are used to find an element in a set. A
recursive algorithm is one that calls itself with input items. A hashing algorithm is a
cryptographic hash technique. It is a scientific calculation that maps data with a subjective
size to a hash with a settled size. It's intended to be a single direction function, that you
cannot alter.
In this chapter, we will cover the different classic algorithms and explain them with suitable
examples.
Sorting:
Bubble
Selection
Insertion
Shell
Merge
Quick
Searching:
Linear
Sequential
Binary
Interpolation
Recursion
Hashing
Classic Algorithms Chapter 8
Technical requirements
Install Go version 1.10 from https://golang.org/doc/install for your OS.
The GitHub URL for the code in this chapter is as follows: https://github.com/
PacktPublishing/Learn-Data-Structures-and-Algorithms-with-Golang/tree/master/
Chapter08.
Sorting
Sorting algorithms arrange the elements in a collection in ascending or descending order.
Lexicographical order can be applied to a collection of characters and strings. The efficiency
of these algorithms is in the performance of sorting the input data into a sorted collection.
The best sorting algorithm time complexity is O(n log n). Sorting algorithms are classified
by the following criteria:
Computational complexity
Memory usage
Stability
Type of sorting: serial/parallel
Adaptability
Method of sorting
In the following sections, we'll look at the different sorting algorithms, that is, bubble,
selection, insertion, shell, merge, and quick.
Bubble
The bubble sort algorithm is a sorting algorithm that compares a pair of neighboring
elements and swaps them if they are in the wrong order. The algorithm has a complexity of
O(n2), where n is the number of elements to be sorted. The smallest or greatest value
bubbles up to the top of the collection, or the smallest or greatest sinks to the bottom
(depending on whether you're sorting into ascending or descending order).
The following code snippet shows the implementation of the bubble sort algorithm. The
bubbleSorter function takes an integer array and sorts the array's elements in ascending
order.
[ 212 ]
Classic Algorithms Chapter 8
The main method initializes the array's integers and invokes the bubbleSorter function,
as follows:
//main package has examples shown
// in Go Data Structures and algorithms book
package main
// main method
func main() {
var integers [11]int = [11]int{31, 13, 12, 4, 18, 16, 7, 2, 3, 0, 10}
fmt.Println("Bubble Sorter")
bubbleSorter(integers)
[ 213 ]
Classic Algorithms Chapter 8
Let's take a look at the selection sort algorithm in the following section.
Selection
Selection sort is an algorithm that divides the input collection into two fragments. This
sublist of elements is sorted by swapping the smallest or largest element from the left of the
list to the right. The algorithm is of the order O(n2). This algorithm is inefficient for large
collections, and it performs worse than the insertion sort algorithm.
The following code shows the implementation of the SelectionSorter function, which
takes the collection to be sorted:
//main package has examples shown
// in Go Data Structures and algorithms book
package main
var i int
for i = 0; i < len(elements)-1; i++ {
var min int
min = i
var j int
for j = i + 1; j <= len(elements)-1; j++ {
if elements[j] < elements[min] {
[ 214 ]
Classic Algorithms Chapter 8
min = j
}
}
swap(elements, i, min)
}
}
Let's take a look at the different selection methods in the next sections.
[ 215 ]
Classic Algorithms Chapter 8
Let's take a look at the insertion sort algorithm in the following section.
Insertion
Insertion sort is an algorithm that creates a final sorted array one element at a time. The
algorithm's performance is of the order O(n2). This algorithm is less efficient on large
collections than other algorithms, such as quick, heap, and merge sort. In real life, a good
example of insertion sort is the way cards are manually sorted by the players in a game of
bridge.
The implementation of the insertion sort algorithm is shown in the following code snippet.
The RandomSequence function takes the number of elements as a parameter and returns an
array of random integers:
//main package has examples shown
// in Go Data Structures and algorithms book
package main
// randomSequence method
func randomSequence(num int) []int {
[ 216 ]
Classic Algorithms Chapter 8
return sequence
}
Let's take a look at the different insertion methods in the next sections.
InsertionSorter method
The implementation of the InsertionSorter method is shown in the following snippet.
This method takes the array of integers as a parameter and sorts them:
//InsertionSorter method
func InsertionSorter(elements []int) {
var n = len(elements)
var i int
[ 217 ]
Classic Algorithms Chapter 8
Let's take a look at the shell sort algorithm in the next section.
Shell
The shell sort algorithm sorts a pair of elements that are not in sequence in a collection. The
distance between the elements to be compared is decreased sequentially. This algorithm
performs more operations and has a greater cache miss ratio than the quick sort algorithm.
In the following code, we can see the implementation of the shell sort algorithm. The
ShellSorter function takes an integer array as a parameter and sorts it:
[ 218 ]
Classic Algorithms Chapter 8
for {
var interval int
interval = power(2, k) + 1
if interval > n-1 {
break
}
intervals = append([]int{interval}, intervals...)
k++
}
var interval int
for _, interval = range intervals {
var i int
for i = interval; i < n; i += interval {
var j int
j = i
for j > 0 {
if elements[j-interval] > elements[j] {
elements[j-interval], elements[j] = elements[j], elements[j-
interval]
}
j = j - interval
}
}
}
}
Let's take a look at the different shell methods in the following sections.
[ 219 ]
Classic Algorithms Chapter 8
Let's take a look at the merge sort algorithm in the next section.
Merge
The merge sort algorithm is a comparison-based method that was invented by John Von
Neumann. Each element in the adjacent list is compared for sorting. The performance of the
algorithm is in the order of O(n log n). This algorithm is the best algorithm for sorting a
linked list.
[ 220 ]
Classic Algorithms Chapter 8
The following code snippet demonstrates the merge sort algorithm. The createArray
function takes num int as a parameter and returns an integer, array, that consists of
randomized elements:
//main package has examples shown
// in Go Data Structures and algorithms book
package main
// create array
func createArray(num int) []int {
var array []int
array = make([]int, num, num)
rand.Seed(time.Now().UnixNano())
var i int
for i = 0; i < num; i++ {
array[i] = rand.Intn(99999) - rand.Intn(99999)
}
return array
}
Let's take a look at the different merge methods in the following sections.
MergeSorter method
The MergeSorter method takes an array of integer elements as a parameter, and two sub-
arrays of elements are recursively passed to the MergeSorter method. The resultant arrays
are joined and returned as the collection, as follows:
// MergeSorter algorithm
func MergeSorter(array []int) []int {
if len(array) < 2 {
return array
}
var middle int
middle = (len(array)) / 2
return JoinArrays(MergeSorter(array[:middle]),
MergeSorter(array[middle:]))
}
[ 221 ]
Classic Algorithms Chapter 8
JoinArrays method
The JoinArrays function takes the leftArr and rightArr integer arrays as parameters.
The combined array is returned in the following code:
// Join Arrays method
func JoinArrays(leftArr []int, rightArr []int) []int {
var k int
for k = 0; k < num; k++ {
if i > len(leftArr)-1 && j <= len(rightArr)-1 {
array[k] = rightArr[j]
j++
} else if j > len(rightArr)-1 && i <= len(leftArr)-1 {
array[k] = leftArr[i]
i++
} else if leftArr[i] < rightArr[j] {
array[k] = leftArr[i]
i++
} else {
array[k] = rightArr[j]
j++
}
}
return array
}
[ 222 ]
Classic Algorithms Chapter 8
Let's take a look at the quick sort algorithm in the following section.
Quick
Quick sort is an algorithm for sorting the elements of a collection in an organized
way. Parallelized quick sort is two to three times faster than merge sort and heap sort. The
algorithm's performance is of the order O(n log n). This algorithm is a space-optimized
version of the binary tree sort algorithm.
In the following code snippet, the quick sort algorithm is implemented. The QuickSorter
function takes an array of integer elements, upper int, and below int as parameters.
The function divides the array into parts, which are recursively divided and sorted:
//main package has examples shown
// in Go Data Structures and algorithms book
package main
[ 223 ]
Classic Algorithms Chapter 8
Let's take a look at the different quick methods in the following sections.
[ 224 ]
Classic Algorithms Chapter 8
// main method
func main() {
var num int
var i int
for i = 0; i < num; i++ {
fmt.Print("array[", i, "]: ")
fmt.Scan(&array[i])
}
[ 225 ]
Classic Algorithms Chapter 8
Now that we are done with sort algorithms, let's take a look at the search algorithms in the
next section.
Searching
Search algorithms are used to retrieve information that's stored in a data source or a
collection. The algorithm is given the key of the element in question, and the associated
value will be found. Search algorithms return a true or a false Boolean value based on the
availability of the information. They can be enhanced to display multiple values related to
the search criteria. Different types of search algorithms include linear, binary, and
interpolation. These algorithms are categorized by the type of search. Search algorithms
include brute force and heuristic methods. The algorithms are chosen for their efficiency.
Different factors for choosing these algorithms are as follows:
Input type
Output type
Definiteness
Correctness
Finiteness
Effectiveness
Generality
[ 226 ]
Classic Algorithms Chapter 8
Linear
The linear search method finds a given value within a collection by sequentially checking
every element in the collection. The time complexity of the linear search algorithm is O(n).
The binary search algorithm and hash tables perform better than this search algorithm.
The implementation of the linear search method is shown in the following code snippet.
The LinearSearch function takes an array of integer elements and findElement int as
parameters. The function returns a Boolean true if the findElement is found; otherwise, it
returns false:
//main package has examples shown
// in Go Data Structures and algorithms book
package main
The main method initializes the array of integer elements and invokes the LinearSearch
method by passing an integer that needs to be found, as follows:
// main method
func main() {
var elements []int
elements = []int{15, 48, 26, 18, 41, 86, 29, 51, 20}
fmt.Println(LinearSearch(elements, 48))
}
[ 227 ]
Classic Algorithms Chapter 8
Let's take a look at the binary search algorithm in the following section.
Binary
The binary search algorithm compares the input value to the middle element of the sorted
collection. If the values are not equal, the half in which the element is not found is
eliminated. The search continues on the remaining half of the collection. The time
complexity of this algorithm is in the order of O(log n).
The following code snippet shows an implementation of the binary search algorithm using
the sort.Search function from the sort package. The main method initializes the
elements array and invokes the sort.Search function to find an integer element:
// main method
func main() {
var elements []int
elements = []int{1, 3, 16, 10, 45, 31, 28, 36, 45, 75}
var element int
element = 36
var i int
[ 228 ]
Classic Algorithms Chapter 8
} else {
fmt.Printf("element %d not found in %v\n", element, elements)
}
}
Let's take a look at the interpolation search algorithm in the following section.
Interpolation
The interpolation search algorithm searches for the element in a sorted collection. The
algorithm finds the input element at an estimated position by diminishing the search space
before or after the estimated position. The time complexity of the search algorithm is of the
order O(log log n).
The following code snippet implements the interpolation search algorithm. The
InterpolationSearch function takes the array of integer elements and the integer
element to be found as parameters. The function finds the element in the collection and
returns the Boolean and the index for the found element:
//main package has examples shown
// in Go Data Structures and algorithms book
package main
[ 229 ]
Classic Algorithms Chapter 8
if elements[low] == element {
return true, low
} else if elements[high] == element {
return true, high
} else {
return false, -1
}
return false, -1
}
The main method initializes the array of integer elements and invokes the
InterpolationSearch method with the elements array and the element parameters, as
follows:
// main method
func main() {
var elements []int
elements = []int{2, 3, 5, 7, 9}
var element int
element = 7
var found bool
var index int
found, index = InterpolationSearch(elements, element)
fmt.Println(found, "found at", index)
}
[ 230 ]
Classic Algorithms Chapter 8
Now that we are done with search algorithms, let's take a look at the recursion algorithms
in the next section.
Recursion
Recursion is an algorithm in which one of the steps invokes the currently running method
or function. This algorithm acquires the outcome for the input by applying basic tasks and
then returns the value. This method was briefly discussed in the Divide and conquer
algorithms section of Chapter 1, Data Structures and Algorithms. During recursion, if the base
condition is not reached, then a stack overflow condition may arise.
A recursion algorithm is implemented in the following code snippet. The Factor method
takes the num as a parameter and returns the factorial of num. The method uses recursion to
calculate the factorial of the number:
//main package has examples shown
// in Go Data Structures and algorithms book
package main
//factorial method
func Factor(num int) int {
if num <= 1 {
return 1
}
return num * Factor(num-1)
}
[ 231 ]
Classic Algorithms Chapter 8
The main method defines the integer with a value of 12 and invokes the Factor method.
The factorial of the number 12 is printed, as shown in the following code:
//main method
func main() {
var num int = 12
fmt.Println("Factorial: %d is %d", num, Factor(num))
}
Now that we are done with recursive algorithms, let's take a look at the hash algorithms in
the next section.
Hashing
Hash functions were introduced in Chapter 4, Non-Linear Data Structures. Hash
implementation in Go has crc32 and sha256 implementations. An implementation of a
hashing algorithm with multiple values using an XOR transformation is shown in the
following code snippet. The CreateHash function takes a byte array, byteStr, as a
parameter and returns the sha256 checksum of the byte array:
//main package has examples shown
// in Go Data Structures and algorithms book
package main
[ 232 ]
Classic Algorithms Chapter 8
//CreateHash method
func CreateHash(byteStr []byte) []byte {
var hashVal hash.Hash
hashVal = sha1.New()
hashVal.Write(byteStr)
bytes = hashVal.Sum(nil)
return bytes
}
In the following sections, we will discuss the different methods of hash algorithms.
[ 233 ]
Classic Algorithms Chapter 8
// main method
func main() {
fmt.Printf("%x\n", bytes)
}
Summary
This chapter covered sorting algorithms such as bubble, selection, insertion, shell, merge,
and quick sort. Search algorithms such as linear, binary, and interpolation were the
discussed. Finally, the recursion and hashing algorithms were explained with code
snippets. All of the algorithms were discussed alongside code examples and performance
analysis.
In the next chapter, network representation using graphs and sparse matrix representation
using list of lists will be covered, along with appropriate examples.
[ 234 ]
Classic Algorithms Chapter 8
Questions
1. What is the order of complexity of bubble sort?
2. Which sorting algorithm takes one element at a time to create a final sorted
collection?
3. What sorting method sorts pairs of elements that are far apart from each other?
4. What is the complexity of using the merge sort algorithm?
5. Which is better: the quick, merge, or heap sort algorithm?
6. What are the different types of search algorithms?
7. Provide a code example of the recursion algorithm.
8. Who was the first person to describe the interpolation search?
9. Which sorting algorithm is based on a comparison-based method of an adjacent
list of elements?
10. Who was the person to publish the shell sort algorithm?
Further reading
The following books are recommended if you want to know more about algorithms such as
sorting, selecting, searching, and hashing:
Design Patterns, by Erich Gamma, Richard Helm, Ralph Johnson, and John
Vlissides
Introduction to Algorithms – Third Edition, by Thomas H. Cormen, Charles E.
Leiserson, Ronald L. Rivest, and Clifford Stein
Data structures and Algorithms: An Easy Introduction, by Rudolph Russell
[ 235 ]
3
Section 3: Advanced Data
Structures and Algorithms using
Go
Network representation, sparse matrix representation, memory management, instance
based learning, compiler translation, and process scheduling-related data structures and
algorithms are presented in this section. The data structures shown in the algorithms are
graphs, list of lists, AVL trees, K-D trees, ball trees, Van Emde Boas trees, buffer trees, and
red-black trees. Cache-oblivious data structures and data flow analysis are covered with
code examples and efficiency analysis.
A social graph that connects people is implemented in this chapter, and a code example
shows how the graph can be traversed. Map layouts are explained with geographic
locations with latitude and longitude. Knowledge graphs are explained via the use of a car
and its parts.
Network and Sparse Matrix Representation Chapter 9
Technical requirements
Install Go version 1.10 from https://golang.org/doc/install for your OS.
The GitHub URL for the code in this chapter is as follows: https://github.com/
PacktPublishing/Learn-Data-Structures-and-Algorithms-with-Golang/tree/master/
Chapter09.
Directed graph
Non-directed graph
Connected graph
Non-connected graph
Simple graph
Multi-graph
An adjacency list consists of adjacent vertices of a graph that have objects or records. An
adjacency matrix consists of source and destination vertices. An incidence matrix is a two-
dimensional Boolean matrix. The matrix has rows of vertices and columns that represent
the links (edges).
Network representation using a graph is shown in the following code. A social graph
consists of an array of links:
///main package has examples shown
// in Go Data Structures and algorithms book
package main
[ 238 ]
Network and Sparse Matrix Representation Chapter 9
The next section talks about the implementation of the different Link class methods.
// AddLink method
func (socialGraph *SocialGraph) AddLink(vertex1 int, vertex2 int, weight
int) {
socialGraph.Links[vertex1] = append(socialGraph.Links[vertex1],
Link{Vertex1: vertex1, Vertex2: vertex2, LinkWeight: weight})
}
[ 239 ]
Network and Sparse Matrix Representation Chapter 9
socialGraph = NewSocialGraph(4)
socialGraph.AddLink(0, 1, 1)
socialGraph.AddLink(0, 2, 1)
socialGraph.AddLink(1, 3, 1)
socialGraph.AddLink(2, 4, 1)
[ 240 ]
Network and Sparse Matrix Representation Chapter 9
socialGraph.PrintLinks()
In the next section, we will take a look at the unit test for the social graph method.
Test
Here, we have written a unit test for the social graph method. The code is as follows:
///main package has examples shown
// in Go Data Structures and algorithms book
package main
socialGraph = NewSocialGraph(1)
if socialGraph == nil {
[ 241 ]
Network and Sparse Matrix Representation Chapter 9
In the next section, a social network representation will be implemented with code
examples. The preceding graph will be enhanced with nodes. Each node will represent a
social entity.
Metrics related to the proximity of entities can be calculated based on the graph. Social
graphs consist of graph nodes and links, which are maps with a key name and multiple
keys names, respectively:
///Main package has examples shown
// in Go Data Structures and algorithms book
package main
[ 242 ]
Network and Sparse Matrix Representation Chapter 9
The different social network methods are explained and implemented in the next section.
[ 243 ]
Network and Sparse Matrix Representation Chapter 9
var m map[Name]struct{}
fmt.Println("Printing all links.")
for root, m = range socialGraph.Links {
var vertex Name
for vertex = range m {
// Edge exists from u to v.
fmt.Printf("Link: %d -> %d\n", root, vertex)
}
[ 244 ]
Network and Sparse Matrix Representation Chapter 9
}
}
socialGraph = NewSocialGraph()
socialGraph.AddEntity(root)
socialGraph.AddEntity(john)
socialGraph.AddEntity(per)
socialGraph.AddEntity(cynthia)
socialGraph.AddLink(root, john)
socialGraph.AddLink(root, per)
socialGraph.AddLink(root, cynthia)
socialGraph.AddLink(john, mayo)
socialGraph.AddLink(john, lorrie)
socialGraph.AddLink(per, ellie)
socialGraph.PrintLinks()
}
[ 245 ]
Network and Sparse Matrix Representation Chapter 9
Map layouts
A map layout is a geographical visualization of locations that are linked together. The
nodes in the graph of a map consist of geo-based information. The node will have
information such as the name of the location, latitude, and longitude. Maps are laid out in
different scales. Cartographic design is referred to as map creation using geographic
information.
A map layout is shown in the following code snippet. The Place class consists of Name,
Latitude, and Longitude properties:
// Place class
type Place struct {
Name string
[ 246 ]
Network and Sparse Matrix Representation Chapter 9
Latitude float64
Longitude float64
The different MapLayout methods are explained and implemented in the next section.
[ 247 ]
Network and Sparse Matrix Representation Chapter 9
mapLayout.GraphNodes[place] = struct{}{}
return true
}
var m map[Place]struct{}
fmt.Println("Printing all links.")
for root, m = range mapLayout.Links {
var vertex Place
for vertex = range m {
[ 248 ]
Network and Sparse Matrix Representation Chapter 9
mapLayout = NewMapLayout()
mapLayout.AddPlace(root)
mapLayout.AddPlace(netherlands)
mapLayout.AddPlace(korea)
mapLayout.AddPlace(tunisia)
mapLayout.AddLink(root, netherlands)
mapLayout.AddLink(root,korea)
mapLayout.AddLink(root,tunisia)
mapLayout.AddLink(korea, singapore)
mapLayout.AddLink(korea,japan)
mapLayout.AddLink(netherlands,uae)
mapLayout.PrintLinks()
}
[ 249 ]
Network and Sparse Matrix Representation Chapter 9
In the next section, we will take a look at the unit test for the NewMapLayout method.
Test
A unit test for the MapLayout class's NewMapLayout method is shown in the following
code snippet:
///main package has examples shown
// in Go Data Structures and algorithms book
package main
mapLayout = NewMapLayout()
test.Log(mapLayout)
if mapLayout == nil {
[ 250 ]
Network and Sparse Matrix Representation Chapter 9
Knowledge graphs
A knowledge graph is a network representation of entities, items, and users as nodes. The
nodes interact with one another via links or edges. Knowledge graphs are widely used
because they are schema less. These data structures are used to represent knowledge in the
form of graphs, and the nodes have textual information. Knowledge graphs are created by
using item, entity, and user nodes and linking them with edges.
// Class Type
type Class struct {
Name string
}
[ 251 ]
Network and Sparse Matrix Representation Chapter 9
The different knowledge graph methods are explained and implemented in the following
sections.
// NewKnowledgeGraph method
func NewKnowledgeGraph() *KnowledgeGraph {
return &KnowledgeGraph{
GraphNodes: make(map[Class]struct{}),
Links: make(map[Class]map[Class]struct{}),
}
}
// AddClass method
func (knowledgeGraph *KnowledgeGraph) AddClass(class Class) bool {
[ 252 ]
Network and Sparse Matrix Representation Chapter 9
// Add Link
func (knowledgeGraph *KnowledgeGraph) AddLink(class1 Class, class2 Class) {
var exists bool
if _, exists = knowledgeGraph.GraphNodes[class1]; !exists {
knowledgeGraph.AddClass(class1)
}
if _, exists = knowledgeGraph.GraphNodes[class2]; !exists {
knowledgeGraph.AddClass(class2)
}
var m map[Class]struct{}
fmt.Println("Printing all links.")
for car, m = range knowledgeGraph.Links {
var vertex Class
for vertex = range m {
fmt.Printf("Link: %s -> %s\n", car.Name, vertex.Name)
}
}
}
[ 253 ]
Network and Sparse Matrix Representation Chapter 9
knowledgeGraph = NewKnowledgeGraph()
knowledgeGraph.AddClass(car)
knowledgeGraph.AddClass(tyre)
knowledgeGraph.AddClass(door)
knowledgeGraph.AddClass(hood)
knowledgeGraph.AddLink(car, tyre)
knowledgeGraph.AddLink(car, door)
knowledgeGraph.AddLink(car, hood)
knowledgeGraph.AddClass(tube)
knowledgeGraph.AddClass(axle)
knowledgeGraph.AddClass(handle)
knowledgeGraph.AddClass(windowGlass)
knowledgeGraph.AddLink(tyre, tube)
knowledgeGraph.AddLink(tyre, axle)
knowledgeGraph.AddLink(door, handle)
knowledgeGraph.AddLink(door, windowGlass)
knowledgeGraph.PrintLinks()
}
[ 254 ]
Network and Sparse Matrix Representation Chapter 9
In the next section, we will take a look at the unit test for the NewKnowledgeGraph method.
Test
The NewKnowledgeGraph method is unit tested in the following code snippet:
///main package has examples shown
// in Go Data Structures and algorithms book
package main
knowledgeGraph = NewKnowledgeGraph()
test.Log(knowledgeGraph)
if knowledgeGraph == nil {
[ 255 ]
Network and Sparse Matrix Representation Chapter 9
The next section talks about the representation of the sparse matrix.
In the following code, a sparse matrix is modeled as a list of lists. A sparse matrix consists
of cells that are a list of lists. Each cell has properties such as Row, Column, and Value:
///main package has examples shown
// in Go Data Structures and algorithms book
package main
//List of List
type LOL struct {
Row int
Column int
Value float64
}
[ 256 ]
Network and Sparse Matrix Representation Chapter 9
SparseMatrix class
SparseMatrix has a cells array and shape, which is an integer array:
//Sparse Matrix
type SparseMatrix struct {
cells []LOL
shape [2]int
}
In the next section, the different Sparse methods of the SparseMatrix struct are
implemented.
[ 257 ]
Network and Sparse Matrix Representation Chapter 9
return true
}
return false
}
[ 258 ]
Network and Sparse Matrix Representation Chapter 9
[ 259 ]
Network and Sparse Matrix Representation Chapter 9
sparseMatrix = NewSparseMatrix(3, 3)
sparseMatrix.SetValue(1, 1, 2.0)
sparseMatrix.SetValue(1, 3, 3.0)
fmt.Println(sparseMatrix)
fmt.Println(sparseMatrix.NumNonZero())
}
[ 260 ]
Network and Sparse Matrix Representation Chapter 9
Summary
This chapter covered how to present networks and sparse matrices using graphs and a list
of lists, respectively. Social network representation, map layouts, and knowledge graphs
were discussed in detail with code examples. The different sparse matrix methods were
also implemented with the appropriate code.
In the next chapter, algorithms such as garbage collection, cache management, and space
allocation will be presented with code examples and an efficiency analysis.
Questions
What data structure is used to represent a set of linked objects?
What is a two-dimensional matrix with Boolean values called?
Give a code example for a network representation using a graph.
Which metrics can be calculated from a social graph?
What is a cartographic design?
Give an example of a knowledge graph and define the class, slots, and facets.
What are the applications of sparse matrices?
Define a list of lists and write a code sample.
What is a map layout?
What different operations can be performed using graphs?
[ 261 ]
Network and Sparse Matrix Representation Chapter 9
Further reading
The following books are recommended if you want to know more about graphs and list of
lists:
Design Patterns, by Erich Gamma, Richard Helm, Ralph Johnson, and John
Vlissides
Introduction to Algorithms – Third Edition, by Thomas H. Cormen, Charles E.
Leiserson, Ronald L. Rivest, and Clifford Stein
Data structures and Algorithms: An Easy Introduction, by Rudolph Russell
[ 262 ]
10
Memory Management
Memory management is a way to control and organize memory. Memory divisions are
called blocks, and they are used for running different processes. The basic goal of memory
management algorithms is to dynamically designate segments of memory to programs on
demand. The algorithms free up memory for reuse when the objects in the memory are
never required again. Garbage collection, cache management, and space allocation
algorithms are good examples of memory management techniques. In software
engineering, garbage collection is used to free up memory that's been allocated to those
objects that won't be used again, thus helping in memory management. The cache provides
in-memory storage for data. You can sort the data in the cache into locale-specific groups.
The data can be stored using key and value sets.
This chapter covers the garbage collection, cache management, and space allocation
algorithms. The memory management algorithms are presented with code samples and
efficiency analyses. The following topics will be covered in this chapter:
Garbage collection
Cache management
Space allocation
Concepts—Go memory management
We'll look at garbage collection first, then look at the different algorithms related to garbage
collection.
Technical requirements
Install Go Version 1.10 from Golang (https://golang.org/), choosing the right version for
your OS.
The GitHub repository for the code in this chapter can be found here: https://github.com/
PacktPublishing/Learn-Data-Structures-and-Algorithms-with-Golang/tree/master/
Chapter10.
Memory Management Chapter 10
Garbage collection
Garbage collection is a type of programmed memory management in which memory,
currently occupied by objects that will never be used again, is gathered. John McCarthy was
the first person to come up with garbage collection for managing Lisp memory
management. This technique specifies which objects need to be de-allocated, and then
discharges the memory. The strategies that are utilized for garbage collection are stack
allocation and region interference. Sockets, relational database handles, user window
objects, and file resources are not overseen by garbage collectors.
Garbage collection algorithms help reduce dangling pointer defects, double-free defects,
and memory leaks. These algorithms are computing-intensive and cause decreased or
uneven performance. According to Apple, one of the reasons for iOS not having garbage
collection is that garbage collection needs five times the memory to match explicit memory
management. In high-transactional systems, concurrent, incremental, and real-time garbage
collectors help manage memory collection and release.
GC throughput
Heap overhead
Pause times
Pause frequency
Pause distribution
Allocation performance
Compaction
Concurrency
Scaling
Tuning
Warm-up time
Page release
Portability
Compatibility
[ 264 ]
Memory Management Chapter 10
//Reference Counter
type ReferenceCounter struct {
num *uint32
pool *sync.Pool
removed *uint32
}
[ 265 ]
Memory Management Chapter 10
// Push method
func (stack *Stack) Push(reference *ReferenceCounter) {
stack.references = append(stack.references[:stack.Count],
reference)
stack.Count = stack.Count + 1
}
// Pop method
func (stack *Stack) Pop() *ReferenceCounter {
if stack.Count == 0 {
return nil
}
var length int = len(stack.references)
var reference *ReferenceCounter = stack.references[length -1]
if length > 1 {
stack.references = stack.references[:length-1]
} else {
stack.references = stack.references[0:]
}
stack.Count = len(stack.references)
return reference
[ 266 ]
Memory Management Chapter 10
[ 267 ]
Memory Management Chapter 10
Reference counting
Reference counting is a technique that's used for keeping the count of references, pointers,
and handles to resources. Memory blocks, disk space, and objects are good examples of
resources. This technique tracks each object as a resource. The metrics that are tracked are
the number of references held by different objects. The objects are recovered when they can
never be referenced again.
The number of references is used for runtime optimizations. Deutsch-Bobrow came up with
the strategy of reference counting. This strategy was related to the number of updated
references that were produced by references that were put in local variables. Henry Baker
came up with a method that includes references in local variables that are deferred until
needed.
In the following subsections, the simple, deferred, one-bit, and weighted techniques of
reference counting will be discussed.
The collection technique tracks, for each object, a tally of the number of references to the
object. The references are held by other objects. The object gets removed when the number
of references to the object is zero. The removed object becomes inaccessible. The removal
of a reference can prompt countless connected references to be purged.
The algorithm is time-consuming because of the size of the object graph and slow access
speed.
In the following code snippets, we can see a simple reference-counting algorithm being
implemented. The ReferenceCounter class has number (num), pool, and removed
references as properties:
///main package has examples shown
// in Go Data Structures and algorithms book
package main
[ 268 ]
Memory Management Chapter 10
//Reference Counter
type ReferenceCounter struct {
num *uint32
pool *sync.Pool
removed *uint32
}
// Add method
func (referenceCounter ReferenceCounter) Add() {
atomic.AddUint32(referenceCounter.num, 1)
}
// Subtract method
func (referenceCounter ReferenceCounter) Subtract() {
if atomic.AddUint32(referenceCounter.num, ^uint32(0)) == 0 {
atomic.AddUint32(referenceCounter.removed, 1)
}
}
Let's look at the main method and see an example of simple reference counting. The
newReferenceCounter method is invoked, and a reference is added by invoking the Add
method. The count reference is printed at the end. This is shown in the following code
snippet
// main method
func main() {
var referenceCounter ReferenceCounter
referenceCounter = newReferenceCounter()
referenceCounter.Add()
fmt.Println(*referenceCounter.count)
}
[ 269 ]
Memory Management Chapter 10
The different types of reference counting techniques are described in the following sections.
[ 270 ]
Memory Management Chapter 10
weight int
}
//WeightedReference method
func WeightedReference() int {
var references []ReferenceCounter
references = GetReferences(root)
var reference ReferenceCounter
var sum int
for _, reference = range references {
sum = sum + reference.weight
}
return sum
}
A mutator in this algorithm handles concurrency by changing the pointers while the
collector is running. It also takes care of the condition so that no black object points to a
white object. The mark algorithm has the following steps:
The following code snippet shows the marking algorithm. Let's look at the implementation
of the Mark method:
func Mark( root *object){
var markedAlready bool
markedAlready = IfMarked(root)
if !markedAlready {
map[root] = true
}
var references *object[]
references = GetReferences(root)
[ 271 ]
Memory Management Chapter 10
For each object in the heap, mark the bit as false if the value of the bit is true
If the value of the bit is true, release the object from the heap
The sweep algorithm releases the objects that are marked for garbage collection.
The entire heap needs to be scavenged, even if a generation is collected. Let's say generation
3 is collected; in this case, generations 0-2 are also scavenged. The generational collection
algorithm is presented in the following code snippet:
func GenerationCollect(){
var currentGeneration int
currentGeneration = 3
var objects *[]object
objects = GetObjectsFromOldGeneration(3)
[ 272 ]
Memory Management Chapter 10
Cache management
Cache management consists of managing static, dynamic, and variable information:
The object cache is stored in various data structures, such as maps and trees. Maps have a
key as an identifier and a value, which is an object.
Cache objects can be related to memory, disks, pools, and streams. Caches have attributes
related to time to live, group, and region. A region consists of a collection of mapped key-
values. Regions can be independent of other regions. Cache configuration consists of
defaults, regions, and auxiliaries.
Memory management
Thread pool controls
Grouping of elements
Configurable runtime parameters
Region data separation and configuration
Remote synchronization
Remote store recovery
Event handling
Remote server chaining and failover
Custom event logging
[ 273 ]
Memory Management Chapter 10
The CacheObject class and the Cache class are described in the following sections.
// CacheObject class
type CacheObject struct {
Value string
TimeToLive int64
}
The IfExpired method of the CacheObject class is shown in the next section.
[ 274 ]
Memory Management Chapter 10
//Cache class
type Cache struct {
objects map[string]CacheObject
mutex *sync.RWMutex
}
The NewCache, GetObject, and SetValue methods of the Cache class are shown in the
following sections.
[ 275 ]
Memory Management Chapter 10
}
return object.Value
}
We'll implement the methods we just took a look at in the main method in the next section.
[ 276 ]
Memory Management Chapter 10
Space allocation
Each function has stack frames associated with individual memory space. Functions have
access to the memory inside the frame, and a frame pointer points to the memory's
location. Transition between frames occurs when the function is invoked. Data is
transferred by value from one frame to another during the transition.
Stack frame creation and memory allocation is demonstrated in the following code.
The addOne function takes num and increments it by one. The function prints the value and
address of num:
///main package has examples shown
// in Go Data Structures and algorithms book
package main
// increment method
func addOne(num int) {
num++
fmt.Println("added to num", num, "Address of num", &num)
}
[ 277 ]
Memory Management Chapter 10
The main method initializes the variable number as 17. The number value and address are
printed before and after invoking the addOne function. This is shown in the following code:
// main method
func main() {
var number int
number = 17
fmt.Println("value of number", number, "Address of number",
&number)
addOne(number)
fmt.Println("value of number after adding One", number, "Address
of", &number)
}
Pointers
Pointers have an address that is 4 or 8 bytes long, depending on whether you have a 32-bit
or 64-bit architecture. The stack's main frame consists of the number 17 with the address
0xc420016058. After adding one, a new frame with num equal to 18 and an address of
0xc420016068 is created. The main method prints the stack's main frame after invoking
the addOne function. The code in the following sections demonstrates memory space
allocation with pointers instead of actual values passed into a function.
The AddOne and main methods of pointers are shown in the following sections.
[ 278 ]
Memory Management Chapter 10
// increment method
func addOne(num *int) {
*num++
fmt.Println("added to num", num, "Address of num", &num, "Value
Points To", *num )
}
In this example, the address of the number is the same as the value of num in the addOne
function. Pointers share the address of the variable for the function to access for reads and
writes within the stack frame. Pointer types are specific to every type that is declared.
Pointers provide indirect memory access outside the function's stack frame. This is shown
in the following code:
// main method
func main() {
var number int
number = 17
fmt.Println("value of number", number, "Address of number",
&number)
addOne(&number)
fmt.Println("value of number after adding One", number, "Address
of", &number)
}
[ 279 ]
Memory Management Chapter 10
Some of the best practices that you can follow to improve memory management are as
follows:
[ 280 ]
Memory Management Chapter 10
Profiling
Profiling in Go can be enabled by using the cpuprofile and memprofile flags. The Go
testing package has support for benchmarking and profiling. The cpuprofile flag can be
invoked by the following command:
go test -run=none -bench=ClientServerParallel4 -cpuprofile=cprofile
net/http
The benchmark can be written to a cprofile output file using the following command:
go tool pprof --text http.test cprof
Let's look at an example of how to profile the programs that you have written. The
flag.Parse method reads the command-line flags. The CPU profiling output is written to
a file. The StopCPUProfile method on the profiler is called to flush any pending file
output that needs to be written before the program stops:
var profile = flag.String("cpuprofile", "", "cpu profile to output file")
func main() {
flag.Parse()
if *profile != "" {
var file *os.File
var err error
file, err = os.Create(*profile)
if err != nil {
log.Fatal(err)
}
pprof.StartCPUProfile(file)
defer pprof.StopCPUProfile()
}
Summary
This chapter covered the garbage collection, cache management, and memory space
allocation algorithms. We looked at reference counting algorithms, including simple,
deferred, one-bit, and weighted. The mark-and-sweep and generational collection
algorithms were also presented with code examples.
The next chapter will cover the next steps we can take after going through this book.
[ 281 ]
Memory Management Chapter 10
Questions
1. Which factors are considered when choosing a garbage collection algorithm?
2. In which reference counting algorithm are program-variable references ignored?
3. What is the type of reference counting algorithm in which a single-bit flag is used
for counting?
4. In which reference counting algorithm is a weight assigned to each reference?
5. Who invented weighted reference counting?
6. Which garbage collection algorithm was proposed by Dijkstra?
7. What class handles concurrency when the mark-and-sweep collector is running?
8. What are the criteria for promoting objects to older generations?
9. Draw a flow chart for the cache management algorithm.
10. How do you get indirect memory access outside a method's stack frame?
Further reading
The following books are recommended if you want to know more about garbage collection:
Design Patterns, by Erich Gamma, Richard Helm, Ralph Johnson, and John
Vlissides
Introduction to Algorithms – Third Edition, by Thomas H. Cormen, Charles E.
Leiserson, Ronald L. Rivest, and Clifford Stein
Data structures and Algorithms: An Easy Introduction, by Rudolph Russell
[ 282 ]
Next Steps
In this appendix, we share the reader's learning outcomes from this book. The code
repository links and key takeaways are presented. References are included for the latest
data structures and algorithms. Tips and techniques are provided to help you keep yourself
up to date with the latest on data structures and algorithms.
Technical requirements
Install Go version 1.10 from https://golang.org/doc/install, being sure to choose the
correct version for your operating system.
The GitHub repository for the code in this appendix can be found here: https://github.
com/PacktPublishing/Learn-Data-Structures-and-Algorithms-with-Golang/tree/
master/Appendix.
Learning outcomes
The learning outcomes from this book are as follows:
In the following section, the key takeaway points, papers, and articles to be referred to,
along with tips and techniques, are discussed.
Next Steps
Key takeaways
The key takeaways for the reader are as follows:
How to choose the correct algorithm and data structures for a problem.
How to compare the complexity and data structures of different algorithms for
code performance and efficiency.
How to apply best practices to improve and increase the performance of an
application.
Real-world problems, solutions, and best practices associated with web and
mobile software solutions are provided in the book as code examples.
Next steps
In this section, papers and articles are provided as further reading for each chapter.
[ 284 ]
Next Steps
[ 285 ]
Next Steps
[ 286 ]
Next Steps
THE REPRESENTATION OF
ALGORITHMS – DTIC (https://apps.dtic.mil/dtic/tr/fulltext/u2/697026.
pdf)
ON THE COMPUTATIONAL COMPLEXITY OF
ALGORITHMS (https://fi.ort.edu.uy/innovaportal/file/20124/1/60-hartm
anis_stearns_complexity_of_algorithms.pdf)
Analysis and Performance of Divide and Conquer
Methodology (http://ijarcet.org/wp-content/uploads/IJARCET-VOL-6-ISSUE-
8-1295-1298.pdf)
[ 287 ]
Next Steps
[ 288 ]
Next Steps
The following papers are related to network and sparse matrix representation:
In Chapter 9, Network and Sparse Matrix Representation, use cases from real-life applications
were presented. Learning how the network data structure and sparse matrices are applied
in different domains, such as airlines, banking, medical, pharma, telecoms, and supply
chains, is a good next step for the reader.
[ 289 ]
Next Steps
The different tips and tricks to be used in Go data structures and algorithm are discussed in
the next section.
Gopherize: https://gopherize.me/?fromhttp=true
Golang Weekly: https://golangweekly.com/issues/240
The Gopher Conference: https://www.gophercon.com/
Google Groups: https://groups.google.com/forum/m/#!newtopic/golang-
dev
Slack Groups: https://techbeacon.com/46-slack-groups-developers
Stack Overflow: http://stackoverflow.com/questions/tagged/go
Dave Cheney's Resources for New Go Programmers: https://dave.cheney.net/
resources-for-new-go-programmers
The following section contains tips for writing good code in Go.
// delayTimeOut method
func delayTimeOut(channel chan interface{}, timeOut time.Duration)
(interface{}, error) {
log.Printf("delayTimeOut enter")
defer log.Printf("delayTimeOut exit")
var data interface{}
[ 290 ]
Next Steps
select {
case <-time.After(timeOut):
return nil, errors.New("delayTimeOut time out")
case data = <-channel:
return data, nil
}
}
//main method
func main() {
channel := make(chan interface{})
go func() {
var err error
var data interface{}
data, err = delayTimeOut(channel, time.Second)
if err != nil {
log.Printf("error %v", err)
return
}
log.Printf("data %v", data)
}()
channel <- struct{}{}
time.Sleep(time.Second * 2)
go func() {
var err error
var data interface{}
data, err = delayTimeOut(channel, time.Second)
if err != nil {
log.Printf("error %v", err)
return
}
log.Printf("data %v", data)
}()
time.Sleep(time.Second * 2)
}
[ 291 ]
Next Steps
import (
"errors"
"golang.org/x/net/context"
"log"
"time"
)
// main method
func main() {
delay = time.Millisecond
go func(context.Context) {
<-contex.Done()
log.Printf("contex done")
[ 292 ]
Next Steps
}(contex)
_ = cancel
time.Sleep(delay * 2)
time.Sleep(delay * 2)
[ 293 ]
Next Steps
package main
import(
"path"
"runtime"
"fmt"
"log"
"time"
)
//checkPoint method
func checkPoint() string {
pc, file, line, _ := runtime.Caller(1)
return fmt.Sprintf("\033[31m%v %s %s %d\x1b[0m", time.Now(),
runtime.FuncForPC(pc).Name(), path.Base(file), line)
}
//method1
func method1(){
fmt.Println(checkPoint())
}
//main method
func main() {
log.SetFlags(log.LstdFlags | log.Lshortfile)
method1()
[ 294 ]
Next Steps
Go tool usage
The Go tool compiler can be invoked with the following command:
go build -gcflags="-S -N"
To test the race conditions, you can use the following command:
go test -race
Running a test method by name can be done using the following syntax:
go test -run=method1
To update your version of Go, you can use the following command:
go get -u
Go environment variables
The GOROOT variable can be configured as an environment variable with this command:
export GOROOT=/opt/go1.7.1
The PATH variable can be configured as an environment variable with this command:
export PATH=$GOROOT/bin:$PATH
The GOPATH variable can be configured as an environment variable with this command:
export GOPATH=$HOME/go
[ 295 ]
Next Steps
The GOPATH variable can be configured in the PATH variable with this command:
export PATH=$GOPATH/bin:$PATH
Test table
Tests are driven by a test table. The following code snippet shows how a test table can be
used:
//main package has examples shown
// in Go Data Structures and algorithms book
package main
import (
"testing"
)
[ 296 ]
Next Steps
Importing packages
You can import packages with the following statements. Here, we show three different
syntactical options:
import "fmt"
import ft "fmt"
import . "fmt"
import(
"fmt"
"errors"
if !ok {
return nil, errors.New("false error")
}
return v, nil
}
[ 297 ]
Next Steps
//SecondFunc method
func SecondFunc() {
defer func() {
var err interface{}
if err = recover(); err != nil {
fmt.Println("recovering error ", err)
}
}()
var v interface{}
v = struct{}{}
var err error
if _, err = FirstFunc(v); err != nil {
panic(err)
}
//main method
func main() {
SecondFunc()
fmt.Println("The execution ended")
}
The following link contain some useful tips and techniques for writing Go code: https://
golang.org/doc/effective_go.html.
[ 298 ]
Other Books You May Enjoy
If you enjoyed this book, you may be interested in these other books by Packt:
[ 300 ]
Other Books You May Enjoy
[ 301 ]
Index
[ 303 ]
main method 202 unordered lists 183
String method 201 homogeneous data structures
Farey series 200 about 142
Fibonacci sequence multi-dimensional arrays 159
about 203 two-dimensional arrays 143
FibonacciNumber method 204
main method 204, 205 I
finite element method (FEM) 256 identity matrix 147
flow chart 35 incidence matrix 238
flow control keywords 35 insertion sort
flyweight pattern 28, 29, 31 about 216
InsertionSorter method 217
G main method 217, 218
Gang of Four (GoF) 15 interpolation search algorithm 229
garbage collection algorithms
factors 264 K
garbage collection knowledge graph 251
about 264 KnowledgeGraph class
generational collection algorithm 272 about 252
mark-and-sweep algorithm 271 AddClass method 252
reference counting 268 AddLink method 253
ReferenceCounter class 265 main method 254
region interference 264 NewKnowledgeGraph method 252
stack allocation 264 PrintLinks method 253
Stack class 266 test 255, 256
generational collection algorithm 272
Go Array 54 L
Go Slice 54
len function 55
graphs
linear complexity 39
about 238
linear data structures
types 238
lists 83
queues 100
H sets 94
hash functions stack 108
about 138, 140, 232 tuples 99
CreateHashMutliple method 233 linear search method 227
main method 234 Link class
XOR method 233 about 239
hashing 232 AddLink method 239
hashing algorithm 211 main method 240, 241
heaps 13, 14 NewSocialGraph method 239
heterogeneous data structures PrintLinks method 240
about 164 test 241, 242
linked lists 165 linked lists
ordered lists 172 about 164, 165
[ 304 ]
circular-linked list 169 symmetric matrix 148
doubly linked list 168 two-dimensional matrix 148
singly linked list 165 upper triangular matrix 145
LinkedList zig-zag matrix 152, 154
about 83, 84 memory management
AddAfter method 87 about 263
AddToEnd method 86 best practices 280
AddToHead method 84, 85 merge sort algorithm
IterateList method 85 about 220, 221
LastNode method 86 JoinArrays method 222
main method 88 main method 222
Node class 83 MergeSorter method 221
NodeWithValue method 87 multi-dimensional arrays
lists about 159
about 10, 11, 83 tensors 160
doubly linked list 89 multiSorter class
LinkedList 83 about 179
ordered lists 172 len method 180
unordered lists 172 less method 181
logarithmic complexity 42, 43 main method 181
look-and-say sequence 205 OrderBy method 180
lower triangular matrix 145 Sort method 180
Swap method 180
M Mutable TreeSets
m x n matrix 144 about 199
map layout 246 RemoveNode method 199
MapLayout class Treeset.bst 200
about 247
AddLink method 248 N
AddPlace method 247 negative matrix 148
main method 249, 250 network representation, with graphs
NewMapLayout method 247 about 238
PrintLinks method 248 knowledge graph 251
test 250, 251 Link class 239
maps 59 map layouts 246
mark-and-sweep algorithm 271, 280 social network 242
matrix non-linear data structures
about 142, 143 about 113
Boolean matrix 156 containers 136
column matrix 144 symbol tables 136
identity matrix 147 tables 134
lower triangular matrix 145 trees 114
null matrix 146 null matrix 146
row matrix 144
spiral matrix 154, 155
[ 305 ]
simple reference counting 268
O weighted reference counting 270
old toy algorithm 154 ReferenceCounter class
one-bit reference counting 270 about 265
ontology 251 newReferenceCounter method 265
ordered lists representations, of algorithms
about 164, 172 flow chart 35
SortByAge type 174 pseudo code 36
struct type 179 row matrix 144
Thing class 175
Thing sorter class 176 S
ToString method 173
scalar 142, 143
search algorithms
P about 211, 226
performance analysis binary search algorithm 228
of algorithms 36 interpolation search algorithm 229
pointers linear search method 227
addOne method 279 selection sort
main method 279, 280 about 214
private class data pattern 31 main method 215, 216
product matrix 150 swap method 215
profiling 281 sequences
proxy pattern 33 about 200
pseudo code 35, 36 Farey sequence 200
Fibonacci sequence 203
Q look-and-say sequence 205
quadratic complexity 40 sets
queues about 94
about 100 AddElement method 95
Add method 101 ContainsElement method 96
main method 102 DeleteElement method 95
New method 101 InterSect method 97
Synchronized Queue 103 main method 96, 98
quick sort algorithm Union method 97
about 223 shell sort algorithm
divideParts method 224 about 218
main method 225, 226 main method 220
swap method 225 power method 219
simple reference counting 268
R singly linked list
recursion 231, 232 about 165
reference counting CreateLinkedList method 166
about 268 main method 167
deferred reference counting 270 ReverseLinkedList method 166
one-bit reference counting 270 slice function 55, 56
[ 306 ]
slice of slices 56, 143 Main method 111
slices New method 109
about 52, 54 Pop method 110
two-dimensional slices 56, 57, 58 Push method 109
social network, methods streams 200
AddEntity method 243 struct type
AddLink method 244 about 179
main method 245, 246 multiSorter class 179
NewSocialGraph method 243 structural design patterns
PrintLinks method 244 about 15
social network adapter pattern 16, 17
about 242 bridge pattern 17, 18
representing 242 composite pattern 20
sorting algorithms decorator pattern 22, 23
about 211, 212 facade pattern 24, 25, 27
bubble sort algorithm 212, 213, 214 flyweight pattern 28, 29, 31
insertion sort 216 private class data pattern 31
merge sort algorithm 220, 221 proxy pattern 33
quick sort algorithm 223 structured program 35
selection sort 214 subsequence 200
shell sort algorithm 218 symbol tables 136
space allocation symmetric matrix 148
about 277, 278 Synchronized Queue
pointers 278 about 103
sparse matrix EndPass method 106
about 237, 256 EndTicketIssue method 105
modeling, as list of lists 256 main method 107
SparseMatrix class New method 104
about 257 passenger method 107
Equal method 258 StartPass method 106
GetValue method 258 StartTicketIssue method 105
LessThan method 257 ticketIssue method 106
main method 260, 261 Synchronized TreeSets 198
NewSparseMatrix method 260
NumNonZero method 257 T
SetValue method 259 T-tree 133
Shape method 257 tables
sparsity 237 about 134
spiral matrix 154, 155 Column class 134
Stack class main method 135
about 266 printTable method 135
main method 267 Row class 134
new method 266 Table class 134
stacks tensors 142, 160
about 108 Thing class
[ 307 ]
ByFactor function type 176 PreOrderTraverseTree method 196
Thing sorter class search method 196
len method 177 String method 197
less method 177 Synchronized TreeSets 198
main method 177 tuples 12, 13, 99
swap method 177 two-dimensional arrays 143
Thue–Morse sequence 207 two-dimensional matrix
TreeNode class add method 148
about 124 determinant method 151
adjustBalance method 126 inverse method 151
BalanceTree method 126 multiply method 150
doubleRotation method 125 subtract method 149
InsertNode method 128 transpose method 151
insertRNode method 127 two-dimensional slices 56, 57, 58
main method 131, 132
opposite method 125 U
removeBalance method 129 unordered lists 172, 183
RemoveNode method 128 UnOrderedList class
removeRNode method 129 about 184
singleRotation method 125 AddtoHead method 184
trees IterateList method 184
about 114 main method 185
Adelson, Velski, and Landis (AVL) tree 124 upper triangular matrix 145
B+ tree 133
B-tree 133 V
binary search tree 114
variadic functions
T-tree 133
about 63
TreeSets
delete operation 65, 66
about 193
update operation 64, 65
Delete method 195
vectors 142
inOrderTraverseTree method 195
InOrderTraverseTree method 195
InsertTreeNode method 194
W
main method 197, 198 weighted reference counting 270
Mutable TreeSets 199
preOrderTraverseTree method 196 Z
zig-zag matrix 152, 154