cli  
Article Series

This article series discuss more than 30 different programming languages. Please read overview before you read any of the details.

Playing with Records Related Articles.

Where to Discuss?

Local Group

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]}

Go: The Song Record Structure

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},
	}
}

Go: The Songs Module

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.

Go: Using Songs Module


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]

Go: Finishing The Task


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 ].