3
\$\begingroup\$

the question is simple, for a input [apple, banana, orange, apple, banana, apple], the program will count it as a map: {apple : 3, orange: 1, banana: 2}, then sort this map by it's values, get [(apple, 3), (banana, 2), (orange, 1)] below is my go version, I'm a go newbie, could you please review it and lecture me how to polish the code? I add a python version for reference

package main

//input, a url.log file, like "apple\nbanana\norange\napple\nbanana\napple\n"
//output, a output.txt file, should be "apple: 3\nbanana: 2\norange: 1\n"
import (
    "fmt"
    "bufio"
    "os"
    "sort"
    "strings"
    "io"
)

func main() {
    m := make(map[string]int)

    // read file line by line
    filename := "url.log"
    f, err := os.Open(filename)
    if err != nil {
        fmt.Println(err)
        return
    }
    defer f.Close()
    r := bufio.NewReader(f)
    for line, err := r.ReadString('\n'); err == nil; {
        url := strings.TrimRight(line, "\n")
        m[url] += 1  // build the map
        line, err = r.ReadString('\n')
    }

    if err != nil {
        fmt.Println(err)
        return
    }

    //sort the map by it's value
    vs := NewValSorter(m)
    vs.Sort()

    // output
    f_out, _ := os.Create("output.go.txt") 
    defer f_out.Close()
    var line string
    for i, k := range vs.Keys {
        line = fmt.Sprintf("%s: %d\n", k, vs.Vals[i])
        io.WriteString(f_out, line)
    }

}

type ValSorter struct {
    Keys []string
    Vals []int
}

func NewValSorter(m map[string]int) *ValSorter {
    vs := &ValSorter {
        Keys: make([]string, 0, len(m)),
        Vals: make([]int, 0, len(m)),
    }
    for k, v := range m {
        vs.Keys = append(vs.Keys, k)
        vs.Vals = append(vs.Vals, v)
    }
    return vs
}

func (vs *ValSorter) Sort() {
    sort.Sort(vs)
}

func (vs *ValSorter) Len() int {
    return len(vs.Vals)
}

func (vs *ValSorter) Less(i, j int) bool {
    return vs.Vals[i] > vs.Vals[j]
}

func (vs *ValSorter) Swap(i, j int) {
    vs.Vals[i], vs.Vals[j] = vs.Vals[j], vs.Vals[i]
    vs.Keys[i], vs.Keys[j] = vs.Keys[j], vs.Keys[i]
}

python version:

from collections import Counter

def main():
    count = Counter()
    with open("url.log", "rb") as f:
        for line in f:
            url = line.rstrip()
            count[url] += 1
    with open("output.py.txt", "wb") as f:
        for key, value in count.most_common():
            f.write("%s: %d\n" %(key, value))


if __name__ == "__main__":
    main()
\$\endgroup\$

2 Answers 2

4
\$\begingroup\$

In Go, I would write:

package main

import (
    "bufio"
    "fmt"
    "io"
    "os"
    "sort"
)

type Key string
type Count int

type Elem struct {
    Key
    Count
}

type Elems []*Elem

func (s Elems) Len() int {
    return len(s)
}

func (s Elems) Swap(i, j int) {
    s[i], s[j] = s[j], s[i]
}

type ByReverseCount struct{ Elems }

func (s ByReverseCount) Less(i, j int) bool {
    return s.Elems[j].Count < s.Elems[i].Count
}

func main() {
    m := make(map[Key]Count)
    fi, err := os.Open("keys.txt")
    if err != nil {
        fmt.Println(err)
        return
    }
    defer fi.Close()
    r := bufio.NewReader(fi)
    for {
        line, err := r.ReadString('\n')
        if err != nil {
            if err == io.EOF && len(line) == 0 {
                break
            }
            fmt.Println(err)
            return
        }
        key := line[:len(line)-1]
        m[Key(key)] += 1
    }
    fi.Close()

    e := make(Elems, 0, len(m))
    for k, c := range m {
        e = append(e, &Elem{k, c})
    }
    sort.Sort(ByReverseCount{e})

    fo, err := os.Create("counts.txt")
    if err != nil {
        fmt.Println(err)
        return
    }
    defer fo.Close()
    for _, e := range e {
        line := fmt.Sprintf("%s: %d\n", e.Key, e.Count)
        fo.WriteString(line)
    }
}

Input (keys.txt):

apple
banana
orange
apple
banana
apple

Output (counts.txt):

apple: 3
banana: 2
orange: 1
\$\endgroup\$
0
3
\$\begingroup\$

Here's another approach using ioutils, which has some shortcuts for reading/writing files (assuming they are short enough to fit into memory). If you're writing a quick util, this might be easier than dealing with bufio in many cases. I've kept the sorting simple and in one function.

package main

import (
    "fmt"
    "io/ioutil"
    "sort"
    "strings"
)

func main() {
    // Read the input file
    input := "./input.txt"
    input_text, err := ioutil.ReadFile(input)
    if err != nil {
        panic(err)
    }

    // Store line counts
    lines := strings.Split(string(input_text), "\n")
    counts := map[string]int{}
    for _, l := range lines {
        counts[l] += 1
    }

    // Get keys sorted
    keys := []string{}
    for k, _ := range counts {
        keys = append(keys, k)
    }
    sort.Strings(keys)

    // Output sorted counts using ordered keys
    output_text := ""
    for _, k := range keys {
        output_text += fmt.Sprintf("%s : %d\n", k, counts[k])
    }

    // Write results to a file
    output := "./output.txt"
    err = ioutil.WriteFile(output, []byte(output_text), 0644)
    if err != nil {
        panic(err)
    }
    fmt.Print("File written\n", output_text)
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.