Preface
Goal: Continue Part One
4: Approach in Solving Unique
A custom pattern matching example, that you can rewrite.
An Algorithm Case
Why making things complex?
My intention is writing a custom pattern matching example, so later I can rewrite other algorithm based on this example. You can read the detail on the first article. The record overview.
Consider going back to simple data. We need to discuss about solving unique list.
Head and Tail Pattern Matching
The original x:xs
pattern matching from haskell
shown as below:
exclude :: String -> ([String] -> [String])
exclude value = filter((/=) value)
unique :: [String] -> [String]
unique [] = []
unique (head:tail) = head:unique(exclude head tail)
tags :: [String]
tags = ["rock", "jazz", "rock", "pop", "pop"]
main = print (unique tags)
With the result of
["rock","jazz","pop"]
This x:xs
pattern is made of two functions:
- Exclude
- Unique
Loop for Exclude
Based on
exclude :: String -> ([String] -> [String])
exclude value = filter((/=) value
We can rewrite twoliners above to this below:
package main
import "fmt"
func exclude(value string, tags []string) []string {
var newTags []string
for _, tag := range tags {
if tag != value {
newTags = append(newTags, tag)
}
}
return newTags
}
func main() {
var tags = []string{
"rock", "jazz", "rock", "pop", "pop"}
fmt.Println(exclude("rock", tags))
}
With the result similar as below slices
:
$ go run 08-exclude.go
[jazz pop pop]
Loop for Unique
Based on
unique :: [String] -> [String]
unique [] = []
unique (head:tail) = head:unique(exclude head tail)
We can rewrite threeliners above to this below:
package main
import "fmt"
func exclude(value string, tags []string) []string {
…
}
func unique(tags []string) []string {
if len(tags) <= 1 {
return tags
} else {
head := tags[0]
tail := tags[1:len(tags)]
newHeads := []string{head}
newTails := unique(exclude(head, tail))
return append(newHeads, newTails...)
}
}
func main() {
var tags = []string{
"rock", "jazz", "rock", "pop", "pop"}
fmt.Println(unique(tags))
}
With the result similar as below slices
:
$ go run 09-unique.go
[rock jazz pop]
The Recursive
Based on
… = head:unique(exclude head tail)
Transformed into
head := tags[0]
tail := tags[1:len(tags)]
newHeads := []string{head}
newTails := unique(exclude(head, tail))
return append(newHeads, newTails...)
I guess the code itself is self explanatory.
myutils Package
The code can be shown as below:
package myutils
func exclude(value string, tags []string) []string {
…
}
func Unique(tags []string) []string {
…
}
I keep the exclude
function with lowercase,
while changing unique
function to uppercase.
Configuration
The go.mod
file is simply as below setting
module mysongs
go 1.15
And 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
replace example.com/myutils => ./myutils
require example.com/myutils v1.0.0
Clean Up
Now we can have a short and tidy code.
package main
import (
"example.com/mysongs"
"example.com/myutils"
"fmt"
)
func main() {
var tags []string
tags = mysongs.GetSongs().FlattenTags()
tags = myutils.Unique(tags)
fmt.Println(tags)
}
With the result similar as below slices
:
$ go run 10-cleanup.go
[60s jazz rock 70s pop]
Now you can rewrite your own pattern matching algorithm to go
.
5: Concurrency using Goroutine
Before we make a complex goroutine,
we can utilize previous flatten example,
as a show case to pass data between goroutine
.
Passing by Reference
To use defer
with array of string,
I need to explain pointer
a bit.
When we run a goroutine, we need to pass variable as parameter argument in function. In order to change the value of the parameter , we need to pass it by reference.
func flatten(songs []mysongs.Song, tags *[]string) {
…
}
And change the value later by uisng pointer.
*tags = append(*tags, tag)
The complete code is as below:
package main
import (
"example.com/mysongs"
"fmt"
)
func flatten(songs []mysongs.Song, tags *[]string) {
for _, song := range songs {
if song.Tags != nil {
for _, tag := range song.Tags {
*tags = append(*tags, tag)
}
}
}
}
func main() {
var tags []string
flatten(mysongs.GetSongs(), &tags)
fmt.Println(tags)
}
_.
With the result similar as below slices
:
$ go run 11-byref.go
[60s jazz 60s rock 70s rock 70s pop]
Waitgroup
Following the official documentation,
we can apply waitgroup
for this situation.
Consider start with defer
.
package main
import (
"example.com/mysongs"
"fmt"
"sync"
)
func flatten(wg *sync.WaitGroup,
songs []mysongs.Song, tags *[]string) {
defer wg.Done()
for _, song := range songs {
if song.Tags != nil {
for _, tag := range song.Tags {
*tags = append(*tags, tag)
}
}
}
}
And apply the waitgroup
pattern` in main function:
func main() {
wg := &sync.WaitGroup{}
var tags []string
wg.Add(1)
go flatten(wg, mysongs.GetSongs(), &tags)
wg.Wait()
fmt.Println(tags)
}
With exactly the same result as previous slices
:
$ go run 12-defer.go
[60s jazz 60s rock 70s rock 70s pop]
Passing Data Between Channel
We need to create two functions.
The flatten
itself, and other function to walk
the result.
The flatten
function is very similar as previous code.
func flatten(message chan string, quit chan int) {
songs := mysongs.GetSongs()
for _, song := range songs {
if song.Tags != nil {
for _, tag := range song.Tags {
message <- tag
}
}
}
quit <- 0
}
_.
On the other hand, the walk
function is,
following the example from official documentation.
func walk(message chan string, quit chan int) {
var tags []string
var tag string
for {
select {
case tag = <-message:
tags = append(tags, tag)
case <-quit:
fmt.Println(tags)
return
}
}
}
Now we can call both functions, in main
.
package main
import (
"example.com/mysongs"
"fmt"
)
func walk(message chan string, quit chan int) {…}
func flatten(message chan string, quit chan int) {…}
func main() {
message := make(chan string)
quit := make(chan int)
go flatten(message, quit)
walk(message, quit)
}
With exactly the same result as previous slices
:
$ go run 13-channel.go
[60s jazz 60s rock 70s rock 70s pop]
What is Next 🤔?
There is however other way to make our life simpler.
We can query using Koazee
, go-linq
or go-funk
.
Consider continue reading [ Go - Playing with Records - Part Three ].