-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.go
231 lines (187 loc) · 5.7 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
package main
// This program can be used to output the N largest integers from a file or
// from stdin. It assumes that there is one integer per line. If the line
// cannot be converted to an integer, it will be skipped. The integers are
// printed on a single line in descending order.
//
// Both N and the file can be specified as command-line parameters. If the
// file option is not specified, the program defaults to reading from stdin.
// If the n option is not specified, the program defaults to a small integer.
//
// The program uses a backing min-heap to store the highest N values while
// scanning numbers. The heap is first filled with N elements, values that
// are less than the minimum element in the initial heap are discarded, and
// every scanned integer that is higher than the minimum element replaces
// the minimum element in the heap. By the end of the scan, only the N largest
// integers remain in the heap.
//
// If any errors occur during execution, the program will exit with exit code 1.
//
// Example file usage:
// go build -o topn
// topn -file=./data -n=15
// 1000 999 998 997 996 995 994 993 992 991 990 989 988 987 986
//
// Example stdin usage:
// $ go build -o topn
// $ for i in {0..1000}; do echo $i; done | ./topn -n=15
// 1000 999 998 997 996 995 994 993 992 991 990 989 988 987 986
import (
"bufio"
"container/heap"
"errors"
"flag"
"fmt"
"log"
"os"
"strconv"
)
const (
defaultFileName = ""
defaultN = 5
)
var errLogger = log.New(os.Stderr, "ERROR: ", log.Ltime)
// DRIVER FUNCTION
// Runs top N program.
func main() {
fileFlag, nFlag := setupFlags()
// Setup scanner - if file flag has not been provided
// read from stdin.
var numberScanner *bufio.Scanner
if *fileFlag == defaultFileName {
numberScanner = bufio.NewScanner(os.Stdin)
} else {
dataFile, err := os.Open(*fileFlag)
if err != nil {
errLogger.Fatalf("Failed to open file - %s", err)
}
defer dataFile.Close()
numberScanner = bufio.NewScanner(dataFile)
}
// Build min-heap from scanning list of numbers
numberHeap, err := buildHeap(numberScanner, nFlag)
if err != nil {
errLogger.Fatalf("Failed to scan numbers - %s", err)
}
// Take the top N integers from the heap and print
// them in descending order.
numbers := takeTopN(numberHeap, nFlag)
printNumbers(numbers)
}
// PROGRAM SETUP FUNCTIONS
// Sets up flags to be used as command-line options
func setupFlags() (*string, *uint) {
var fileFlag = flag.String("file", defaultFileName, "file to read")
var nFlag = flag.Uint("n", defaultN, "amount of numbers to select")
flag.Parse()
return fileFlag, nFlag
}
// PROGRAM ALGORITHM FUNCTIONS
// Scans numbers with number scanner and builds min-heap. Returns a fully
// constructed min-heap if no error occurred during scan. Otherwise, returns
// partially constructed min-heap and error.
func buildHeap(numberScanner *bufio.Scanner, nFlag *uint) (*TopHeap, error) {
topHeap := NewTopHeap(*nFlag)
heap.Init(topHeap)
if *nFlag == 0 {
return topHeap, nil
}
for numberScanner.Scan() {
// Skip lines that can't be converted to ints
value, err := strconv.Atoi(numberScanner.Text())
if err != nil {
continue
}
// Fill up the heap until n-elements have been added.
if topHeap.Len() < int(*nFlag) {
heap.Push(topHeap, value)
continue
}
// If the value is less than the minimum, we don't need to
// add it to the heap. We only want the N-highest.
minimum, err := topHeap.Minimum()
if err != nil || value < minimum {
continue
}
// If we've got a value that's higher than the minimum, make
// room for the new value by replacing the minimum.
topHeap.ReplaceMin(value)
}
return topHeap, numberScanner.Err()
}
// Selects largest N numbers by popping them off the heap.
func takeTopN(topHeap *TopHeap, nFlag *uint) []int {
if topHeap.Len() == 0 {
return []int{}
}
selection := make([]int, 0)
for i := uint(0); i < *nFlag && topHeap.Len() > 0; i++ {
selection = append(selection, heap.Pop(topHeap).(int))
}
return selection
}
// PROGRAM OUTPUT FUNCTIONS
// Prints numbers in on line, highest first. numbers are expected to be in
// ascending order.
func printNumbers(numbers []int) {
// Start from the end of numbers in order to print highest first.
for i := len(numbers) - 1; i >= 0; i-- {
number := numbers[i]
// Don't print trailing whitespace if last number in range.
if i == 0 {
fmt.Printf("%d", number)
continue
}
fmt.Printf("%d ", number)
}
fmt.Println()
}
// SUPPORTING DATA STRUCTURE
// This is a 'no frills' min-heap implementation. Most of this code was taken
// from the min-heap example on http://golang.org. It suited my needs exactly.
// I did add a couple of convenience functions like the constructor function
// (NewTopHeap) and `ReplaceMin`.
type IntHeap []int
type TopHeap struct {
IntHeap
}
// Creates new TopHeap instance.
func NewTopHeap(n uint) *TopHeap {
return &TopHeap{}
}
// Returns number of elements in heap.
func (h IntHeap) Len() int {
return len(h)
}
// Compares heap elements.
func (h IntHeap) Less(i, j int) bool {
return h[i] < h[j]
}
// Swaps elements within the heap.
func (h IntHeap) Swap(i, j int) {
h[i], h[j] = h[j], h[i]
}
// Adds an element to the heap.
func (h *IntHeap) Push(x interface{}) {
*h = append(*h, x.(int))
}
// Removes and returns the minimum element from the heap.
func (h *IntHeap) Pop() interface{} {
old := *h
n := len(old)
x := old[n-1]
*h = old[0 : n-1]
return x
}
// Replaces the minimum element in the heap with the provided value
func (h *TopHeap) ReplaceMin(value interface{}) {
heap.Pop(h)
heap.Push(h, value)
}
// Returns minimum element of the heap
func (h *TopHeap) Minimum() (int, error) {
if h.Len() == 0 {
return 0, errors.New("Heap is empty")
}
return h.IntHeap[0], nil
}