diff --git a/.gitignore b/.gitignore index 09944b57..6061dea7 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,8 @@ go.work # Students students.txt + +# VS Code +.vscode/ +*.code-workspace +.history/ diff --git a/exercise1/problem1/main.go b/exercise1/problem1/main.go index dfca465c..e1a52439 100644 --- a/exercise1/problem1/main.go +++ b/exercise1/problem1/main.go @@ -1,3 +1,8 @@ package main -func addUp() {} +func addUp(n int) (sum int) { + for i := 0; i <= n; i++ { + sum += i + } + return +} diff --git a/exercise1/problem10/main.go b/exercise1/problem10/main.go index 04ec3430..a707031d 100644 --- a/exercise1/problem10/main.go +++ b/exercise1/problem10/main.go @@ -1,3 +1,22 @@ package main -func sum() {} +import ( + "fmt" + "strconv" +) + +func sum(m, n string) (string, error) { + intM, err := strconv.Atoi(m) + if err != nil { + return "", fmt.Errorf("string: %s cannot be converted", m) + } + + intN, err := strconv.Atoi(n) + if err != nil { + return "", fmt.Errorf("string: %s cannot be converted", n) + } + + result := intM + intN + + return strconv.Itoa(result), nil +} diff --git a/exercise1/problem2/main.go b/exercise1/problem2/main.go index 2ca540b8..fe75619c 100644 --- a/exercise1/problem2/main.go +++ b/exercise1/problem2/main.go @@ -1,3 +1,16 @@ package main -func binary() {} +import "strconv" + +func binary(n int) (binaryStr string) { + if n == 0 { + return "0" + } + + for n > 0 { + binaryStr = strconv.Itoa(n%2) + binaryStr + n /= 2 + } + + return +} diff --git a/exercise1/problem3/main.go b/exercise1/problem3/main.go index d346641a..c7d52ff0 100644 --- a/exercise1/problem3/main.go +++ b/exercise1/problem3/main.go @@ -1,3 +1,9 @@ package main -func numberSquares() {} +func numberSquares(n int) int { + sum := 0 + for i := 1; i <= n; i++ { + sum += i * i + } + return sum +} diff --git a/exercise1/problem4/main.go b/exercise1/problem4/main.go index 74af9044..7a13f7da 100644 --- a/exercise1/problem4/main.go +++ b/exercise1/problem4/main.go @@ -1,3 +1,14 @@ package main -func detectWord() {} +import ( + "unicode" +) + +func detectWord(word string) (detectWord string) { + for _, char := range word { + if unicode.IsLower(char) { + detectWord += string(char) + } + } + return +} diff --git a/exercise1/problem5/main.go b/exercise1/problem5/main.go index c5a804c9..eb4cd0a6 100644 --- a/exercise1/problem5/main.go +++ b/exercise1/problem5/main.go @@ -1,3 +1,7 @@ package main -func potatoes() {} +import "strings" + +func potatoes(word string) int { + return strings.Count(word, "potato") +} diff --git a/exercise1/problem6/main.go b/exercise1/problem6/main.go index 06043890..eb87a9c5 100644 --- a/exercise1/problem6/main.go +++ b/exercise1/problem6/main.go @@ -1,3 +1,19 @@ package main -func emojify() {} +import "strings" + +func emojify(word string) string { + if strings.Contains(word, "smile") { + word = strings.ReplaceAll(word, "smile", "🙂") + } + if strings.Contains(word, "grin") { + word = strings.ReplaceAll(word, "grin", "😀") + } + if strings.Contains(word, "sad") { + word = strings.ReplaceAll(word, "sad", "😥") + } + if strings.Contains(word, "mad") { + word = strings.ReplaceAll(word, "mad", "😠") + } + return word +} diff --git a/exercise1/problem7/main.go b/exercise1/problem7/main.go index 57c99b5c..addb6313 100644 --- a/exercise1/problem7/main.go +++ b/exercise1/problem7/main.go @@ -1,3 +1,11 @@ package main -func highestDigit() {} +func highestDigit(n int) (maxDigit int) { + for n > 0 { + if n%10 > maxDigit { + maxDigit = n % 10 + } + n /= 10 + } + return +} diff --git a/exercise1/problem8/main.go b/exercise1/problem8/main.go index 97fa0dae..ac7d7229 100644 --- a/exercise1/problem8/main.go +++ b/exercise1/problem8/main.go @@ -1,3 +1,11 @@ package main -func countVowels() {} +func countVowels(word string) (count int) { + for _, r := range word { + switch r { + case 'a', 'e', 'i', 'o', 'u': + count++ + } + } + return +} diff --git a/exercise1/problem9/main.go b/exercise1/problem9/main.go index e8c84a54..7aa6e484 100644 --- a/exercise1/problem9/main.go +++ b/exercise1/problem9/main.go @@ -1,7 +1,13 @@ package main -func bitwiseAND() {} +func bitwiseAND(n, m int) int { + return n & m +} -func bitwiseOR() {} +func bitwiseOR(n, m int) int { + return n | m +} -func bitwiseXOR() {} +func bitwiseXOR(n, m int) int { + return n ^ m +} diff --git a/exercise2/problem1/problem1.go b/exercise2/problem1/problem1.go index 4763006c..a7a885f6 100644 --- a/exercise2/problem1/problem1.go +++ b/exercise2/problem1/problem1.go @@ -1,4 +1,12 @@ package problem1 -func isChangeEnough() { +func isChangeEnough(changes [4]int, total float32) bool { + coinValues := [4]float32{0.25, 0.10, 0.05, 0.01} + var dollars float32 + + for i, coin := range changes { + dollars += float32(coin) * coinValues[i] + } + + return dollars >= total } diff --git a/exercise2/problem10/problem10.go b/exercise2/problem10/problem10.go index 7142a022..cde772a1 100644 --- a/exercise2/problem10/problem10.go +++ b/exercise2/problem10/problem10.go @@ -1,3 +1,11 @@ package problem10 -func factory() {} +func factory() (map[string]int, func(string) func(int)) { + brandInfo := make(map[string]int) + return brandInfo, func(brandName string) func(int) { + brandInfo[brandName] = 0 + return func(incrementValue int) { + brandInfo[brandName] += incrementValue + } + } +} diff --git a/exercise2/problem11/problem11.go b/exercise2/problem11/problem11.go index 33988711..66aa7860 100644 --- a/exercise2/problem11/problem11.go +++ b/exercise2/problem11/problem11.go @@ -1,3 +1,13 @@ package problem11 -func removeDups() {} +func removeDups[T comparable](slice []T) []T { + result := []T{} + seen := map[T]struct{}{} + for _, value := range slice { + if _, ok := seen[value]; !ok { + seen[value] = struct{}{} + result = append(result, value) + } + } + return result +} diff --git a/exercise2/problem12/problem12.go b/exercise2/problem12/problem12.go index 4c1ae327..b2933886 100644 --- a/exercise2/problem12/problem12.go +++ b/exercise2/problem12/problem12.go @@ -1,3 +1,11 @@ package problem11 -func keysAndValues() {} +func keysAndValues[K comparable, V comparable](inp map[K]V) ([]K, []V) { + keys := []K{} + values := []V{} + for key, value := range inp { + keys = append(keys, key) + values = append(values, value) + } + return keys, values +} diff --git a/exercise2/problem2/problem2.go b/exercise2/problem2/problem2.go index fdb199f0..c2a429a0 100644 --- a/exercise2/problem2/problem2.go +++ b/exercise2/problem2/problem2.go @@ -1,4 +1,14 @@ package problem2 -func capitalize() { +import ( + "strings" +) + +func capitalize(names []string) []string { + for i, name := range names { + if len(name) > 0 { + names[i] = strings.ToUpper(string(name[0])) + strings.ToLower(name[1:]) + } + } + return names } diff --git a/exercise2/problem3/problem3.go b/exercise2/problem3/problem3.go index f183fafb..3d41a5fd 100644 --- a/exercise2/problem3/problem3.go +++ b/exercise2/problem3/problem3.go @@ -9,5 +9,23 @@ const ( lr dir = "lr" ) -func diagonalize() { +func diagonalize(n int, d dir) [][]int { + exp := make([][]int, n) + + for i := 0; i < n; i++ { + exp[i] = make([]int, n) + for j := 0; j < n; j++ { + switch d { + case "ul": + exp[i][j] = i + j + case "ll": + exp[i][j] = n - i + j - 1 + case "ur": + exp[i][j] = n + i - j - 1 + case "lr": + exp[i][j] = (n - i - 1) + (n - j - 1) + } + } + } + return exp } diff --git a/exercise2/problem4/problem4.go b/exercise2/problem4/problem4.go index 1f680a4d..d79ffdcc 100644 --- a/exercise2/problem4/problem4.go +++ b/exercise2/problem4/problem4.go @@ -1,4 +1,11 @@ package problem4 -func mapping() { +import "strings" + +func mapping(inp []string) map[string]string { + exp := make(map[string]string) + for _, value := range inp { + exp[value] = strings.ToUpper(value) + } + return exp } diff --git a/exercise2/problem5/problem5.go b/exercise2/problem5/problem5.go index 43fb96a4..3620d1fc 100644 --- a/exercise2/problem5/problem5.go +++ b/exercise2/problem5/problem5.go @@ -1,4 +1,18 @@ package problem5 -func products() { +import "sort" + +func products(catalog map[string]int, minPrice int) (exp []string) { + for product, price := range catalog { + if price >= minPrice { + exp = append(exp, product) + } + } + sort.Slice(exp, func(i, j int) bool { + if catalog[exp[i]] == catalog[exp[j]] { + return exp[i] < exp[j] + } + return catalog[exp[i]] > catalog[exp[j]] + }) + return } diff --git a/exercise2/problem6/problem6.go b/exercise2/problem6/problem6.go index 89fc5bfe..4fcf4194 100644 --- a/exercise2/problem6/problem6.go +++ b/exercise2/problem6/problem6.go @@ -1,4 +1,12 @@ package problem6 -func sumOfTwo() { +func sumOfTwo(a, b []int, sum int) bool { + for _, i := range a { + for _, j := range b { + if i+j == sum { + return true + } + } + } + return false } diff --git a/exercise2/problem7/problem7.go b/exercise2/problem7/problem7.go index 32514209..79f51068 100644 --- a/exercise2/problem7/problem7.go +++ b/exercise2/problem7/problem7.go @@ -1,4 +1,5 @@ package problem7 -func swap() { +func swap(a, b *int) { + *a, *b = *b, *a } diff --git a/exercise2/problem8/problem8.go b/exercise2/problem8/problem8.go index 9389d3b0..b28a0682 100644 --- a/exercise2/problem8/problem8.go +++ b/exercise2/problem8/problem8.go @@ -1,16 +1,13 @@ package problem8 func simplify(list []string) map[string]int { - var indMap map[string]int - - indMap = make(map[string]int) - load(&indMap, &list) - + indMap := make(map[string]int) + load(indMap, list) return indMap } -func load(m *map[string]int, students *[]string) { - for i, name := range *students { - (*m)[name] = i +func load(m map[string]int, students []string) { + for i, name := range students { + m[name] = i } } diff --git a/exercise2/problem9/problem9.go b/exercise2/problem9/problem9.go index fc96d21a..6d787ac4 100644 --- a/exercise2/problem9/problem9.go +++ b/exercise2/problem9/problem9.go @@ -1,3 +1,13 @@ package problem9 -func factory() {} +type resultType func(...int) []int + +func factory(multiple int) resultType { + return func(list ...int) []int { + result := make([]int, len(list)) + for i, value := range list { + result[i] = value * multiple + } + return result + } +} diff --git a/exercise3/problem1/problem1.go b/exercise3/problem1/problem1.go index d45605c6..1011ad35 100644 --- a/exercise3/problem1/problem1.go +++ b/exercise3/problem1/problem1.go @@ -1,3 +1,35 @@ package problem1 -type Queue struct{} +import "errors" + +type Queue struct { + items []interface{} +} + +func (q *Queue) Enqueue(item interface{}) { + q.items = append(q.items, item) +} + +func (q *Queue) Dequeue() (interface{}, error) { + if q.IsEmpty() { + return nil, errors.New("Queue is empty") + } + item := q.items[0] + q.items = q.items[1:] + return item, nil +} + +func (q *Queue) Peek() (interface{}, error) { + if q.IsEmpty() { + return nil, errors.New("Queue is empty") + } + return q.items[0], nil +} + +func (q *Queue) Size() int { + return len(q.items) +} + +func (q *Queue) IsEmpty() bool { + return len(q.items) == 0 +} diff --git a/exercise3/problem2/problem2.go b/exercise3/problem2/problem2.go index e9059889..4cb503c6 100644 --- a/exercise3/problem2/problem2.go +++ b/exercise3/problem2/problem2.go @@ -1,3 +1,36 @@ package problem2 -type Stack struct{} +import "errors" + +type Stack struct { + items []interface{} +} + +func (s *Stack) Push(item interface{}) { + s.items = append(s.items, item) +} + +func (s *Stack) Pop() (interface{}, error) { + if s.IsEmpty() { + return nil, errors.New("Stack is empty") + } + lastIndex := s.Size() - 1 + item := s.items[lastIndex] + s.items = s.items[:lastIndex] + return item, nil +} + +func (s *Stack) Peek() (interface{}, error) { + if s.IsEmpty() { + return nil, errors.New("Stack is empty") + } + return s.items[s.Size()-1], nil +} + +func (s *Stack) Size() int { + return len(s.items) +} + +func (s *Stack) IsEmpty() bool { + return s.Size() == 0 +} diff --git a/exercise3/problem3/problem3.go b/exercise3/problem3/problem3.go index d8d79ac0..a292d7df 100644 --- a/exercise3/problem3/problem3.go +++ b/exercise3/problem3/problem3.go @@ -1,3 +1,106 @@ package problem3 -type Set struct{} +type Set struct { + items map[interface{}]struct{} +} + +func NewSet() *Set { + return &Set{ + items: make(map[interface{}]struct{}), + } +} + +func (s *Set) Add(item interface{}) { + s.items[item] = struct{}{} +} + +func (s *Set) Remove(item interface{}) { + delete(s.items, item) +} + +func (s *Set) IsEmpty() bool { + return len(s.items) == 0 +} + +func (s *Set) Size() int { + return len(s.items) +} + +func (s *Set) Has(item interface{}) bool { + _, exists := s.items[item] + return exists +} + +func (s *Set) Copy() *Set { + resultSet := NewSet() + for key := range s.items { + resultSet.Add(key) + } + return resultSet +} + +func (s *Set) Difference(r *Set) *Set { + resultSet := NewSet() + for key := range s.items { + if !r.Has(key) { + resultSet.Add(key) + } + } + return resultSet +} + +func (s *Set) IsSubset(r *Set) bool { + for key := range s.items { + if !r.Has(key) { + return false + } + } + return true +} + +func (s *Set) List() []interface{} { + result := []interface{}{} + for key := range s.items { + result = append(result, key) + } + return result +} + +func Union(sets ...*Set) *Set { + resultSet := NewSet() + for _, set := range sets { + for key := range set.items { + resultSet.Add(key) + } + } + return resultSet +} + +func Intersect(sets ...*Set) *Set { + if len(sets) == 0 { + return NewSet() + } + + minSet := sets[0] + for _, set := range sets[1:] { + if set.Size() < minSet.Size() { + minSet = set + } + } + + resultSet := NewSet() + for key := range minSet.items { + isCommon := true + for _, set := range sets { + if !set.Has(key) { + isCommon = false + break + } + } + if isCommon { + resultSet.Add(key) + } + } + + return resultSet +} diff --git a/exercise3/problem4/README.md b/exercise3/problem4/README.md index dd6ea6bc..35c0b15c 100644 --- a/exercise3/problem4/README.md +++ b/exercise3/problem4/README.md @@ -32,4 +32,8 @@ Get the size of the linked list. Check if the linked list is empty. +- _Reverse_ + +Reverse linked list by changing the links between elements + ! Please check the tests for signature and more examples. diff --git a/exercise3/problem4/problem4.go b/exercise3/problem4/problem4.go index ebf78147..d3ae48dd 100644 --- a/exercise3/problem4/problem4.go +++ b/exercise3/problem4/problem4.go @@ -1,3 +1,147 @@ package problem4 -type LinkedList struct{} +import ( + "errors" +) + +type Element[T comparable] struct { + value T + next *Element[T] +} + +type LinkedList[T comparable] struct { + head *Element[T] + tail *Element[T] + size int +} + +func (l *LinkedList[T]) Add(e *Element[T]) { + if l.head == nil { + l.head = e + l.tail = e + } else { + l.tail.next = e + l.tail = e + } + l.size++ +} + +func (l *LinkedList[T]) Size() int { + return l.size +} + +func (l *LinkedList[T]) Insert(e *Element[T], index int) error { + if index < 0 || index > l.size { + return errors.New("index out of range") + } + + if index == 0 { + e.next = l.head + l.head = e + if l.size == 0 { + l.tail = e + } + } else { + prev, err := l.findPreviousByIndex(index - 1) + if err != nil { + return err + } + e.next = prev.next + prev.next = e + if e.next == nil { + l.tail = e + } + } + l.size++ + return nil +} + +func (l *LinkedList[T]) Delete(e *Element[T]) error { + if l.IsEmpty() { + return errors.New("linkedlist is empty") + } + + if l.head.value == e.value { + l.head = l.head.next + if l.head == nil { + l.tail = nil + } + } else { + prev, err := l.findPrevious(e.value) + if err != nil { + return err + } + prev.next = prev.next.next + if prev.next == nil { + l.tail = prev + } + } + l.size-- + return nil +} + +func (l *LinkedList[T]) Find(value T) (*Element[T], error) { + current := l.head + for current != nil { + if current.value == value { + return current, nil + } + current = current.next + } + return nil, errors.New("value not found") +} + +func (l *LinkedList[T]) List() []T { + result := make([]T, 0, l.size) + for current := l.head; current != nil; current = current.next { + result = append(result, current.value) + } + return result +} + +func (l *LinkedList[T]) IsEmpty() bool { + return l.size == 0 +} + +func (l *LinkedList[T]) findPreviousByIndex(index int) (*Element[T], error) { + if index < 0 || index >= l.size { + return nil, errors.New("index out of range") + } + + current := l.head + for i := 0; i < index; i++ { + current = current.next + } + return current, nil +} + +func (l *LinkedList[T]) findPrevious(value T) (*Element[T], error) { + if l.head == nil || l.head.value == value { + return nil, errors.New("value not found") + } + + current := l.head + for current.next != nil && current.next.value != value { + current = current.next + } + + if current.next == nil { + return nil, errors.New("value not found") + } + return current, nil +} + +func (l *LinkedList[T]) Reverse() { + var prev, next *Element[T] + current := l.head + l.tail = current + + for current != nil { + next = current.next + current.next = prev + prev = current + current = next + } + + l.head = prev +} diff --git a/exercise3/problem4/problem4_test.go b/exercise3/problem4/problem4_test.go index a8c68499..baee2e43 100644 --- a/exercise3/problem4/problem4_test.go +++ b/exercise3/problem4/problem4_test.go @@ -451,4 +451,49 @@ func TestLinkedList(t *testing.T) { } }) }) + + t.Run("Reverse", func(t *testing.T) { + table := []struct { + list []any + expected []any + }{ + { + []any{1, 2, 3, 4, 5}, + []any{5, 4, 3, 2, 1}, + }, + { + []any{"a", "b", "c", "d", "e"}, + []any{"e", "d", "c", "b", "a"}, + }, + { + []any{true, false}, + []any{false, true}, + }, + } + + for _, r := range table { + ll := &LinkedList[any]{} + for _, value := range r.list { + ll.Add( + &Element[any]{ + value: value, + }, + ) + } + + ll.Reverse() + + actual := ll.List() + if len(actual) != len(r.expected) { + t.Errorf("Reverse(%v) was incorrect, got size: %d, expected size: %d", r.list, len(actual), len(r.expected)) + } + + for i := 0; i < len(actual); i++ { + if actual[i] != r.expected[i] { + t.Errorf("Reverse(%v) was incorrect, actual value: %v, expect value: %v", r.list, actual[i], r.expected[i]) + } + } + } + + }) } diff --git a/exercise3/problem5/problem5.go b/exercise3/problem5/problem5.go index 4177599f..c490a487 100644 --- a/exercise3/problem5/problem5.go +++ b/exercise3/problem5/problem5.go @@ -1,3 +1,20 @@ package problem5 -type Person struct{} +import "fmt" + +type Person struct { + name string + age int +} + +func (me *Person) compareAge(other *Person) string { + if other.age > me.age { + return fmt.Sprintf("%s is older than me.", other.name) + } + + if other.age < me.age { + return fmt.Sprintf("%s is younger than me.", other.name) + } + + return fmt.Sprintf("%s is the same age as me.", other.name) +} diff --git a/exercise3/problem6/problem6.go b/exercise3/problem6/problem6.go index 4e8d1af8..9a0fa6e5 100644 --- a/exercise3/problem6/problem6.go +++ b/exercise3/problem6/problem6.go @@ -1,7 +1,31 @@ package problem6 -type Animal struct{} +type Animal struct { + name string + legsNum int +} -type Insect struct{} +func (a *Animal) GetLegs() int { + return a.legsNum +} -func sumOfAllLegsNum() {} +type Insect struct { + name string + legsNum int +} + +func (i *Insect) GetLegs() int { + return i.legsNum +} + +type HasLegs interface { + GetLegs() int +} + +func sumOfAllLegsNum(list ...HasLegs) int { + total := 0 + for _, l := range list { + total += l.GetLegs() + } + return total +} diff --git a/exercise3/problem7/problem7.go b/exercise3/problem7/problem7.go index 26887151..d1773d93 100644 --- a/exercise3/problem7/problem7.go +++ b/exercise3/problem7/problem7.go @@ -1,10 +1,54 @@ package problem7 +import "fmt" + +type Treasury interface { + withdraw(int) +} + +type Post interface { + sendTo(string) +} + type BankAccount struct { + name string + balance int +} + +func (b *BankAccount) withdraw(amount int) { + b.balance -= amount } type FedexAccount struct { + name string + packages []string +} + +func (f *FedexAccount) sendTo(name string) { + f.packages = append(f.packages, fmt.Sprintf("%s send package to %s", f.name, name)) } type KazPostAccount struct { + name string + balance int + packages []string +} + +func (k *KazPostAccount) withdraw(amount int) { + k.balance -= amount +} + +func (k *KazPostAccount) sendTo(name string) { + k.packages = append(k.packages, fmt.Sprintf("%s send package to %s", k.name, name)) +} + +func withdrawMoney(amount int, accounts ...Treasury) { + for _, account := range accounts { + account.withdraw(amount) + } +} +func sendPackagesTo(name string, posts ...Post) { + for _, post := range posts { + post.sendTo(name) + } } diff --git a/exercise5/problem1/problem1.go b/exercise5/problem1/problem1.go index 4f514fab..8e71df1a 100644 --- a/exercise5/problem1/problem1.go +++ b/exercise5/problem1/problem1.go @@ -1,9 +1,16 @@ package problem1 +import "sync" + func incrementConcurrently(num int) int { + wg := new(sync.WaitGroup) + wg.Add(1) + go func() { + defer wg.Done() num++ }() + wg.Wait() return num } diff --git a/exercise5/problem2/problem2.go b/exercise5/problem2/problem2.go index 16d38e1d..b568b2fc 100644 --- a/exercise5/problem2/problem2.go +++ b/exercise5/problem2/problem2.go @@ -1,5 +1,10 @@ package problem2 +import ( + "runtime" + "sync" +) + // add - sequential code to add numbers, don't update it, just to illustrate concept func add(numbers []int) int64 { var sum int64 @@ -10,7 +15,41 @@ func add(numbers []int) int64 { } func addConcurrently(numbers []int) int64 { + cpuCount := runtime.NumCPU() + runtime.GOMAXPROCS(cpuCount) + chunkSize := len(numbers) / cpuCount + resultCh := make(chan int, cpuCount) + wg := new(sync.WaitGroup) + var sum int64 + for i := 0; i < cpuCount; i++ { + start := i * chunkSize + end := start + chunkSize + if i == cpuCount-1 { + end = len(numbers) + } + wg.Add(1) + go processAddChunks(numbers[start:end], resultCh, wg) + } + + go func() { + wg.Wait() + close(resultCh) + }() + + for result := range resultCh { + sum += int64(result) + } + return sum } + +func processAddChunks(chunk []int, resultCh chan<- int, wg *sync.WaitGroup) { + defer wg.Done() + result := 0 + for _, num := range chunk { + result += num + } + resultCh <- result +} diff --git a/exercise5/problem3/problem3.go b/exercise5/problem3/problem3.go index e085a51a..a261335c 100644 --- a/exercise5/problem3/problem3.go +++ b/exercise5/problem3/problem3.go @@ -1,11 +1,11 @@ package problem3 func sum(a, b int) int { - var c int + ch := make(chan int) go func(a, b int) { - c = a + b + ch <- a + b }(a, b) - return c + return <-ch } diff --git a/exercise5/problem4/problem4.go b/exercise5/problem4/problem4.go index b5899ddf..f8457faa 100644 --- a/exercise5/problem4/problem4.go +++ b/exercise5/problem4/problem4.go @@ -1,6 +1,7 @@ package problem4 func iter(ch chan<- int, nums []int) { + defer close(ch) for _, n := range nums { ch <- n } diff --git a/exercise5/problem5/problem5.go b/exercise5/problem5/problem5.go index ac192c58..16c45d89 100644 --- a/exercise5/problem5/problem5.go +++ b/exercise5/problem5/problem5.go @@ -1,8 +1,25 @@ package problem5 -func producer() {} +import "fmt" -func consumer() {} +func producer(words []string, ch chan<- string) { + defer close(ch) + for _, word := range words { + ch <- word + } +} + +func consumer(ch <-chan string) string { + var result string + for word := range ch { + if result == "" { + result += word + } else { + result += fmt.Sprintf(" %s", word) + } + } + return result +} func send( words []string, diff --git a/exercise5/problem6/problem6.go b/exercise5/problem6/problem6.go index e1beea87..0b0d6691 100644 --- a/exercise5/problem6/problem6.go +++ b/exercise5/problem6/problem6.go @@ -2,8 +2,33 @@ package problem6 type pipe func(in <-chan int) <-chan int -var multiplyBy2 pipe = func() {} +var multiplyBy2 pipe = func(in <-chan int) <-chan int { + out := make(chan int) + go func() { + for v := range in { + out <- v * 2 + } + close(out) + }() + return out +} -var add5 pipe = func() {} +var add5 pipe = func(in <-chan int) <-chan int { + out := make(chan int) + go func() { + for v := range in { + out <- v + 5 + } + close(out) + }() + return out +} -func piper(in <-chan int, pipes []pipe) <-chan int {} +func piper(in <-chan int, pipes []pipe) <-chan int { + var out <-chan int + for _, pipe := range pipes { + out = pipe(in) + in = out + } + return out +} diff --git a/exercise5/problem7/problem7.go b/exercise5/problem7/problem7.go index c3c1d0c9..267c4842 100644 --- a/exercise5/problem7/problem7.go +++ b/exercise5/problem7/problem7.go @@ -1,3 +1,25 @@ package problem7 -func multiplex(ch1 <-chan string, ch2 <-chan string) []string {} +import "sync" + +func multiplex(channels ...<-chan string) []string { + result := []string{} + wg := new(sync.WaitGroup) + wg.Add(len(channels)) + mx := new(sync.Mutex) + + for _, ch := range channels { + go func(c <-chan string) { + defer wg.Done() + for v := range c { + mx.Lock() + result = append(result, v) + mx.Unlock() + } + }(ch) + } + + wg.Wait() + + return result +} diff --git a/exercise5/problem8/problem8.go b/exercise5/problem8/problem8.go index 3e951b3b..bb2643f0 100644 --- a/exercise5/problem8/problem8.go +++ b/exercise5/problem8/problem8.go @@ -4,4 +4,16 @@ import ( "time" ) -func withTimeout(ch <-chan string, ttl time.Duration) string {} +func withTimeout(ch <-chan string, ttl time.Duration) string { + for { + select { + case msg, ok := <-ch: + if !ok { + return "channel closed" + } + return msg + case <-time.After(ttl): + return "timeout" + } + } +} diff --git a/exercise6/problem1/problem1.go b/exercise6/problem1/problem1.go index ee453b24..ac4d481d 100644 --- a/exercise6/problem1/problem1.go +++ b/exercise6/problem1/problem1.go @@ -1,9 +1,26 @@ package problem1 +import "sync" + type bankAccount struct { blnc int + mx *sync.Mutex } func newAccount(blnc int) *bankAccount { - return &bankAccount{blnc} + return &bankAccount{blnc, &sync.Mutex{}} +} + +func (b *bankAccount) withdraw(amount int) { + defer b.mx.Unlock() + b.mx.Lock() + if b.blnc >= amount { + b.blnc -= amount + } +} + +func (b *bankAccount) deposit(amount int) { + defer b.mx.Unlock() + b.mx.Lock() + b.blnc += amount } diff --git a/exercise6/problem2/problem2.go b/exercise6/problem2/problem2.go index 97e02368..037de66e 100644 --- a/exercise6/problem2/problem2.go +++ b/exercise6/problem2/problem2.go @@ -1,6 +1,7 @@ package problem2 import ( + "sync" "time" ) @@ -8,13 +9,30 @@ var readDelay = 10 * time.Millisecond type bankAccount struct { blnc int + mx *sync.RWMutex } func newAccount(blnc int) *bankAccount { - return &bankAccount{blnc} + return &bankAccount{blnc, &sync.RWMutex{}} } func (b *bankAccount) balance() int { time.Sleep(readDelay) - return 0 + defer b.mx.RUnlock() + b.mx.RLock() + return b.blnc +} + +func (b *bankAccount) withdraw(amount int) { + defer b.mx.Unlock() + b.mx.Lock() + if b.blnc >= amount { + b.blnc -= amount + } +} + +func (b *bankAccount) deposit(amount int) { + defer b.mx.Unlock() + b.mx.Lock() + b.blnc += amount } diff --git a/exercise6/problem3/problem3.go b/exercise6/problem3/problem3.go index b34b90bb..acb946e2 100644 --- a/exercise6/problem3/problem3.go +++ b/exercise6/problem3/problem3.go @@ -1,5 +1,7 @@ package problem3 +import "sync/atomic" + type counter struct { val int64 } @@ -9,3 +11,15 @@ func newCounter() *counter { val: 0, } } + +func (c *counter) inc() { + atomic.AddInt64(&c.val, 1) +} + +func (c *counter) dec() { + atomic.AddInt64(&c.val, -1) +} + +func (c *counter) value() int64 { + return atomic.LoadInt64(&c.val) +} diff --git a/exercise6/problem4/problem4.go b/exercise6/problem4/problem4.go index 793449c9..7f53e5ef 100644 --- a/exercise6/problem4/problem4.go +++ b/exercise6/problem4/problem4.go @@ -1,31 +1,44 @@ package problem4 import ( + "sync" "time" ) -func worker(id int, _ *[]string, ch chan<- int) { - // TODO wait for shopping list to be completed +func worker(id int, shoppingList *[]string, ch chan<- int, cond *sync.Cond) { + defer cond.L.Unlock() + cond.L.Lock() + + if len(*shoppingList) == 0 { + cond.Wait() + } + ch <- id } -func updateShopList(shoppingList *[]string) { +func updateShopList(shoppingList *[]string, cond *sync.Cond) { time.Sleep(10 * time.Millisecond) + defer cond.L.Unlock() + cond.L.Lock() + *shoppingList = append(*shoppingList, "apples") *shoppingList = append(*shoppingList, "milk") *shoppingList = append(*shoppingList, "bake soda") + + cond.Signal() } func notifyOnShopListUpdate(shoppingList *[]string, numWorkers int) <-chan int { notifier := make(chan int) + cond := &sync.Cond{L: &sync.Mutex{}} for i := range numWorkers { - go worker(i+1, shoppingList, notifier) + go worker(i+1, shoppingList, notifier, cond) time.Sleep(time.Millisecond) // order matters } - go updateShopList(shoppingList) + go updateShopList(shoppingList, cond) return notifier } diff --git a/exercise6/problem5/problem5.go b/exercise6/problem5/problem5.go index 8e4a1703..a54d8522 100644 --- a/exercise6/problem5/problem5.go +++ b/exercise6/problem5/problem5.go @@ -1,31 +1,42 @@ package problem5 import ( + "sync" "time" ) -func worker(id int, shoppingList *[]string, ch chan<- int) { - // TODO wait for shopping list to be completed +func worker(id int, shoppingList *[]string, ch chan<- int, cond *sync.Cond) { + defer cond.L.Unlock() + cond.L.Lock() + for len(*shoppingList) == 0 { + cond.Wait() + } ch <- id } -func updateShopList(shoppingList *[]string) { +func updateShopList(shoppingList *[]string, cond *sync.Cond) { time.Sleep(10 * time.Millisecond) + defer cond.L.Unlock() + cond.L.Lock() + *shoppingList = append(*shoppingList, "apples") *shoppingList = append(*shoppingList, "milk") *shoppingList = append(*shoppingList, "bake soda") + + cond.Broadcast() } func notifyOnShopListUpdate(shoppingList *[]string, numWorkers int) <-chan int { notifier := make(chan int) + cond := &sync.Cond{L: &sync.Mutex{}} for i := range numWorkers { - go worker(i+1, shoppingList, notifier) + go worker(i+1, shoppingList, notifier, cond) time.Sleep(time.Millisecond) // order matters } - go updateShopList(shoppingList) + go updateShopList(shoppingList, cond) return notifier } diff --git a/exercise6/problem6/problem6.go b/exercise6/problem6/problem6.go index 0c1122b9..fcbf38a7 100644 --- a/exercise6/problem6/problem6.go +++ b/exercise6/problem6/problem6.go @@ -6,14 +6,13 @@ import ( func runTasks(init func()) { var wg sync.WaitGroup + onceInit := sync.OnceFunc(init) for range 10 { wg.Add(1) go func() { defer wg.Done() - - //TODO: modify so that load function gets called only once. - init() + onceInit() }() } wg.Wait() diff --git a/exercise6/problem7/problem7.go b/exercise6/problem7/problem7.go index ef49497b..d54925e3 100644 --- a/exercise6/problem7/problem7.go +++ b/exercise6/problem7/problem7.go @@ -3,17 +3,27 @@ package problem7 import ( "fmt" "math/rand" + "sync" "time" ) func task() { start := time.Now() var t *time.Timer + mx := &sync.Mutex{} + t = time.AfterFunc( randomDuration(), func() { + defer mx.Unlock() + mx.Lock() fmt.Println(time.Now().Sub(start)) - t.Reset(randomDuration()) + + func() { + defer mx.Unlock() + mx.Lock() + t.Reset(randomDuration()) + }() }, ) time.Sleep(5 * time.Second) diff --git a/exercise6/problem8/problem8.go b/exercise6/problem8/problem8.go index 949eb2d2..6c6ef8b7 100644 --- a/exercise6/problem8/problem8.go +++ b/exercise6/problem8/problem8.go @@ -1,3 +1,30 @@ package problem8 -func multiplex(chs []<-chan string) []string {} +import "sync" + +func multiplex(chs []<-chan string) []string { + result := []string{} + wg := new(sync.WaitGroup) + resultCh := make(chan string) + + for _, ch := range chs { + wg.Add(1) + go func(c <-chan string) { + defer wg.Done() + for v := range c { + resultCh <- v + } + }(ch) + } + + go func() { + wg.Wait() + close(resultCh) + }() + + for v := range resultCh { + result = append(result, v) + } + + return result +} diff --git a/exercise7/blogging-platform/README.md b/exercise7/blogging-platform/README.md new file mode 100644 index 00000000..e6ef7017 --- /dev/null +++ b/exercise7/blogging-platform/README.md @@ -0,0 +1,3 @@ +# Blogging Platform + +Please check https://roadmap.sh/projects/blogging-platform-api. diff --git a/exercise7/blogging-platform/go.mod b/exercise7/blogging-platform/go.mod new file mode 100644 index 00000000..ca16e703 --- /dev/null +++ b/exercise7/blogging-platform/go.mod @@ -0,0 +1,5 @@ +module github.com/talgat-ruby/exercises-go/exercise7/blogging-platform + +go 1.23.3 + +require github.com/lib/pq v1.10.9 diff --git a/exercise7/blogging-platform/go.sum b/exercise7/blogging-platform/go.sum new file mode 100644 index 00000000..aeddeae3 --- /dev/null +++ b/exercise7/blogging-platform/go.sum @@ -0,0 +1,2 @@ +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= diff --git a/exercise7/blogging-platform/main.go b/exercise7/blogging-platform/main.go new file mode 100644 index 00000000..1ffa1477 --- /dev/null +++ b/exercise7/blogging-platform/main.go @@ -0,0 +1,49 @@ +package main + +import ( + "context" + "log/slog" + "os" + "os/signal" + + "github.com/talgat-ruby/exercises-go/exercise7/blogging-platform/internal/api" + "github.com/talgat-ruby/exercises-go/exercise7/blogging-platform/internal/db" +) + +func main() { + ctx, cancel := context.WithCancel(context.Background()) + + // db + _, err := db.New() + if err != nil { + slog.ErrorContext( + ctx, + "initialize service error", + "service", "db", + "error", err, + ) + panic(err) + } + + // api + a := api.New() + if err := a.Start(ctx); err != nil { + slog.ErrorContext( + ctx, + "initialize service error", + "service", "api", + "error", err, + ) + panic(err) + } + + go func() { + shutdown := make(chan os.Signal, 1) // Create channel to signify s signal being sent + signal.Notify(shutdown, os.Interrupt) // When an interrupt is sent, notify the channel + + sig := <-shutdown + slog.WarnContext(ctx, "signal received - shutting down...", "signal", sig) + + cancel() + }() +} diff --git a/exercise7/blogging-platform/pkg/httputils/request/body.go b/exercise7/blogging-platform/pkg/httputils/request/body.go new file mode 100644 index 00000000..92d639f4 --- /dev/null +++ b/exercise7/blogging-platform/pkg/httputils/request/body.go @@ -0,0 +1,76 @@ +package request + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "strings" + + "github.com/talgat-ruby/exercises-go/exercise7/blogging-platform/pkg/httputils/statusError" +) + +func JSON(w http.ResponseWriter, r *http.Request, dst interface{}) error { + ct := r.Header.Get("Content-Type") + if ct != "" { + mediaType := strings.ToLower(strings.TrimSpace(strings.Split(ct, ";")[0])) + if mediaType != "application/json" { + msg := "Content-Type header is not application/json" + return statusError.New(http.StatusUnsupportedMediaType, msg) + } + } + + r.Body = http.MaxBytesReader(w, r.Body, 1048576) + + dec := json.NewDecoder(r.Body) + dec.DisallowUnknownFields() + + err := dec.Decode(&dst) + if err != nil { + var syntaxError *json.SyntaxError + var unmarshalTypeError *json.UnmarshalTypeError + + switch { + case errors.As(err, &syntaxError): + msg := fmt.Sprintf("Request body contains badly-formed JSON (at position %d)", syntaxError.Offset) + return statusError.New(http.StatusBadRequest, msg) + + case errors.Is(err, io.ErrUnexpectedEOF): + msg := "Request body contains badly-formed JSON" + return statusError.New(http.StatusBadRequest, msg) + + case errors.As(err, &unmarshalTypeError): + msg := fmt.Sprintf( + "Request body contains an invalid value for the %q field (at position %d)", + unmarshalTypeError.Field, + unmarshalTypeError.Offset, + ) + return statusError.New(http.StatusBadRequest, msg) + + case strings.HasPrefix(err.Error(), "json: unknown field "): + fieldName := strings.TrimPrefix(err.Error(), "json: unknown field ") + msg := fmt.Sprintf("Request body contains unknown field %s", fieldName) + return statusError.New(http.StatusBadRequest, msg) + + case errors.Is(err, io.EOF): + msg := "Request body must not be empty" + return statusError.New(http.StatusBadRequest, msg) + + case err.Error() == "http: request body too large": + msg := "Request body must not be larger than 1MB" + return statusError.New(http.StatusRequestEntityTooLarge, msg) + + default: + return err + } + } + + err = dec.Decode(&struct{}{}) + if !errors.Is(err, io.EOF) { + msg := "Request body must only contain a single JSON object" + return statusError.New(http.StatusBadRequest, msg) + } + + return nil +} diff --git a/exercise7/blogging-platform/pkg/httputils/response/body.go b/exercise7/blogging-platform/pkg/httputils/response/body.go new file mode 100644 index 00000000..e1fd78a8 --- /dev/null +++ b/exercise7/blogging-platform/pkg/httputils/response/body.go @@ -0,0 +1,33 @@ +package response + +import ( + "encoding/json" + "fmt" + "net/http" +) + +type DataResponse struct { + Data interface{} `json:"data"` +} + +func JSON(w http.ResponseWriter, status int, data interface{}) error { + if data == nil { + w.WriteHeader(http.StatusNoContent) + return nil + } + + js, err := json.Marshal(data) + if err != nil { + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return fmt.Errorf("JSON marshal error: %w", err) + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(status) + if _, err := w.Write(js); err != nil { + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return fmt.Errorf("writer error: %w", err) + } + + return nil +} diff --git a/exercise7/blogging-platform/pkg/httputils/statusError/main.go b/exercise7/blogging-platform/pkg/httputils/statusError/main.go new file mode 100644 index 00000000..6cf4e1b6 --- /dev/null +++ b/exercise7/blogging-platform/pkg/httputils/statusError/main.go @@ -0,0 +1,18 @@ +package statusError + +type StatusError struct { + status int + msg string +} + +func New(status int, msg string) error { + return &StatusError{status, msg} +} + +func (st *StatusError) Error() string { + return st.msg +} + +func (st *StatusError) Status() int { + return st.status +} diff --git a/exercise9/README.md b/exercise9/README.md new file mode 100644 index 00000000..ef1d468e --- /dev/null +++ b/exercise9/README.md @@ -0,0 +1,14 @@ +# Exercise 9 + +Project + +## Teams + +Team 1 + +1. Тұрарова Айзада (api) +2. Манкенов Арай (api) +3. Усербай Асылбек (controller) +4. Кемалатдин Ғалымжан (controller) +5. Имангали Аскар (db) +6. Кабдылкак Арнур (db) diff --git a/exercise9/go.mod b/exercise9/go.mod new file mode 100644 index 00000000..72f28b6f --- /dev/null +++ b/exercise9/go.mod @@ -0,0 +1,3 @@ +module github.com/talgat-ruby/exercises-go/exercise9 + +go 1.23.5