Skip to content

Commit

Permalink
Add Roman and Palindrome Kata
Browse files Browse the repository at this point in the history
  • Loading branch information
cmilhench committed Aug 9, 2024
1 parent 4329d49 commit 9534e2e
Show file tree
Hide file tree
Showing 5 changed files with 341 additions and 4 deletions.
4 changes: 0 additions & 4 deletions exp/context/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@ import (
"reflect"
)

type User struct {
Name string
}

// WithValueOf is a helper function to set a value of a specific type.
func WithValueOf[T any](parent context.Context, val *T) context.Context {
return context.WithValue(parent, reflect.TypeOf((*T)(nil)), val)
Expand Down
39 changes: 39 additions & 0 deletions exp/context/context_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package context

import (
"context"
"reflect"
"testing"
)

func TestWithUser(t *testing.T) {
type User struct {
Name string
}
type args struct {
parent context.Context
user *User
}
tests := []struct {
name string
args args
want *User
}{
{
name: "test",
args: args{
parent: context.Background(),
user: &User{Name: "test"},
},
want: &User{Name: "test"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := WithValueOf(tt.args.parent, tt.args.user)
if got, ok := ValueOf[User](ctx); ok && !reflect.DeepEqual(got, tt.want) {
t.Errorf("WithUser() = %v, want %v", got, tt.want)
}
})
}
}
88 changes: 88 additions & 0 deletions exp/env/env_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Package env provides environment variable utilities.
package env

import (
"os"
"testing"
)

func TestGet(t *testing.T) {
type args struct {
name string
}
tests := []struct {
name string
args args
shouldPanic bool
want string
}{
{
name: "should return value",
args: args{name: "TEST1"},
shouldPanic: true,
},
{
name: "should return default value",
args: args{name: "TEST2"},
shouldPanic: false,
want: "test2",
},
}
os.Setenv("TEST2", "test2")
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.shouldPanic {
if reason, isPanic := shouldPanic(t, func() { Get(tt.args.name) }); !isPanic {
t.Errorf("Get() should panic, but got %v", reason)
}
} else {
if got := Get(tt.args.name); got != tt.want {
t.Errorf("Get() = %v, want %v", got, tt.want)
}
}
})
}
}

func TestGetDefault(t *testing.T) {
type args struct {
name string
value string
}
tests := []struct {
name string
args args
want string
}{
{
name: "should return value",
args: args{name: "TEST1", value: "test1"},
want: "test1",
},
{
name: "should return default value",
args: args{name: "TEST2", value: "default"},
want: "test2",
},
}
os.Setenv("TEST2", "test2")
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := GetDefault(tt.args.name, tt.args.value); got != tt.want {
t.Errorf("GetDefault() = %v, want %v", got, tt.want)
}
})
}
}

func shouldPanic(t *testing.T, f func()) (reason any, isPanic bool) {
t.Helper()
defer func() {
if err := recover(); err != nil {
reason = err
isPanic = true
}
}()
f()
return nil, false
}
74 changes: 74 additions & 0 deletions internal/kata/palindrome_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package kata

import "testing"

func Test_IsPalindrome(t *testing.T) {
tests := []struct {
name string
in int
wantOut bool
}{
{"0 is a palindrome", 0, true},
{"1 is a palindrome", 1, true},
{"121 is a palindrome", 121, true},
{"123 is not a palindrome", 123, false},
{"-121 is not a palindrome", -121, false},
{"10 is not a palindrome", 10, false},
{"-101 is not a palindrome", -101, false},
{"123454321 is a palindrome", 123454321, true},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
gotOut := IsPalindromeNumber(tc.in)
if gotOut != tc.wantOut {
t.Errorf("IsPalindrome(%v) = %v, want %v", tc.in, gotOut, tc.wantOut)
}
})
}
}

func Test_IsPalindromeText(t *testing.T) {
tests := []struct {
name string
in string
wantOut bool
}{
{"A is a palindrome", "A", true},
{"AA is a palindrome", "AA", true},
{"AB is not a palindrome", "AB", false},
{"A A is not a palindrome", "A A", true},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
gotOut := IsPalindromeText(tc.in)
if gotOut != tc.wantOut {
t.Errorf("IsPalindrome(%v) = %v, want %v", tc.in, gotOut, tc.wantOut)
}
})
}
}

// ----------------------------------------------------------------------------

func IsPalindromeText(text string) bool {
for i, j := 0, len(text)-1; i < j; i, j = i+1, j-1 {
if text[i] != text[j] {
return false
}
}
return true
}

func IsPalindromeNumber(num int) bool {
var r, d int
// 1234 % 10 = 4, 1234 / 10 = 123
// 123 % 10 = 3, 123 / 10 = 12
// 12 % 10 = 2, 12 / 10 = 1
// 1 % 10 = 1, 1 / 10 = 0
for i := num; i > 0; {
d, i = i%10, i/10
r = r*10 + d
}

return num == r
}
140 changes: 140 additions & 0 deletions internal/kata/roman_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package kata

import (
"fmt"
"strings"
"testing"
)

func Test_RomanNumeral(t *testing.T) {
tests := []struct {
name string
in int
wantOut string
}{
{"0001", 1, "I"},
{"0004", 4, "IV"},
{"0005", 5, "V"},
{"0009", 9, "IX"},
{"0010", 10, "X"},
{"0039", 39, "XXXIX"},
{"0246", 246, "CCXLVI"},
{"0789", 789, "DCCLXXXIX"},
{"2421", 2421, "MMCDXXI"},
{"0160", 160, "CLX"},
{"0027", 207, "CCVII"},
{"1009", 1009, "MIX"},
{"1066", 1066, "MLXVI"},
{"3999", 3999, "MMMCMXCIX"},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
gotOut := RomanNumeral(tc.in)
if gotOut != tc.wantOut {
t.Errorf("Comma(%v) = %v, want %v", tc.in, gotOut, tc.wantOut)
}
})
}
}

func Test_ParseRomanNumeral(t *testing.T) {
tests := []struct {
name string
in string
wantOut int
}{
{"0001", "I", 1},
{"0004", "IV", 4},
{"0005", "V", 5},
{"0009", "IX", 9},
{"0010", "X", 10},
{"0039", "XXXIX", 39},
{"0246", "CCXLVI", 246},
{"0789", "DCCLXXXIX", 789},
{"2421", "MMCDXXI", 2421},
{"0160", "CLX", 160},
{"0027", "CCVII", 207},
{"1009", "MIX", 1009},
{"1066", "MLXVI", 1066},
{"3999", "MMMCMXCIX", 3999},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
gotOut := ParseRomanNumeral(tc.in)
if gotOut != tc.wantOut {
t.Errorf("Comma(%v) = %v, want %v", tc.in, gotOut, tc.wantOut)
}
})
}
}

// ----------------------------------------------------------------------------

// 1 5 10 50 100 500 1000
// I V X L C D M
// I can be placed before V (5) and X (10) to make 4 and 9.
// X can be placed before L (50) and C (100) to make 40 and 90.
// C can be placed before D (500) and M (1000) to make 400 and 900.
// 3999 = MMMCMXCIX

func RomanNumeral1(num int) string {
if 0 > num || num > 3999 {
return ""
}
sym := "IVXLCDM"
out := ""
for idx := 1; num > 0; {
r := num % 10
switch r {
case 1, 2, 3:
I := sym[idx-1]
out = fmt.Sprintf("%s%s", strings.Repeat(string(I), r), out)
case 4:
I, V := sym[idx-1], sym[idx]
out = fmt.Sprintf("%s%s%s", string(I), string(V), out)
case 5:
V := sym[idx]
out = fmt.Sprintf("%s%s", string(V), out)
case 6, 7, 8:
I, V := sym[idx-1], sym[idx]
out = fmt.Sprintf("%s%s%s", string(V), strings.Repeat(string(I), r-5), out)
case 9:
I, X := sym[idx-1], sym[idx+1]
out = fmt.Sprintf("%s%s%s", string(I), string(X), out)
default:
}
num = num / 10
idx = idx + 2
}

return out
}

func RomanNumeral(num int) string {
val := []int{1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1}
sym := []string{"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"}
out := ""
for i := 0; i < len(val); i++ {
for num >= val[i] {
out += sym[i]
num -= val[i]
}
}
return out
}

func ParseRomanNumeral(roman string) int {
val := map[byte]int{'I': 1, 'V': 5, 'X': 10, 'L': 50, 'C': 100, 'D': 500, 'M': 1000}
out := 0
prev := 0
for i := len(roman) - 1; i >= 0; i-- {
cur := val[roman[i]]
if cur < prev {
out -= cur
} else {
out += cur
}
prev = cur
}
return out
}

0 comments on commit 9534e2e

Please sign in to comment.