Repository: vaskoz/dailycodingproblem-go (go)

Issue:
Day 385
Good morning! Here's your coding interview problem for today.

This problem was asked by Apple.

You are given a hexadecimal-encoded string that has been XOR'd against a single char.

Decrypt the message. For example, given the string:

`7a575e5e5d12455d405e561254405d5f1276535b5e4b12715d565b5c551262405d505e575f`
You should be able to decrypt it and get:

`Hello world from Daily Coding Problem`

Pull request description:
Day385
Resolves #774.

Patch:
diff --git a/README.md b/README.md
index 7c0bcf3..2b7a552 100644
--- a/README.md
+++ b/README.md
@@ -379,4 +379,5 @@ problems from
 * [Day 381](https://github.com/vaskoz/dailycodingproblem-go/issues/768)
 * [Day 382](https://github.com/vaskoz/dailycodingproblem-go/issues/770)
 * [Day 383](https://github.com/vaskoz/dailycodingproblem-go/issues/772)
+* [Day 385](https://github.com/vaskoz/dailycodingproblem-go/issues/774)
 
diff --git a/day385/problem.go b/day385/problem.go
new file mode 100644
index 0000000..dfb821c
--- /dev/null
+++ b/day385/problem.go
@@ -0,0 +1,76 @@
+package day385
+
+import (
+	"encoding/hex"
+	"strings"
+)
+
+// nolint
+var freqMap = map[byte]float64{
+	'a': 0.08167, 'b': 0.01492, 'c': 0.02782,
+	'd': 0.04253, 'e': 0.12702, 'f': 0.02228,
+	'g': 0.02015, 'h': 0.06094, 'i': 0.06966,
+	'j': 0.00153, 'k': 0.00772, 'l': 0.04025,
+	'm': 0.02406, 'n': 0.06749, 'o': 0.07507,
+	'p': 0.01929, 'q': 0.00095, 'r': 0.05987,
+	's': 0.06327, 't': 0.09056, 'u': 0.02758,
+	'v': 0.00978, 'w': 0.02360, 'x': 0.00150,
+	'y': 0.01974, 'z': 0.00074,
+}
+
+// DecryptSecretXorMessage checks all possible chars used for XOR.
+func DecryptSecretXorMessage(msg string) string {
+	decoded, err := hex.DecodeString(msg)
+	if err != nil {
+		panic("bad input message not hex")
+	}
+
+	smallest := 1000.0
+	ans := ""
+
+	for b := byte(0); b < byte(255); b++ {
+		freq := make(map[byte]int)
+		sb := strings.Builder{}
+		totalC := 0
+		invalidC := 0
+
+		for i := 0; i < len(decoded); i++ {
+			newB := decoded[i] ^ b
+			lowered := strings.ToLower(string(newB))
+
+			if lowerC := lowered[0]; lowerC >= 'a' && lowerC <= 'z' {
+				freq[lowerC]++
+			} else if lowerC != ' ' {
+				invalidC++
+			}
+			totalC++
+
+			sb.WriteByte(newB)
+		}
+
+		if invalidC > 0 {
+			continue
+		}
+
+		var diff float64
+
+		for k, v := range freq {
+			diff += abs(freqMap[k] - float64(v)/float64(totalC))
+		}
+
+		if diff < smallest {
+			smallest = diff
+			ans = sb.String()
+		}
+	}
+
+	return ans
+}
+
+func abs(a float64) float64 {
+	if a < 0 {
+		return -a
+	}
+
+	return a
+}


Test patch:
diff --git a/day385/problem_test.go b/day385/problem_test.go
new file mode 100644
index 0000000..c9f9970
--- /dev/null
+++ b/day385/problem_test.go
@@ -0,0 +1,43 @@
+package day385
+
+import "testing"
+
+// nolint
+var testcases = []struct {
+	encoded, decoded string
+}{
+	{
+		"7a575e5e5d12455d405e561254405d5f1276535b5e4b12715d565b5c551262405d505e575f",
+		"Hello world from Daily Coding Problem",
+	},
+}
+
+func TestDecryptSecretXorMessage(t *testing.T) {
+	t.Parallel()
+
+	for _, tc := range testcases {
+		if res := DecryptSecretXorMessage(tc.encoded); res != tc.decoded {
+			t.Errorf("Expected %v, got %v", tc.decoded, res)
+		}
+	}
+}
+
+func TestDecryptSecretXorMessageBadHex(t *testing.T) {
+	t.Parallel()
+
+	defer func() {
+		if err := recover(); err == nil {
+			t.Errorf("Expected a panic from non-hex input")
+		}
+	}()
+
+	DecryptSecretXorMessage("nothex")
+}
+
+func BenchmarkDecryptSecretXorMessage(b *testing.B) {
+	for i := 0; i < b.N; i++ {
+		for _, tc := range testcases {
+			DecryptSecretXorMessage(tc.encoded)
+		}
+	}
+}