Video Coming Soon...
40: sort, and uniq
The sort and uniq commands go together like peanut butter and chocolate. The sort command takes its input and sorts it, then writes the sorted version to the output. The uniq command takes a sorted input stream and prints only the unique elements.
Can you think of a reason why uniq only works on sorted input?
The Challenge
When implementing sort you'll have to implement the -f (ignore case) and -n (numeric) options. You'll also be required to use the slices package. The documentation is here:
For uniq you'll also be required to implement the -i (ignore case) and c (count occurrence). If your input is sorted then uniq doesn't need the slices package. The documentation for uniq is:
You can implement a version of uniq that doesn't need sorted input, but that version would need to use memory to remember what lines its seen and...it's very complicated.
Requirements
See the list of requirements
- bufio -- https://pkg.go.dev/bufio
- flag -- https://pkg.go.dev/flag
- fmt -- https://pkg.go.dev/fmt
- os -- https://pkg.go.dev/os
- slices -- https://pkg.go.dev/slices
- strconv -- https://pkg.go.dev/strconv
- strings -- https://pkg.go.dev/strings
The sort Code
You know the drill. I'm a lazy man who only makes versions of these tools good enough to get you going.
See my first version of sort
View Source file go-coreutils/sort/main.go Onlypackage main
import (
"fmt"
"bufio"
"os"
"slices"
"flag"
"strings"
"strconv"
)
type Opts struct {
IgnoreCase bool
Numeric bool
}
func ParseOpts() Opts {
var opts Opts
flag.BoolVar(&opts.IgnoreCase, "f", false, "ignore case")
flag.BoolVar(&opts.Numeric, "n", false, "numeric sort")
flag.Parse()
return opts
}
func NumericSort(a string, b string) int {
a_int, a_err := strconv.Atoi(a)
b_int, b_err := strconv.Atoi(b)
if a_err != nil || b_err != nil {
return strings.Compare(a, b)
} else {
return a_int - b_int
}
}
func IgnoreCase(a string, b string) int {
return strings.Compare(strings.ToLower(a), strings.ToLower(b))
}
func main() {
lines := make([]string, 0, 100)
opts := ParseOpts()
scan := bufio.NewScanner(os.Stdin)
for scan.Scan() {
line := scan.Text()
lines = append(lines, line)
}
if opts.Numeric {
slices.SortFunc(lines, NumericSort)
} else if opts.IgnoreCase{
slices.SortFunc(lines, IgnoreCase)
} else {
slices.Sort(lines)
}
for _, line := range lines {
fmt.Println(line)
}
}
The uniq Code
Did you figure out why uniq needs sorted input?
See my first version of uniq
View Source file go-coreutils/uniq/main.go Onlypackage main
import (
"fmt"
"os"
"bufio"
"strings"
"flag"
)
type Opts struct {
IgnoreCase bool
Count bool
}
func ParseOpts() Opts {
var opts Opts
flag.BoolVar(&opts.IgnoreCase, "i", false, "ignore case")
flag.BoolVar(&opts.Count, "c", false, "count occurrence")
flag.Parse()
return opts
}
func StringEqual(a string, b string, ignore_case bool) bool {
if ignore_case {
a = strings.ToLower(a)
b = strings.ToLower(b)
}
return strings.Compare(a, b) == 0
}
func main() {
scan := bufio.NewScanner(os.Stdin)
seen_line := ""
seen_count := 0
opts := ParseOpts()
for scan.Scan() {
line := scan.Text()
if !StringEqual(line, seen_line, opts.IgnoreCase) {
if opts.Count {
fmt.Print(seen_count, " ")
}
fmt.Println(line)
seen_line = line
seen_count = 0
} else {
seen_count++
}
}
}
Testing It
If you want to push your learning further then you can try to implement an automated test for this. I actually need to learn how to test utilities like this with Go, so for now just consider this an extra challenge for later until I learn how to teach it.
Register for Learn Go the Hard Way
Register today for the course and get the all currently available videos and lessons, plus all future modules for no extra charge.