Preface
Goal: A practical case to collect unique record fields using Go.
With Go
, we have simple code.
Avoid coder to abuse too many features.
The code written years ago, will still works today.
Simplicity is a necessity.
Even dummies like can code with this simple language.
Reference Reading
Boring is good for business.
Source Examples
You can obtain source examples here:
Common Use Case
Task: Get the unique tag string
Please read overview for more detail.
Prepopulated Data
Songs and Poetry
package mysongs
type Song struct {
Title string
Tags []string
}
type Songs []Song
func GetSongs() Songs{
songs := []Song{
Song{"Cantaloupe Island",
[]string{"60s", "jazz"}},
Song{"Let It Be",
[]string{"60s", "rock"}},
Song{"Knockin' on Heaven's Door",
[]string{"70s", "rock"}},
Song{"Emotion",
[]string{"70s", "pop"}},
Song{"The River",
nil},
}
return songs
}
Go Solution
The Answer
There might be many ways to do things in Go
.
One of them is this oneliner as below:
func main() {
var tags []string
tags = mysongs.GetSongs().FlattenTags()
tags = myutils.Unique(tags)
fmt.Println(tags)
}
Enough with introduction, at this point we should go straight to coding.
Environment
No need any special setup. Just run and voila..!
Indentation
Tabs.
1: Data Structure
We are going to use slices
throught out this article.
slices
is open array, in contrast with fix length array.
Simple Array
Before building a struct
,
I begin with simple array
with fix length [5]string
as below code:
package main
import "fmt"
func main() {
var tags = [5]string{
"rock", "jazz", "rock", "pop", "pop"}
fmt.Println(tags)
}
It is easy to dump variable in go
using fmt
.
With the result similar as below array
:
$ go run 01-tags.go
[rock jazz rock pop pop]
The Song Struct
We can continue our journey to records, using struct
.
package main
import "fmt"
type Song struct {
Title string
Tags []string
}
func main() {
var song = Song{"Cantaloupe Island",
[]string{"60s", "jazz"}}
fmt.Println(song)
}
With the result similar as below record:
$ go run 02-song.go
{Cantaloupe Island [60s jazz]}
Array of Song Record
Meet The Songs Array
From just karaoke, we can go pro with recording studio. From simple data, we can build a structure to solve our task.
package main
import "fmt"
type Song struct {
Title string
Tags []string
}
func main() {
songs := []Song{
Song{"Cantaloupe Island",
[]string{"60s", "jazz"}},
Song{"Let It Be",
[]string{"60s", "rock"}},
Song{"Knockin' on Heaven's Door",
[]string{"70s", "rock"}},
Song{"Emotion",
[]string{"70s", "pop"}},
Song{"The River",
nil},
}
for _, song := range songs {
fmt.Println(song)
}
}
_.
It is more convenience to show the result using for range
,
with the result similar as below sequential lines of Song
type:
$ go run 03-songs.go
{Cantaloupe Island [60s jazz]}
{Let It Be [60s rock]}
{Knockin' on Heaven's Door [70s rock]}
{Emotion [70s pop]}
{The River []}
Maybe Null
As you can see, no need additional setting.
The struct accept null types, instead of writing []string{}
.
2: Separating Package
Since we need to reuse the songs record multiple times, it is a good idea to separate the record from logic.
Separating package in go
require a bit of concentration.
The rules seems tedious at first,
but in the end your code is organized well.
Function Naming
Public function must begin with uppercase
letter.
Reading
You should read this first
mysongs Package
To avoid a main code, that full of a bunch of loop, we can modularized into separate package.
The code can be shown as below:
package mysongs
type Song struct {
Title string
Tags []string
}
type Songs []Song
func GetSongs() Songs{
return []Song{
Song{"Cantaloupe Island",
[]string{"60s", "jazz"}},
Song{"Let It Be",
[]string{"60s", "rock"}},
Song{"Knockin' on Heaven's Door",
[]string{"70s", "rock"}},
Song{"Emotion",
[]string{"70s", "pop"}},
Song{"The River",
nil},
}
}
I also add addtional Songs
type constructor.
Configuration
The go.mod
file is simply as below setting
module mysongs
go 1.15
Using mysongs Package
Now we can have a very short code.
package main
import (
"example.com/mysongs"
"fmt"
)
func main() {
for _, song := range mysongs.GetSongs() {
fmt.Println(song)
}
}
_.
Configuration
The go.mod
file for root directory has some settings:
module tutor
go 1.15
replace example.com/mysongs => ./mysongs
require example.com/mysongs v1.0.0
With the result exactly the same as above array.
3: Finishing The Task
Map, Filter, Flatten, Unique
Extracting Fields
Go
do not use idiomatic map
, filter
, and reduce
as in functional programming.
We should do it by using loop instead.
package main
import (
"example.com/mysongs"
"fmt"
)
func mapAndFilter(songs []mysongs.Song) [][]string {
var tagss [][]string
for _, song := range songs {
if song.Tags != nil {
tagss = append(tagss, song.Tags)
}
}
return tagss
}
func main() {
tagss := mapAndFilter(mysongs.GetSongs())
fmt.Println(tagss)
}
With the result similar as below slices
of slices
:
$ go run 05-map.go
[[60s jazz] [60s rock] [70s rock] [70s pop]]
This doesn’t seems hard right!
Flatten Function
There is no standard library for flatten.
But we can rewrite above code as loop in loop,
with help of append
function as shown below:
package main
import (
"example.com/mysongs"
"fmt"
)
func flatten(songs []mysongs.Song) []string {
var tags []string
for _, song := range songs {
if song.Tags != nil {
for _, tag := range song.Tags {
tags = append(tags, tag)
}
}
}
return tags
}
func main() {
tags := flatten(mysongs.GetSongs())
fmt.Println(tags)
}
With the result as below slices
.
❯ go run 06-flatten.go
[60s jazz 60s rock 70s rock 70s pop]
No hidden algorithm. No high level abstraction. Just loop.
Flatten Method Chaining
We can also rewrite function above as method chaining.
package mysongs
type Song struct {…}
type Songs []Song
func GetSongs() Songs{
return …
}
func (songs Songs) FlattenTags() []string {
var tags []string
…
return tags
}
So we can call this method in object oriented manners.
tags = mysongs.GetSongs().FlattenTags()
Unique Reference
In order to get to get distinct
values,
we can utilize unique
functin from this site.
Unique
This is basically, using hash (map
) to mark a value.
- [gitlab.com/…/go/07][src-07-unique-go]
package main
import (
"example.com/mysongs"
"fmt"
)
func unique(tags []string) []string {
keys := make(map[string]bool)
uniq := []string{}
for _, tag := range tags {
_, value := keys[tag]
if !value {
keys[tag] = true
uniq = append(uniq, tag)
}
}
return uniq
}
func main() {
var tags []string
tags = mysongs.GetSongs().FlattenTags()
tags = unique(tags)
fmt.Println(tags)
}
With the final result similar as below sequence:
$ go run 07-unique.go
[60s jazz rock 70s pop]
What is Next 🤔?
There are so many things to be explored with Go. We already have a case example, we have to move on.
Consider continue reading [ Go - Playing with Records - Part Two ].