Skip to content
Open
2 changes: 2 additions & 0 deletions docs/exercises.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ title: Exercises
<li>25_range_iterators - Range over Iterators</li>
<li>26_errors - Error Handling</li>
<li>27_custom_errors - Custom Errors</li>
<li>41_regex - Regular Expressions</li>
<li>42_string_functions - String Functions</li>
<li>36_json - JSON Processing</li>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Verify exercise numbering sequence.

The new exercises 41_regex and 42_string_functions are listed before 36_json in the Advanced section. This breaks the numerical sequence. Consider either:

  • Reordering to maintain numerical sequence (36, 37, 41, 42)
  • Or renumbering these exercises to fit sequentially

However, if exercises 38-40 exist elsewhere and this ordering is intentional, this is acceptable.

Run this script to check if exercises 38, 39, and 40 exist in the catalog:


🏁 Script executed:

#!/bin/bash
# Check for exercises 38, 39, and 40 in the catalog
grep -E "slug: (38|39|40)_" internal/exercises/catalog.yaml

Length of output: 82


🏁 Script executed:

#!/bin/bash
# Check where exercises 38, 39, 40 appear in docs/exercises.md
grep -n "38_\|39_\|40_" docs/exercises.md

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Show the full structure around lines 52-54 with context
sed -n '40,60p' docs/exercises.md

Length of output: 622


Reorder Advanced exercises in docs/exercises.md

  • In the Advanced (21-37) section, move <li>36_json – JSON Processing</li> and <li>37_xml – XML Processing</li> above the new entries (<li>41_regex – Regular Expressions</li>, <li>42_string_functions – String Functions</li>).
  • Update the section header to Advanced (21-42) if you intend to include 41 and 42 here.
🤖 Prompt for AI Agents
In docs/exercises.md around lines 52 to 54, the Advanced section items are out
of order and the header range is inconsistent: move the two existing items
<li>36_json – JSON Processing</li> and <li>37_xml – XML Processing</li> so they
appear above the new entries <li>41_regex – Regular Expressions</li> and
<li>42_string_functions – String Functions</li>, and update the Advanced section
header to read "Advanced (21-42)" to reflect inclusion of exercises 41 and 42.

<li>37_xml - XML Processing</li>
</ul>
Expand Down
96 changes: 57 additions & 39 deletions internal/exercises/catalog.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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: ".*"
Expand All @@ -191,12 +190,60 @@ 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: 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_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: ".*"
hints:
- Use `select` to handle multiple channel operations concurrently.
- Use `time.After` to simulate a 5 microsecond timeout.
- slug: 41_regex
title: Regular Expressions
test_regex: ".*"
hints:
- Use regexp.MustCompile to create compiled regular expressions.
- Use MatchString to test if a string matches a pattern.
- Use FindAllString to extract all matches from text.
- Use ReplaceAllString to replace matches with new text.
- "Email regex: ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"
- "Phone regex: ^\\(\\d{3}\\) \\d{3}-\\d{4}$"
- "Number regex: \\d+\\.?\\d*"
- "Vowel regex: [aeiouAEIOU]"
- slug: 42_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.
projects:
- slug: 101_text_analyzer
title: Text Analyzer (Easy)
Expand Down Expand Up @@ -238,40 +285,11 @@ 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
topics: ["time", "epoch", "unix"]
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"
- "Remember Go's `time.Parse` can help parse date strings."
37 changes: 37 additions & 0 deletions internal/exercises/solutions/41_regex/regex.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package regex

// Task:
// Implement regular expression functions using Go's regexp package.
//
// 1. Implement IsValidEmail to validate email addresses using regex.
// 2. Implement ExtractNumbers to extract all numbers from a string.
// 3. Implement ReplaceVowels to replace all vowels with asterisks.
// 4. Implement IsPhoneNumber to validate phone numbers in format (XXX) XXX-XXXX.

// IsValidEmail should return true if the email address is valid.
// A valid email should have the format: [email protected]
func IsValidEmail(email string) bool {
// TODO: implement using regexp.MustCompile and MatchString
return false
}

// ExtractNumbers should return all numbers found in the input string.
// Numbers can be integers or decimals (e.g., "123", "45.67").
func ExtractNumbers(text string) []string {
// TODO: implement using regexp.FindAllString
return nil
}

// ReplaceVowels should replace all vowels (a, e, i, o, u) with asterisks.
// Case-insensitive replacement.
func ReplaceVowels(text string) string {
// TODO: implement using regexp.MustCompile and ReplaceAllString
return ""
}

// IsPhoneNumber should return true if the phone number matches format (XXX) XXX-XXXX.
// Example: (123) 456-7890
func IsPhoneNumber(phone string) bool {
// TODO: implement using regexp.MustCompile and MatchString
return false
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Solution file contains only placeholder implementations.

This file is located in the solutions/ directory but contains only TODO comments and placeholder return values. Solution files should provide working reference implementations that pass all tests, unlike template files which intentionally fail.

Verify whether this is intentional or if the actual regex implementations need to be added.


I'll verify the repository structure and conventions around solution files to determine if this is a legitimate concern.


🏁 Script executed:

#!/bin/bash
# Check if the file exists and examine surrounding structure
echo "=== File existence and location ==="
find . -type f -name "regex.go" | head -20

echo -e "\n=== Directory structure around solutions/41_regex/ ==="
fd -t d "41_regex|regex" | head -20

echo -e "\n=== Check for template vs solutions structure ==="
find . -type d \( -name "solutions" -o -name "template" -o -name "templates" \) | head -10

echo -e "\n=== List solution files to understand pattern ==="
find . -path "*/solutions/*" -name "*.go" -type f | head -15

Length of output: 1280


🏁 Script executed:

#!/bin/bash
# Examine content of a few solution files to see if they have implementations or just placeholders
echo "=== Sample solution files content ==="
for file in $(find . -path "*/solutions/*" -name "*.go" -type f | head -5); do
  echo "File: $file"
  echo "First 30 lines:"
  head -30 "$file"
  echo -e "\n---\n"
done

Length of output: 1896


🏁 Script executed:

#!/bin/bash
# Check for documentation about the exercise structure
echo "=== Looking for README or documentation ==="
find . -iname "readme*" -o -iname "*.md" | grep -E "(exercise|solution|regex)" | head -10

echo -e "\n=== General README files ==="
find . -maxdepth 3 -iname "readme*" -type f

Length of output: 105


🏁 Script executed:

#!/bin/bash
# Compare the template and solution regex.go files
echo "=== SOLUTIONS FILE: solutions/41_regex/regex.go ==="
cat ./internal/exercises/solutions/41_regex/regex.go

echo -e "\n=== TEMPLATES FILE: templates/41_regex/regex.go ==="
cat ./internal/exercises/templates/41_regex/regex.go

Length of output: 2756


🏁 Script executed:

#!/bin/bash
# Check documentation to understand the intent
echo "=== EXERCISES DOCUMENTATION ==="
cat ./docs/exercises.md

Length of output: 2798


Populate regex solution file with real implementations
The file internal/exercises/solutions/41_regex/regex.go is identical to its template and only contains TODO placeholders. Unlike other solution files, it needs working implementations of IsValidEmail, ExtractNumbers, ReplaceVowels, and IsPhoneNumber using Go’s regexp package so that all tests pass.

🤖 Prompt for AI Agents
In internal/exercises/solutions/41_regex/regex.go around lines 1-37 the four
functions are unimplemented; implement them using Go's regexp package:
IsValidEmail should compile a basic anchored email pattern (e.g.
`^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$`) and return
regexp.MustCompile(...).MatchString(email); ExtractNumbers should use a regex
like `\d+(?:\.\d+)?` and return re.FindAllString(text, -1); ReplaceVowels should
use a case-insensitive vowel regex `(?i)[aeiou]` and return
re.ReplaceAllString(text, "*"); IsPhoneNumber should use an anchored pattern for
`(XXX) XXX-XXXX` like `^\(\d{3}\) \d{3}-\d{4}$` and return MatchString(phone).
Ensure proper return types and no panics by using regexp.MustCompile as
requested.

Original file line number Diff line number Diff line change
@@ -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)
}
37 changes: 37 additions & 0 deletions internal/exercises/templates/41_regex/regex.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package regex

// Task:
// Implement regular expression functions using Go's regexp package.
//
// 1. Implement IsValidEmail to validate email addresses using regex.
// 2. Implement ExtractNumbers to extract all numbers from a string.
// 3. Implement ReplaceVowels to replace all vowels with asterisks.
// 4. Implement IsPhoneNumber to validate phone numbers in format (XXX) XXX-XXXX.

// IsValidEmail should return true if the email address is valid.
// A valid email should have the format: [email protected]
func IsValidEmail(email string) bool {
// TODO: implement using regexp.MustCompile and MatchString
return false
}

// ExtractNumbers should return all numbers found in the input string.
// Numbers can be integers or decimals (e.g., "123", "45.67").
func ExtractNumbers(text string) []string {
// TODO: implement using regexp.FindAllString
return nil
}

// ReplaceVowels should replace all vowels (a, e, i, o, u) with asterisks.
// Case-insensitive replacement.
func ReplaceVowels(text string) string {
// TODO: implement using regexp.MustCompile and ReplaceAllString
return ""
}

// IsPhoneNumber should return true if the phone number matches format (XXX) XXX-XXXX.
// Example: (123) 456-7890
func IsPhoneNumber(phone string) bool {
// TODO: implement using regexp.MustCompile and MatchString
return false
}
99 changes: 99 additions & 0 deletions internal/exercises/templates/41_regex/regex_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package regex

import "testing"

func TestIsValidEmail(t *testing.T) {
tests := []struct {
email string
expected bool
}{
{"[email protected]", true},
{"[email protected]", true},
{"invalid-email", false},
{"@domain.com", false},
{"user@", false},
{"user@domain", false},
{"", false},
}

for _, test := range tests {
result := IsValidEmail(test.email)
if result != test.expected {
t.Errorf("IsValidEmail(%q) = %v, want %v", test.email, result, test.expected)
}
}
}

func TestExtractNumbers(t *testing.T) {
tests := []struct {
text string
expected []string
}{
{"I have 123 apples and 45.67 oranges", []string{"123", "45.67"}},
{"The price is $99.99", []string{"99.99"}},
{"No numbers here", []string{}},
{"123", []string{"123"}},
{"12.34.56", []string{"12.34", "56"}},
{"", []string{}},
}

for _, test := range tests {
result := ExtractNumbers(test.text)
if len(result) != len(test.expected) {
t.Errorf("ExtractNumbers(%q) length = %d, want %d", test.text, len(result), len(test.expected))
continue
}
for i, num := range result {
if num != test.expected[i] {
t.Errorf("ExtractNumbers(%q)[%d] = %q, want %q", test.text, i, num, test.expected[i])
}
}
}
}

func TestReplaceVowels(t *testing.T) {
tests := []struct {
text string
expected string
}{
{"hello", "h*ll*"},
{"HELLO", "H*LL*"},
{"Hello World", "H*ll* W*rld"},
{"bcdfg", "bcdfg"},
{"", ""},
{"aeiou", "*****"},
{"AEIOU", "*****"},
}

for _, test := range tests {
result := ReplaceVowels(test.text)
if result != test.expected {
t.Errorf("ReplaceVowels(%q) = %q, want %q", test.text, result, test.expected)
}
}
}

func TestIsPhoneNumber(t *testing.T) {
tests := []struct {
phone string
expected bool
}{
{"(123) 456-7890", true},
{"(555) 123-4567", true},
{"(000) 000-0000", true},
{"123-456-7890", false},
{"(123)456-7890", false},
{"(123) 4567890", false},
{"123 456 7890", false},
{"(123) 456-789", false},
{"(12) 456-7890", false},
{"", false},
}

for _, test := range tests {
result := IsPhoneNumber(test.phone)
if result != test.expected {
t.Errorf("IsPhoneNumber(%q) = %v, want %v", test.phone, result, test.expected)
}
}
}
Loading