Repository: vaskoz/dailycodingproblem-go (go)

Issue:
Day 358
Good morning! Here's your coding interview problem for today.

This problem was asked by Dropbox.

Create a data structure that performs all the following operations in `O(1)` time:

* `plus`: Add a key with value `1`. If the key already exists, increment its value by one.
* `minus`: Decrement the value of a key. If the key's value is currently `1`, remove it.
* `get_max`: Return a key with the highest value.
* `get_min`: Return a key with the lowest value.

Pull request:
Day358
Resolves #707.

Patch:
diff --git a/README.md b/README.md
index 01ad874..1f83179 100644
--- a/README.md
+++ b/README.md
@@ -362,6 +362,7 @@ problems from
 * [Day 355](https://github.com/vaskoz/dailycodingproblem-go/issues/704)
 * [Day 356](https://github.com/vaskoz/dailycodingproblem-go/issues/705)
 * [Day 357](https://github.com/vaskoz/dailycodingproblem-go/issues/706)
+* [Day 358](https://github.com/vaskoz/dailycodingproblem-go/issues/707)
 * [Day 359](https://github.com/vaskoz/dailycodingproblem-go/issues/708)
 * [Day 360](https://github.com/vaskoz/dailycodingproblem-go/issues/709)
 * [Day 361](https://github.com/vaskoz/dailycodingproblem-go/issues/712)
diff --git a/day358/problem.go b/day358/problem.go
new file mode 100644
index 0000000..150f4bc
--- /dev/null
+++ b/day358/problem.go
@@ -0,0 +1,152 @@
+package day358
+
+import "container/list"
+
+// BigOhOne is a data structure that performs all operations
+// in O(1) time: constant time operations.
+type BigOhOne interface {
+
+	// Plus adds a key with value 1.
+	// If the key already exists, increment its value by one.
+	Plus(key interface{})
+
+	// Minus decrements the value of a key.
+	// If the key's value is currently 1, remove it.
+	Minus(key interface{})
+
+	// Max returns the key with the highest value.
+	Max() (interface{}, int)
+
+	// Min returns the key with the lowest value.
+	Min() (interface{}, int)
+}
+
+type bigOhOne struct {
+	entryList         *list.List
+	lookupEntryByKey  map[interface{}]*list.Element
+	lookupListByCount map[int]*list.Element
+}
+
+type entry struct {
+	count       int
+	values      *list.List
+	lookupByKey map[interface{}]*list.Element
+}
+
+func (bo *bigOhOne) Plus(key interface{}) {
+	eSource := bo.lookupEntryByKey[key]
+
+	if eSource == nil {
+		vals := list.New()
+		lookup := make(map[interface{}]*list.Element)
+		lookup[key] = vals.PushFront(key)
+
+		newEntry := &entry{
+			count:       0,
+			values:      vals,
+			lookupByKey: lookup,
+		}
+		eSource = bo.entryList.PushFront(newEntry)
+	}
+
+	sourceEntry := eSource.Value.(*entry)
+
+	eTarget := bo.lookupListByCount[sourceEntry.count+1]
+
+	if eTarget == nil {
+		newEntry := &entry{
+			count:       sourceEntry.count + 1,
+			values:      list.New(),
+			lookupByKey: make(map[interface{}]*list.Element),
+		}
+		eTarget = bo.entryList.InsertAfter(newEntry, eSource)
+	}
+
+	targetEntry := eTarget.Value.(*entry)
+
+	bo.lookupEntryByKey[key] = eTarget
+	bo.lookupListByCount[targetEntry.count] = eTarget
+	targetEntry.lookupByKey[key] = targetEntry.values.PushFront(key)
+
+	sourceEntryKey := sourceEntry.lookupByKey[key]
+	sourceEntry.values.Remove(sourceEntryKey)
+	delete(sourceEntry.lookupByKey, key)
+
+	if len(sourceEntry.lookupByKey) == 0 {
+		delete(bo.lookupListByCount, sourceEntry.count)
+		bo.entryList.Remove(eSource)
+	}
+}
+
+func (bo *bigOhOne) Minus(key interface{}) {
+	eSource := bo.lookupEntryByKey[key]
+
+	if eSource == nil {
+		return
+	}
+
+	sourceEntry := eSource.Value.(*entry)
+
+	eTarget := bo.lookupListByCount[sourceEntry.count-1]
+
+	if eTarget == nil {
+		newEntry := &entry{
+			count:       sourceEntry.count - 1,
+			values:      list.New(),
+			lookupByKey: make(map[interface{}]*list.Element),
+		}
+		eTarget = bo.entryList.InsertBefore(newEntry, eSource)
+	}
+
+	targetEntry := eTarget.Value.(*entry)
+
+	bo.lookupEntryByKey[key] = eTarget
+	bo.lookupListByCount[targetEntry.count] = eTarget
+	targetEntry.lookupByKey[key] = targetEntry.values.PushFront(key)
+
+	sourceEntryKey := sourceEntry.lookupByKey[key]
+	sourceEntry.values.Remove(sourceEntryKey)
+	delete(sourceEntry.lookupByKey, key)
+
+	if len(sourceEntry.lookupByKey) == 0 {
+		delete(bo.lookupListByCount, sourceEntry.count)
+		bo.entryList.Remove(eSource)
+	}
+
+	if targetEntry.count == 0 {
+		delete(bo.lookupEntryByKey, key)
+		delete(bo.lookupListByCount, targetEntry.count)
+		bo.entryList.Remove(eTarget)
+	}
+}
+
+func (bo *bigOhOne) Max() (interface{}, int) {
+	if bo.entryList.Len() == 0 {
+		return nil, 0
+	}
+
+	back := bo.entryList.Back().Value.(*entry)
+	val := back.values.Back().Value
+
+	return val, back.count
+}
+
+func (bo *bigOhOne) Min() (interface{}, int) {
+	if bo.entryList.Len() == 0 {
+		return nil, 0
+	}
+
+	front := bo.entryList.Front().Value.(*entry)
+	val := front.values.Front().Value
+
+	return val, front.count
+}
+
+// New returns a new instance of a BigOhOne data structure.
+func New() BigOhOne {
+	return &bigOhOne{
+		entryList:         list.New(),
+		lookupEntryByKey:  make(map[interface{}]*list.Element),
+		lookupListByCount: make(map[int]*list.Element),
+	}
+}


Test patch:
diff --git a/day358/problem_test.go b/day358/problem_test.go
new file mode 100644
index 0000000..6ea460c
--- /dev/null
+++ b/day358/problem_test.go
@@ -0,0 +1,84 @@
+package day358
+
+import "testing"
+
+// nolint
+var actions = []struct {
+	key    interface{}
+	action string
+	count  int
+}{
+	{nil, "max", 0},
+	{nil, "min", 0},
+	{"cat", "plus", 1},
+	{"dog", "plus", 1},
+	{"cat", "minus", 1},
+	{"dog", "minus", 1},
+	{"foo", "plus", 3},
+	{"bar", "plus", 4},
+	{"bar", "max", 4},
+	{"foo", "min", 3},
+	{"foo", "plus", 2},
+	{"foo", "max", 5},
+	{"bar", "min", 4},
+	{"foo", "minus", 3},
+	{"bar", "max", 4},
+	{"foo", "min", 2},
+	{"foo", "minus", 1},
+	{"bar", "max", 4},
+	{"foo", "min", 1},
+	{"foo", "minus", 1},
+	{"bar", "max", 4},
+	{"bar", "min", 4},
+	{"doesntexist", "minus", 4},
+}
+
+func TestBigOhOne(t *testing.T) {
+	t.Parallel()
+
+	bo := New()
+
+	for _, a := range actions {
+		switch a.action {
+		case "plus":
+			for i := 0; i < a.count; i++ {
+				bo.Plus(a.key)
+			}
+		case "minus":
+			for i := 0; i < a.count; i++ {
+				bo.Minus(a.key)
+			}
+		case "min":
+			if key, count := bo.Min(); key != a.key || count != a.count {
+				t.Errorf("Expected Min (%v,%v) got (%v,%v)", a.key, a.count, key, count)
+			}
+		case "max":
+			if key, count := bo.Max(); key != a.key || count != a.count {
+				t.Errorf("Expected Max (%v,%v) got (%v,%v)", a.key, a.count, key, count)
+			}
+		}
+	}
+}
+
+func BenchmarkBigOhOne(b *testing.B) {
+	bo := New()
+
+	for i := 0; i < b.N; i++ {
+		for _, a := range actions {
+			switch a.action {
+			case "plus":
+				for i := 0; i < a.count; i++ {
+					bo.Plus(a.key)
+				}
+			case "minus":
+				for i := 0; i < a.count; i++ {
+					bo.Minus(a.key)
+				}
+			case "min":
+				bo.Min()
+			case "max":
+				bo.Max()
+			}
+		}
+	}
+}