## Популярные задачи на собеседованиях ### 1. На вход подаются два неупорядоченных слайса любой длины. Надо написать функцию, которая возвращает их пересечение Можно решить сортировкой, за более долгое время, но без выделения дополнительной памяти. А можно выделить дополнительную память и решить за линейное время. Надо посчитать количество появлений элементов первого массива (лучше брать тот, что покороче) — используем для этого словарь. Потом пройтись по второму массиву и вычитать из словаря те элементы, которые есть в нем. По ходу добавляем в результат те элементы, у которых частота появлений больше нуля. - [x] Советуем посетить [Математические операции над множествами](https://github.com/goavengers/go-datastructure#-point_right-%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%D0%B0-sets) ```go package main import ( "fmt" ) // На вход подаются два неупорядоченных массива любой длины. // Необходимо написать функцию, которая возвращает пересечение массивов func intersection(a, b []int) []int { counter := make(map[int]int) var result []int for _, elem := range a { counter[elem]++ } for _, elem := range b { if count, ok := counter[elem]; ok && count > 0 { counter[elem] -= 1 result = append(result, elem) } } return result } func main() { a := []int{23, 3, 1, 2} b := []int{6, 2, 4, 23} // [2, 23] fmt.Printf("%v\n", intersection(a, b)) a = []int{1, 1, 1} b = []int{1, 1, 1, 1} // [1, 1, 1] fmt.Printf("%v\n", intersection(a, b)) } ``` ### 2. Написать генератор случайных чисел В принципе, легкая задача, на базовые знания по асинхронному взаимодействию в Go. Для решения я бы использовал небуфферезированный канал. Будем асинхронно писать туда случайные числа и закроем его, когда закончим писать. Плюс ее можно использовать в немного измененном виде в задаче на [слияние N каналов](#3). ```go package main import ( "fmt" "math/rand" "time" ) func randNumsGenerator(n int) <-chan int { r := rand.New(rand.NewSource(time.Now().UnixNano())) out := make(chan int) go func() { for i := 0; i < n; i++ { out <- r.Intn(n) } close(out) }() return out } func main() { for num := range randNumsGenerator(10) { fmt.Println(num) } } ``` ### 3. Слить N каналов в один Даны n каналов типа chan int. Надо написать функцию, которая смерджит все данные из этих каналов в один и вернет его. Мы хотим, чтобы результат работы функции выглядел примерно так: ```go for num := range joinChannels(a, b, c) { fmt.Println(num) } ``` Для этого напишем функцию, которая будет асинхронно читать из исходных каналов, которые ей передадут в качестве аргументов, и писать в результирующий канал, который вернется из функции. - Создаем канал, куда будем сливать все данные. Он будет небуферезированный, потому что мы не знаем, сколько данных придет из каналов. - Дальше асинхронно прочитаем из исходных каналов и закроем результирующий канал для мерджа, когда все чтение закончится. - Чтобы дождаться конца чтения, просто обернем этот цикл по каналам в wait group. ```go package main import ( "sync" ) func joinChannels(chs ...<-chan int) <-chan int { mergedCh := make(chan int) go func() { wg := &sync.WaitGroup{} wg.Add(len(chs)) for _, ch := range chs { go func(ch <-chan int, wg *sync.WaitGroup) { defer wg.Done() for id := range ch { mergedCh <- id } }(ch, wg) } wg.Wait() close(mergedCh) }() return mergedCh } ``` ```go package main import ( "fmt" ) func main() { a := make(chan int) b := make(chan int) c := make(chan int) go func() { for _, num := range []int{1, 2, 3} { a <- num } close(a) }() go func() { for _, num := range []int{20, 10, 30} { b <- num } close(b) }() go func() { for _, num := range []int{300, 200, 100} { c <- num } close(c) }() for num := range joinChannels(a, b, c) { fmt.Println(num) } } ``` ### 4. Сделать конвейер чисел Даны два канала. В первый пишутся числа. Нужно, чтобы числа читались из первого по мере поступления, что-то с ними происходило (допустим, возводились в квадрат) и результат записывался во второй канал. Довольно частая задача, более подробно можно почитать [тут](https://blog.golang.org/pipelines). Решается довольно прямолинейно — запускаем две горутины. - В одной пишем в первый канал. - Во второй читаем из первого канала и пишем во второй. Главное — не забыть закрыть каналы, чтобы ничего нигде не заблокировалось. ```go package main import ( "fmt" ) func main() { naturals := make(chan int) squares := make(chan int) go func() { for x := 0; x <= 10; x++ { naturals <- x } close(naturals) }() go func() { for x := range naturals { squares <- x * x } close(squares) }() for x := range squares { fmt.Println(x) } } ``` ### 5. Написать WorkerPool с заданной функцией Довольно распространенная задача, плюс подобные задачи встречаются на практике. Нам нужно разбить процессы на несколько горутин — при этом не создавать новую горутину каждый раз, а просто переиспользовать уже имеющиеся. - Для этого создадим канал с джобами и результирующий канал. - Для каждого воркера создадим горутину, который будет ждать новую джобу, применять к ней заданную функцию и пулять ответ в результирующий канал. ```go package main import ( "fmt" ) func worker(id int, f func(int) int, jobs <-chan int, results chan<- int) { for j := range jobs { results <- f(j) } } func main() { const numJobs = 5 jobs := make(chan int, numJobs) results := make(chan int, numJobs) multiplier := func(x int) int { return x * 10 } for w := 1; w <= 3; w++ { go worker(w, multiplier, jobs, results) } for j := 1; j <= numJobs; j++ { jobs <- j } close(jobs) for i := 1; i <= numJobs; i++ { fmt.Println(<-results) } } ``` ### 6. Сделать кастомную waitGroup на семафоре Семафор можно легко получить из канала. Чтоб не аллоцировать лишние данные, будем складывать туда пустые структуры. В нашем случае мы хотим сделать семафор, который будет ждать выполнения пяти горутин. - Для этого просто добавим вместо обычного канала буфферизированный. - И внутри каждой горутины положим в него значение. - А в конце будем дожидаться, что все ок — мы вычитаем все значения из канала. ```go package main import ( "fmt" ) type sema chan struct{} func New(n int) sema { return make(sema, n) } func (s sema) Inc(k int) { for i := 0; i < k; i++ { s <- struct{}{} } } func (s sema) Dec(k int) { for i := 0; i < k; i++ { <-s } } func main() { numbers := []int{1, 2, 3, 4, 5} n := len(numbers) sem := New(n) for _, num := range numbers { go func(n int) { fmt.Println(n) sem.Inc(1) }(num) } sem.Dec(n) } ``` ### 7. Что выведет код? ```go package main import ( "fmt" ) func main() { runtime.GOMAXPROCS(1) done := false go func() { done = true }() for !done { } fmt.Println("finished") } ```
Ответ или подсказка Как можно изменить этот код, чтобы был вывод “finished”?
### 8. Какая есть проблема в коде? ```go var counter int for i := 0; i < 1000; i++ { go func() { counter++ }() } ```
Ответ или подсказка Как её можно решить? А как её можно бы было решить, если бы в языке не было пакета sync?
### 9. Что выведет код? ```go func main() { v := 5 p := &v println(*p) changePointer(p) println(*p) } func changePointer(p *int) { v := 3 p = &v } ```
Ответ или подсказка Почему? Как нужно изменить функцию changePointer, чтобы вывело 5 и 3 (в оригинальной версии выводится 5 и 5)?
### 10. Что выведет код? ```go func worker() chan int { ch := make(chan int) go func() { time.Sleep(3 * time.Second) ch <- 42 }() return ch } func main() { timeStart := time.Now() _, _ = <-worker(), <-worker() println(int(time.Since(timeStart).Seconds())) // что выведет - 3 или 6? } ```
Ответ или подсказка Что нужно изменить, чтобы код работал за 3 секунды?