Go 001 Verify Benford's Law

Input

package main

//
// Verify Benford's Law
//
// Given a set of files, evaluate the number of occurences
// of ASCII digits 0 to 9 and the number of leading occurences
// (i.e. occurence as leading digit of a number) of ASCII
// digits 0 to 9.
// Prints the result to stdout.
//
// (C) 2016, Lukas Prokop, Public Domain
//

import (
    "fmt"
    "math/big"
    "os"
    "path/filepath"
)

var occ [10]*big.Int
var leading [10]*big.Int
var one *big.Int
var start bool

// init initializes all global variables to 0,
// except for one which becomes... well, 1.
func init() {
    for i := 0; i < 10; i++ {
        occ[i] = big.NewInt(0)
        leading[i] = big.NewInt(0)
    }
    one = big.NewInt(1)
    start = true
}

// printUsage prints CLI usage description to stdout
func printUsage() {
    fmt.Println("./benford [<filepath>]+")
    fmt.Println("  Verify Benford's Law for files of given filepaths.")
    fmt.Println("  if filepath is a directory, traverse it recursively.")
}

// printResult prints values of the global variables
// to stdout
func printResult() {
    fmt.Println("general occurence:")
    for i := 0; i < 10; i++ {
        fmt.Printf(" %d  %s\n", i, occ[i].Text(10))
    }
    fmt.Println("leading occurence:")
    for i := 0; i < 10; i++ {
        fmt.Printf(" %d  %s\n", i, leading[i].Text(10))
    }
}

// processBuffer takes a buffer, iterates ASCII values
// and if it's a digit, it updates the global variables
func processBuffer(buf []byte) {
    for _, by := range buf {
        if '0' <= by && by <= '9' {
            num := by - '0'
            occ[num].Add(occ[num], one)
            if start {
                leading[num].Add(leading[num], one)
                start = false
            }
        } else {
            start = true
        }
    }
}

// processFile takes an open file descriptor to read
// byte blocks from the file and passes it to processBuffer
func processFile(fd *os.File, filepath string) {
    buf := make([]byte, os.Getpagesize())
    for {
        size, err := (*fd).Read(buf)
        if err != nil && size == 0 {
            break
        }
        if err != nil {
            panic(err)
        }
        processBuffer(buf)
    }
}

// processDir implements the callback interface of filepath.Walk
// by taking a path and forwards (seemingly regular) files to
// processFile
func processDir(path string, info os.FileInfo, err error) error {
    if info.Mode()&os.ModeType != 0 {
        // non-regular file
        fmt.Printf("Non-regular file \"%s\" skipped\n", path)
        return nil
    } else {
        fd, err := os.Open(path)
        if err != nil {
            return err
        }
        processFile(fd, path)
        fd.Close()
    }
    return nil
}

// main implements the main routine
func main() {
    // help required?
    medic := false
    for _, a := range os.Args {
        if a == "-h" || a == "--help" {
            medic = true
        }
    }

    // invalid usage or help? show usage
    if len(os.Args) < 2 || medic {
        printUsage()
        os.Exit(1)
    }

    // for every argument passed in, process it
    for _, arg := range os.Args[1:] {
        if arg[0] == '-' {
            panic(fmt.Sprintf("Unknown option %s", arg))
        }
        fd, err := os.Open(arg)
        if err != nil {
            panic(err)
        }
        fi, err := fd.Stat()
        switch {
        case err != nil:
            panic(err)
        case fi.IsDir():
            err := filepath.Walk(arg, processDir)
            if err != nil {
                panic(err)
            }
        default:
            processFile(fd, arg)
        }
        fd.Close()
    }

    // print global variables values
    printResult()
}