Repository: vaskoz/dailycodingproblem-go (go)

Issue:
Day 300
Good morning! Here's your coding interview problem for today.

This problem was asked by Uber.

On election day, a voting machine writes data in the form `(voter_id, candidate_id)` to a text file. Write a program that reads this file as a stream and returns the top 3 candidates at any given time. If you find a voter voting more than once, report this as fraud.

Pull request:
Day300
Resolves #612.

Patch:
diff --git a/README.md b/README.md
index aaf7a1a..ddbc35c 100644
--- a/README.md
+++ b/README.md
@@ -307,3 +307,4 @@ problems from
 * [Day 297](https://github.com/vaskoz/dailycodingproblem-go/issues/607)
 * [Day 298](https://github.com/vaskoz/dailycodingproblem-go/issues/609)
 * [Day 299](https://github.com/vaskoz/dailycodingproblem-go/issues/611)
+* [Day 300](https://github.com/vaskoz/dailycodingproblem-go/issues/612)
diff --git a/day300/problem.go b/day300/problem.go
new file mode 100644
index 0000000..e846f92
--- /dev/null
+++ b/day300/problem.go
@@ -0,0 +1,61 @@
+package day300
+
+import (
+	"fmt"
+	"io"
+	"os"
+	"sort"
+)
+
+var (
+	out io.Writer = os.Stdout // nolint
+	in  io.Reader = os.Stdin  // nolint
+)
+
+// VotingResult is the number of votes a candidate received.
+type VotingResult struct {
+	Candidate, Votes int
+}
+
+func main() {
+	var voterID, candidateID int
+	voters := make(map[int]struct{})
+	candidates := make(map[int]*VotingResult)
+	top3 := make([]*VotingResult, 0, 3)
+	_, err := fmt.Fscanf(in, "%d,%d", &voterID, &candidateID)
+	for err != io.EOF {
+		if _, voted := voters[voterID]; voted {
+			fmt.Fprintf(out, "voter %d already voted. this vote for candidate %d will not count\n", voterID, candidateID)
+		} else {
+			voters[voterID] = struct{}{}
+			if cand, exists := candidates[candidateID]; !exists {
+				candidates[candidateID] = &VotingResult{Candidate: candidateID, Votes: 1}
+				if len(top3) < 3 {
+					top3 = append(top3, candidates[candidateID])
+				}
+			} else {
+				cand.Votes++
+				if cand.Votes > top3[0].Votes && !Contains(top3, candidates[candidateID]) {
+					top3[0] = cand
+				}
+			}
+			sort.Slice(top3, func(i, j int) bool {
+				return top3[i].Votes < top3[j].Votes
+			})
+			for _, v := range top3 {
+				fmt.Fprintf(out, "%d,%v ", v.Candidate, v.Votes)
+			}
+			fmt.Fprintln(out)
+		}
+		_, err = fmt.Fscanf(in, "%d,%d", &voterID, &candidateID)
+	}
+}
+
+func Contains(top3 []*VotingResult, cand *VotingResult) bool {
+	for _, c := range top3 {
+		if cand == c {
+			return true
+		}
+	}
+	return false
+}


Test patch:
diff --git a/day300/problem_test.go b/day300/problem_test.go
new file mode 100644
index 0000000..8640b39
--- /dev/null
+++ b/day300/problem_test.go
@@ -0,0 +1,42 @@
+package day300
+
+import (
+	"bytes"
+	"strings"
+	"testing"
+)
+
+// nolint
+var testcases = []struct {
+	input, output string
+}{
+	{"1,1 2,2 3,3 4,3 5,3", "1,1 \n1,1 2,1 \n1,1 2,1 3,1 \n1,1 2,1 3,2 \n1,1 2,1 3,3 \n"},
+	{"1,1 2,2 3,3 1,3", "1,1 \n1,1 2,1 \n1,1 2,1 3,1 \nvoter 1 already voted. this vote for candidate 3 will not count\n"},
+	{"1,1 2,2 3,3 4,4 5,4",
+		"1,1 \n1,1 2,1 \n1,1 2,1 3,1 \n1,1 2,1 3,1 \n2,1 3,1 4,2 \n"},
+}
+
+func TestMainVoterTop3(t *testing.T) {
+	// don't run in parallel due to global var
+	for _, tc := range testcases {
+		buff := new(bytes.Buffer)
+		out = buff
+		in = strings.NewReader(tc.input)
+		main()
+		if result := buff.String(); result != tc.output {
+			t.Errorf("Expected \n%s, got \n%s", tc.output, result)
+		}
+	}
+}
+
+func BenchmarkMainVoterTop3(b *testing.B) {
+	// don't run in parallel due to global var
+	buff := new(bytes.Buffer)
+	out = buff
+	for i := 0; i < b.N; i++ {
+		for _, tc := range testcases {
+			in = strings.NewReader(tc.input)
+			main()
+		}
+	}
+}