Repository: vaskoz/dailycodingproblem-go
Pull request #955 (2020-09-03 20:00:00)

Day370
Completed the problem, however, the given test cases seem to be incorrect.


Patch:
diff --git a/README.md b/README.md
index 0d44231..b45b9db 100644
--- a/README.md
+++ b/README.md
@@ -372,7 +372,8 @@ problems from
 * [Day 367](https://github.com/vaskoz/dailycodingproblem-go/issues/740)
 * [Day 368](https://github.com/vaskoz/dailycodingproblem-go/issues/743)
 * [Day 369](https://github.com/vaskoz/dailycodingproblem-go/issues/745)
-* Day 370 - Never received an e-mail for this day.
+* [Day 370](https://github.com/vaskoz/dailycodingproblem-go/issues/954)
+    - Thanks to @Birdy-C for providing me the problem.
 * [Day 371](https://github.com/vaskoz/dailycodingproblem-go/issues/750)
 * [Day 372](https://github.com/vaskoz/dailycodingproblem-go/issues/748)
 * [Day 373](https://github.com/vaskoz/dailycodingproblem-go/issues/752)
diff --git a/day370/problem.go b/day370/problem.go
new file mode 100644
index 0000000..b128283
--- /dev/null
+++ b/day370/problem.go
@@ -0,0 +1,74 @@
+package day370
+
+import (
+	"log"
+	"sort"
+)
+
+// DeliveryType enumerates either a pickup or dropoff delivery.
+type DeliveryType string
+
+const (
+	Pickup  = "pickup"
+	Dropoff = "dropoff"
+)
+
+// DeliveryAction represents the data supplied to the algorithm.
+type DeliveryAction struct {
+	DeliveryID uint
+	Timestamp  uint
+	Type       DeliveryType
+}
+
+// CourierActiveTime returns the number of seconds the courier was active
+// in delivering packages.
+//
+// Function panics if bad data is discovered e.g. mismatch of dropoff and pickups.
+// Runtime is O(N log N) due to sort.
+func CourierActiveTime(data []DeliveryAction) uint {
+	if len(data) == 0 {
+		return 0
+	}
+
+	sort.Slice(data, func(i, j int) bool {
+		return data[i].Timestamp < data[j].Timestamp
+	})
+
+	var answer, startActiveTime uint
+
+	outForDelivery := make(map[uint]DeliveryAction)
+	d := data[0]
+
+	if d.Type != Pickup {
+		log.Panicf("mismatched. can't have first chronological item as Dropoff. %v", d)
+	}
+
+	startActiveTime = d.Timestamp
+	outForDelivery[d.DeliveryID] = d
+
+	for _, d := range data[1:] {
+		switch d.Type {
+		case Pickup:
+			if len(outForDelivery) == 0 {
+				startActiveTime = d.Timestamp
+			} else if _, found := outForDelivery[d.DeliveryID]; found {
+				log.Panicf("duplicate pickup exists. %v", d)
+			}
+
+			outForDelivery[d.DeliveryID] = d
+		case Dropoff:
+			if _, found := outForDelivery[d.DeliveryID]; !found {
+				log.Panicf("mismatched. can't have Dropoff without preceding Pickup. %v", d)
+			} else {
+				delete(outForDelivery, d.DeliveryID)
+				if len(outForDelivery) == 0 {
+					answer += (d.Timestamp - startActiveTime)
+				}
+			}
+		default:
+			log.Panicf("invalid DeliveryType. %v", d)
+		}
+	}
+
+	return answer
+}


Test patch:
diff --git a/day370/problem_test.go b/day370/problem_test.go
new file mode 100644
index 0000000..0ba085a
--- /dev/null
+++ b/day370/problem_test.go
@@ -0,0 +1,126 @@
+package day370
+
+import (
+	"testing"
+)
+
+// nolint
+var testcases = []struct {
+	data       []DeliveryAction
+	activeTime uint
+}{
+	{nil, 0},
+	{
+		[]DeliveryAction{ // overlapping deliveries
+			{1, 10, DeliveryType("pickup")},
+			{1, 20, DeliveryType("dropoff")},
+			{2, 15, DeliveryType("pickup")},
+			{3, 17, DeliveryType("pickup")},
+			{3, 22, DeliveryType("dropoff")},
+			{2, 30, DeliveryType("dropoff")},
+		},
+		20,
+	},
+	{
+		[]DeliveryAction{ // non-overlapping deliveries
+			{1, 10, DeliveryType("pickup")},
+			{1, 20, DeliveryType("dropoff")},
+			{2, 40, DeliveryType("pickup")},
+			{3, 100, DeliveryType("pickup")},
+			{3, 110, DeliveryType("dropoff")},
+			{2, 50, DeliveryType("dropoff")},
+		},
+		30,
+	},
+}
+
+func TestCourierActiveTime(t *testing.T) {
+	t.Parallel()
+
+	for _, tc := range testcases {
+		if activeTime := CourierActiveTime(tc.data); activeTime != tc.activeTime {
+			t.Errorf("Expected %d, got %d", tc.activeTime, activeTime)
+		}
+	}
+}
+
+func TestCourierActiveTimeDropOffBeforePickup(t *testing.T) {
+	t.Parallel()
+
+	data := []DeliveryAction{
+		{1, 1573280047, DeliveryType("pickup")},
+		{1, 1570320725, DeliveryType("dropoff")},
+		{2, 1570321092, DeliveryType("pickup")},
+		{3, 1570321212, DeliveryType("pickup")},
+		{3, 1570322352, DeliveryType("dropoff")},
+		{2, 1570323012, DeliveryType("dropoff")},
+	}
+
+	defer func() {
+		if err := recover(); err == nil {
+			t.Errorf("Expected a panic due to the first event by time being a Dropoff")
+		}
+	}()
+
+	CourierActiveTime(data)
+}
+
+func TestCourierActiveTimeDuplicatePickup(t *testing.T) {
+	t.Parallel()
+
+	data := []DeliveryAction{
+		{3, 30, DeliveryType("pickup")},
+		{3, 40, DeliveryType("pickup")},
+	}
+
+	defer func() {
+		if err := recover(); err == nil {
+			t.Errorf("Expected a panic due to a duplicate pickup for the same delivery ID")
+		}
+	}()
+
+	CourierActiveTime(data)
+}
+
+func TestCourierActiveTimeDropoffBeforePickup(t *testing.T) {
+	t.Parallel()
+
+	data := []DeliveryAction{
+		{1, 10, DeliveryType("pickup")},
+		{1, 20, DeliveryType("dropoff")},
+		{3, 30, DeliveryType("dropoff")},
+	}
+
+	defer func() {
+		if err := recover(); err == nil {
+			t.Errorf("Expected a panic due to a dropoff only. no pickup.")
+		}
+	}()
+
+	CourierActiveTime(data)
+}
+
+func TestCourierActiveTimeInvalidDeliveryType(t *testing.T) {
+	t.Parallel()
+
+	data := []DeliveryAction{
+		{3, 30, DeliveryType("pickup")},
+		{4, 40, DeliveryType("gonnapanic")},
+	}
+
+	defer func() {
+		if err := recover(); err == nil {
+			t.Errorf("Expected a panic due to a duplicate pickup for the same delivery ID")
+		}
+	}()
+
+	CourierActiveTime(data)
+}
+
+func BenchmarkCourierActiveTime(b *testing.B) {
+	for i := 0; i < b.N; i++ {
+		for _, tc := range testcases {
+			CourierActiveTime(tc.data)
+		}
+	}
+}