diff --git a/golang/strings/string_utils.go b/golang/strings/string_utils.go new file mode 100644 index 00000000..fa65cc63 --- /dev/null +++ b/golang/strings/string_utils.go @@ -0,0 +1,81 @@ +package go_strings + +import ( + "strings" + "unicode" +) + +// ReverseString reverses the given string, handling Unicode correctly. +func ReverseString(s string) string { + runes := []rune(s) + for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 { + runes[i], runes[j] = runes[j], runes[i] + } + return string(runes) +} + +// CamelToSnake converts a camelCase or PascalCase string to snake_case. +func CamelToSnake(s string) string { + var result strings.Builder + for i, r := range s { + if unicode.IsUpper(r) { + if i > 0 { + result.WriteRune('_') + } + result.WriteRune(unicode.ToLower(r)) + } else { + result.WriteRune(r) + } + } + return result.String() +} + +// SnakeToCamel converts a snake_case string to camelCase. +func SnakeToCamel(s string) string { + parts := strings.Split(s, "_") + var result strings.Builder + for i, part := range parts { + if part == "" { + continue + } + if i == 0 { + result.WriteString(strings.ToLower(part)) + } else { + result.WriteString(strings.ToUpper(part[:1]) + strings.ToLower(part[1:])) + } + } + return result.String() +} + +// TruncateWithEllipsis truncates a string to maxLen and appends "..." if truncated. +func TruncateWithEllipsis(s string, maxLen int) string { + runes := []rune(s) + if len(runes) <= maxLen { + return s + } + if maxLen <= 3 { + return string(runes[:maxLen]) + } + return string(runes[:maxLen-3]) + "..." +} + +// CountWords counts the number of words in a string. +func CountWords(s string) int { + return len(strings.Fields(s)) +} + +// IsPalindrome checks if a string is a palindrome, ignoring case and non-alphanumeric characters. +func IsPalindrome(s string) bool { + var cleaned []rune + for _, r := range strings.ToLower(s) { + if unicode.IsLetter(r) || unicode.IsDigit(r) { + cleaned = append(cleaned, r) + } + } + for i, j := 0, len(cleaned)-1; i < j; i, j = i+1, j-1 { + if cleaned[i] != cleaned[j] { + return false + } + } + return true +} diff --git a/golang/strings/string_utils_test.go b/golang/strings/string_utils_test.go new file mode 100644 index 00000000..62fbf695 --- /dev/null +++ b/golang/strings/string_utils_test.go @@ -0,0 +1,129 @@ +package go_strings + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestReverseString_다양한입력(t *testing.T) { + tests := []struct { + name string + input string + expected string + }{ + {"영문", "hello", "olleh"}, + {"한글", "안녕하세요", "요세하녕안"}, + {"빈문자열", "", ""}, + {"한글자", "a", "a"}, + {"회문", "racecar", "racecar"}, + {"이모지", "hello🌍", "🌍olleh"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, ReverseString(tt.input)) + }) + } +} + +func TestCamelToSnake_변환(t *testing.T) { + tests := []struct { + name string + input string + expected string + }{ + {"camelCase", "camelCase", "camel_case"}, + {"PascalCase", "PascalCase", "pascal_case"}, + {"단일소문자", "hello", "hello"}, + {"여러대문자", "HTTPServer", "h_t_t_p_server"}, + {"빈문자열", "", ""}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, CamelToSnake(tt.input)) + }) + } +} + +func TestSnakeToCamel_변환(t *testing.T) { + tests := []struct { + name string + input string + expected string + }{ + {"기본", "snake_case", "snakeCase"}, + {"세단어", "hello_world_go", "helloWorldGo"}, + {"단일단어", "hello", "hello"}, + {"빈문자열", "", ""}, + {"앞뒤언더스코어", "_hello_world_", "helloWorld"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, SnakeToCamel(tt.input)) + }) + } +} + +func TestTruncateWithEllipsis_잘라내기(t *testing.T) { + tests := []struct { + name string + input string + maxLen int + expected string + }{ + {"짧은문자열", "hi", 10, "hi"}, + {"정확히같은길이", "hello", 5, "hello"}, + {"잘라내기", "hello world", 8, "hello..."}, + {"매우짧은최대길이", "hello", 3, "hel"}, + {"한글잘라내기", "안녕하세요 세계입니다", 7, "안녕하세..."}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, TruncateWithEllipsis(tt.input, tt.maxLen)) + }) + } +} + +func TestCountWords_단어수세기(t *testing.T) { + tests := []struct { + name string + input string + expected int + }{ + {"일반문장", "hello world", 2}, + {"여러공백", " hello world ", 2}, + {"빈문자열", "", 0}, + {"탭포함", "hello\tworld\ngo", 3}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, CountWords(tt.input)) + }) + } +} + +func TestIsPalindrome_회문검사(t *testing.T) { + tests := []struct { + name string + input string + expected bool + }{ + {"영문회문", "racecar", true}, + {"대소문자무시", "RaceCar", true}, + {"공백과특수문자무시", "A man, a plan, a canal: Panama", true}, + {"회문아님", "hello", false}, + {"빈문자열", "", true}, + {"숫자포함회문", "12321", true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, IsPalindrome(tt.input)) + }) + } +}