Video Coming Soon...

Created by Zed A. Shaw Updated 2025-11-14 00:08:54

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

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 sortView Source file go-coreutils/sort/main.go Only

package 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 uniqView Source file go-coreutils/uniq/main.go Only

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

Previous Lesson Next Lesson

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.