diff --git a/docs/exercises.md b/docs/exercises.md index 7a88f72..2808c4f 100644 --- a/docs/exercises.md +++ b/docs/exercises.md @@ -40,7 +40,7 @@ title: Exercises
  • 20_methods - Methods
  • -

    Advanced (21-37)

    +

    Advanced (21-47)

    diff --git a/internal/exercises/catalog.yaml b/internal/exercises/catalog.yaml index 37b779b..d84a3f0 100644 --- a/internal/exercises/catalog.yaml +++ b/internal/exercises/catalog.yaml @@ -169,20 +169,19 @@ concepts: test_regex: ".*" hints: - Use `make(chan T, N)` to create a buffered channel and `<-` to send and receive on it. -- slug: 39_channel_directions - title: Channel Directions - test_regex: ".*" - hints: - - Create two channels, one for sending jobs and one for receiving results. - - Start 5 workers using go routines. - - Send 50 jobs to the worker pool. - - Receive results from the worker pool and store them in a `rs` slice. - - Enforce type safety by specifying the channel directions in worker function. - slug: 35_channel_sync title: Channel Synchronization test_regex: ".*" hints: - Use a buffered boolean channel and wait till go routine completes. +- slug: 36_json + title: JSON + test_regex: ".*" + hints: + - Use the encoding/json package to work with JSON data. + - Implement MarshalPerson to convert a struct into JSON using json.Marshal. + - Implement UnmarshalPerson to convert a JSON string into a struct using json.Unmarshal. + - Handle and return errors properly in both functions. - slug: 37_xml title: XML Encoding and Decoding test_regex: ".*" @@ -191,6 +190,33 @@ concepts: - Add XML struct tags using `xml:"fieldname"` to map struct fields to XML elements. - Use xml.Marshal to convert structs to XML bytes. - Use xml.Unmarshal to parse XML bytes into structs. +- slug: 48_sorting_by_functions + title: "Sorting by Functions" + difficulty: beginner + topics: ["slices", "sorting", "functions"] + hints: + - "Implement sort.Interface with Len(), Less(), and Swap() methods" + - "Create custom slice types like ByName and ByAge (e.g., type ByName []Person)" + - "Use sort.Sort() to sort slices with your custom comparison logic" + - "Remember to make copies of slices to avoid modifying the original" +- slug: 38_time_formatting + title: "Time Formatting" + difficulty: beginner + topics: ["time", "formatting", "parsing"] + hints: + - "Use time.Now().Format() to format current time with a specific layout" + - "Use time.Parse() to parse time strings with known layouts" + - "Use time.LoadLocation() to work with different timezones" + - "Extract time components using .Date() and .Clock() methods" +- slug: 39_channel_directions + title: Channel Directions + test_regex: ".*" + hints: + - Create two channels, one for sending jobs and one for receiving results. + - Start 5 workers using go routines. + - Send 50 jobs to the worker pool. + - Receive results from the worker pool and store them in a `rs` slice. + - Enforce type safety by specifying the channel directions in worker function. - slug: 40_channel_select title: Channel Select test_regex: ".*" @@ -247,7 +273,50 @@ concepts: hints: - Use `default` case in select to handle non-blocking channel operations. - Safely increment `dropped` using mutex `Lock()` and `Unlock()`. - +- slug: 47_string_functions + title: String Functions + test_regex: ".*" + hints: + - Use strings.Contains to check if a substring exists in a string. + - Use strings.HasPrefix and strings.HasSuffix for prefix/suffix checking. + - Use strings.Index to find the position of a substring. + - Use strings.ToUpper and strings.ToLower for case conversion. + - Use strings.TrimSpace to remove leading and trailing whitespace. +- slug: 49_panic + title: Panic and Recover + test_regex: ".*" + hints: + - "Use `panic()` to simulate runtime errors when appropriate." + - "Use `defer` with `recover()` to catch and handle panics." + - "Recovering from panics allows graceful handling of unexpected situations." + - "Remember: `recover()` only works inside a deferred function." +- slug: 50_timers + title: "Timers" + difficulty: medium + topics: ["time", "goroutines", "concurrency", "synchronization"] + test_regex: ".*" + hints: + - "Use a map[string]*time.Timer to track active timers by key." + - "Use a mutex to safely access the timers map concurrently." + - "Start a timer with time.AfterFunc(d, fn) to run a callback after duration d." + - "If Start is called again for an existing key, stop and replace the old timer." + - "Stop should remove the timer from the map and prevent the callback from firing." + - "Reset can call timer.Reset(d) to reschedule the same callback." + - "Remember to remove timers from the map after they fire to avoid memory leaks." + - "Write tests to verify Start, Stop, and Reset behavior, including concurrency scenarios." +- slug: 51_rate_limiting + title: Rate Limiting + test_regex: ".*" + difficulty: medium + topics: ["time", "concurrency", "maps"] + hints: + - "Implement a RateLimiter struct that tracks request timestamps per key using a map[string][]time.Time." + - "Use a mutex to safely handle concurrent access to the map." + - "In the Allow method, remove timestamps older than the interval and check if the number of recent requests exceeds the limit." + - "Reset should clear the request history for a key; if the key does not exist, do nothing." + - "Consider edge cases: multiple keys, concurrent access, and requests exactly at the interval boundary." + - "In tests, use time.Sleep with a small buffer above the interval to avoid flakiness." + - "Initialize the timestamps map in NewRateLimiter to prevent nil pointer errors." projects: - slug: 101_text_analyzer title: Text Analyzer (Easy) @@ -289,15 +358,6 @@ projects: test_regex: ".*" hints: - Implement an in-memory key-value store with basic CRUD operations and optional persistence. -- slug: 36_json - title: JSON - test_regex: ".*" - hints: - - Use the encoding/json package to work with JSON data. - - Implement MarshalPerson to convert a struct into JSON using json.Marshal. - - Implement UnmarshalPerson to convert a JSON string into a struct using json.Unmarshal. - - Handle and return errors properly in both functions. - - slug: 109_epoch title: "Epoch Conversion" difficulty: beginner @@ -305,62 +365,4 @@ projects: hints: - "Use Go's `time.Unix()` to convert an epoch to time." - "Use `t.Unix()` to convert time back to epoch." - - "Remember Go’s `time.Parse` can help parse date strings." - -- slug: 37_sorting_by_functions - title: "Sorting by Functions" - difficulty: beginner - topics: ["slices", "sorting", "functions"] - hints: - - "Implement sort.Interface with Len(), Less(), and Swap() methods" - - "Create custom slice types like ByName and ByAge (e.g., type ByName []Person)" - - "Use sort.Sort() to sort slices with your custom comparison logic" - - "Remember to make copies of slices to avoid modifying the original" - -- slug: 38_time_formatting - title: "Time Formatting" - difficulty: beginner - topics: ["time", "formatting", "parsing"] - hints: - - "Use time.Now().Format() to format current time with a specific layout" - - "Use time.Parse() to parse time strings with known layouts" - - "Use time.LoadLocation() to work with different timezones" - - "Extract time components using .Date() and .Clock() methods" - -- slug: 39_panic - title: Panic and Recover - test_regex: ".*" - hints: - - "Use `panic()` to simulate runtime errors when appropriate." - - "Use `defer` with `recover()` to catch and handle panics." - - "Recovering from panics allows graceful handling of unexpected situations." - - "Remember: `recover()` only works inside a deferred function." - -- slug: 64_timers - title: "Timers" - difficulty: medium - topics: ["time", "goroutines", "concurrency", "synchronization"] - test_regex: ".*" - hints: - - "Use a map[string]*time.Timer to track active timers by key." - - "Use a mutex to safely access the timers map concurrently." - - "Start a timer with time.AfterFunc(d, fn) to run a callback after duration d." - - "If Start is called again for an existing key, stop and replace the old timer." - - "Stop should remove the timer from the map and prevent the callback from firing." - - "Reset can call timer.Reset(d) to reschedule the same callback." - - "Remember to remove timers from the map after they fire to avoid memory leaks." - - "Write tests to verify Start, Stop, and Reset behavior, including concurrency scenarios." - -- slug: 68_rate_limiting - title: Rate Limiting - test_regex: ".*" - difficulty: medium - topics: ["time", "concurrency", "maps"] - hints: - - "Implement a RateLimiter struct that tracks request timestamps per key using a map[string][]time.Time." - - "Use a mutex to safely handle concurrent access to the map." - - "In the Allow method, remove timestamps older than the interval and check if the number of recent requests exceeds the limit." - - "Reset should clear the request history for a key; if the key does not exist, do nothing." - - "Consider edge cases: multiple keys, concurrent access, and requests exactly at the interval boundary." - - "In tests, use time.Sleep with a small buffer above the interval to avoid flakiness." - - "Initialize the timestamps map in NewRateLimiter to prevent nil pointer errors." + - "Remember Go's `time.Parse` can help parse date strings." diff --git a/internal/exercises/solutions/47_string_functions/string_functions.go b/internal/exercises/solutions/47_string_functions/string_functions.go new file mode 100644 index 0000000..cdc2588 --- /dev/null +++ b/internal/exercises/solutions/47_string_functions/string_functions.go @@ -0,0 +1,38 @@ +package string_functions + +import "strings" + +// Contains checks if substr is within s +func Contains(s, substr string) bool { + return strings.Contains(s, substr) +} + +// HasPrefix tests whether the string s begins with prefix +func HasPrefix(s, prefix string) bool { + return strings.HasPrefix(s, prefix) +} + +// HasSuffix tests whether the string s ends with suffix +func HasSuffix(s, suffix string) bool { + return strings.HasSuffix(s, suffix) +} + +// Index returns the index of the first instance of substr in s, or -1 if substr is not present in s +func Index(s, substr string) int { + return strings.Index(s, substr) +} + +// ToUpper returns s with all Unicode letters mapped to their upper case +func ToUpper(s string) string { + return strings.ToUpper(s) +} + +// ToLower returns s with all Unicode letters mapped to their lower case +func ToLower(s string) string { + return strings.ToLower(s) +} + +// TrimSpace returns s with all leading and trailing white space removed +func TrimSpace(s string) string { + return strings.TrimSpace(s) +} diff --git a/internal/exercises/templates/47_string_functions/string_functions.go b/internal/exercises/templates/47_string_functions/string_functions.go new file mode 100644 index 0000000..f8c0d60 --- /dev/null +++ b/internal/exercises/templates/47_string_functions/string_functions.go @@ -0,0 +1,46 @@ +package string_functions + +// TODO: Implement these functions using Go's strings package +// You'll need to import "strings" package when implementing the functions + +// Contains checks if substr is within s +func Contains(s, substr string) bool { + // TODO: use strings.Contains + return false // Intentionally wrong to simulate failing exercise +} + +// HasPrefix tests whether the string s begins with prefix +func HasPrefix(s, prefix string) bool { + // TODO: use strings.HasPrefix + return false // Intentionally wrong to simulate failing exercise +} + +// HasSuffix tests whether the string s ends with suffix +func HasSuffix(s, suffix string) bool { + // TODO: use strings.HasSuffix + return false // Intentionally wrong to simulate failing exercise +} + +// Index returns the index of the first instance of substr in s, or -1 if substr is not present in s +func Index(s, substr string) int { + // TODO: use strings.Index + return -1 // Intentionally wrong to simulate failing exercise +} + +// ToUpper returns s with all Unicode letters mapped to their upper case +func ToUpper(s string) string { + // TODO: use strings.ToUpper + return "" // Intentionally wrong to simulate failing exercise +} + +// ToLower returns s with all Unicode letters mapped to their lower case +func ToLower(s string) string { + // TODO: use strings.ToLower + return "" // Intentionally wrong to simulate failing exercise +} + +// TrimSpace returns s with all leading and trailing white space removed +func TrimSpace(s string) string { + // TODO: use strings.TrimSpace + return "" // Intentionally wrong to simulate failing exercise +} diff --git a/internal/exercises/templates/47_string_functions/string_functions_test.go b/internal/exercises/templates/47_string_functions/string_functions_test.go new file mode 100644 index 0000000..9550c31 --- /dev/null +++ b/internal/exercises/templates/47_string_functions/string_functions_test.go @@ -0,0 +1,153 @@ +package string_functions + +import "testing" + +func TestContains(t *testing.T) { + tests := []struct { + s string + substr string + want bool + }{ + {"hello world", "world", true}, + {"hello world", "hello", true}, + {"hello world", "foo", false}, + {"", "", true}, + {"hello", "", true}, + } + + for _, tt := range tests { + t.Run(tt.s+" contains "+tt.substr, func(t *testing.T) { + if got := Contains(tt.s, tt.substr); got != tt.want { + t.Errorf("Contains(%q, %q) = %v, want %v", tt.s, tt.substr, got, tt.want) + } + }) + } +} + +func TestHasPrefix(t *testing.T) { + tests := []struct { + s string + prefix string + want bool + }{ + {"hello world", "hello", true}, + {"hello world", "world", false}, + {"", "", true}, + {"hello", "hello", true}, + } + + for _, tt := range tests { + t.Run(tt.s+" has prefix "+tt.prefix, func(t *testing.T) { + if got := HasPrefix(tt.s, tt.prefix); got != tt.want { + t.Errorf("HasPrefix(%q, %q) = %v, want %v", tt.s, tt.prefix, got, tt.want) + } + }) + } +} + +func TestHasSuffix(t *testing.T) { + tests := []struct { + s string + suffix string + want bool + }{ + {"hello world", "world", true}, + {"hello world", "hello", false}, + {"", "", true}, + {"hello", "hello", true}, + } + + for _, tt := range tests { + t.Run(tt.s+" has suffix "+tt.suffix, func(t *testing.T) { + if got := HasSuffix(tt.s, tt.suffix); got != tt.want { + t.Errorf("HasSuffix(%q, %q) = %v, want %v", tt.s, tt.suffix, got, tt.want) + } + }) + } +} + +func TestIndex(t *testing.T) { + tests := []struct { + s string + substr string + want int + }{ + {"hello world", "world", 6}, + {"hello world", "hello", 0}, + {"hello world", "foo", -1}, + {"", "", 0}, + {"hello", "l", 2}, + } + + for _, tt := range tests { + t.Run("index of "+tt.substr+" in "+tt.s, func(t *testing.T) { + if got := Index(tt.s, tt.substr); got != tt.want { + t.Errorf("Index(%q, %q) = %v, want %v", tt.s, tt.substr, got, tt.want) + } + }) + } +} + +func TestToUpper(t *testing.T) { + tests := []struct { + s string + want string + }{ + {"hello", "HELLO"}, + {"Hello World", "HELLO WORLD"}, + {"", ""}, + {"123", "123"}, + {"héllo", "HÉLLO"}, + } + + for _, tt := range tests { + t.Run("ToUpper("+tt.s+")", func(t *testing.T) { + if got := ToUpper(tt.s); got != tt.want { + t.Errorf("ToUpper(%q) = %q, want %q", tt.s, got, tt.want) + } + }) + } +} + +func TestToLower(t *testing.T) { + tests := []struct { + s string + want string + }{ + {"HELLO", "hello"}, + {"Hello World", "hello world"}, + {"", ""}, + {"123", "123"}, + {"HÉLLO", "héllo"}, + } + + for _, tt := range tests { + t.Run("ToLower("+tt.s+")", func(t *testing.T) { + if got := ToLower(tt.s); got != tt.want { + t.Errorf("ToLower(%q) = %q, want %q", tt.s, got, tt.want) + } + }) + } +} + +func TestTrimSpace(t *testing.T) { + tests := []struct { + s string + want string + }{ + {" hello ", "hello"}, + {" hello world ", "hello world"}, + {"", ""}, + {"hello", "hello"}, + {" ", ""}, + {"\n\thello\t\n", "hello"}, + } + + for _, tt := range tests { + t.Run("TrimSpace("+tt.s+")", func(t *testing.T) { + if got := TrimSpace(tt.s); got != tt.want { + t.Errorf("TrimSpace(%q) = %q, want %q", tt.s, got, tt.want) + } + }) + } +}