Pull to refresh

Анализ теста по Go с PHDays

Reading time4 min
Views3.6K

Думаю, многие из вас сталкивались с замысловатыми задачками, которые в реальной практике встретить почти невозможно, но которые очень любят давать во всяких тестах и на собеседованиях.

В конце мая прошла конференция PHDays, на которой был тест как раз с такими задачками. К моему сожалению, я провалила этот тест, но затем разобралась что, как и почему, и хочу поделиться с вами.

Итак, 5 картинок с кодом, к каждому дается 4 варианта ответа.

Задача 1

package main

import "fmt"

func f(slice []int) {
    slice = append(slice, 84)
}

func main() {
    s := []int{23, 42}
    f(s)
    fmt.Println(s)
}

Варианты ответа:

1. [23 42]
2. [23 42 84]
3. Программа не скомпилилируется
4. runtime error: slice out of range

Ответ

Если попробовать скомпилировать код, то компилятор обратит ваше внимание на строку slice = append(slice, 84) и не просто так.

Да, слайсы имеют ссылочный тип. Но если они ссылочные, что не так-то? Должно работать!

А проблема в том, как работает функция append — она не меняет старый слайс, а возвращает новый. А из функции f мы результат не получаем.

Соответственно, правильный ответ будет под номером 1: [23 42].

Чтобы получить ответ из пункта 2 ([23 42 84]), нам нужно этот результат как-то передать в main. Сделать это можно двумя способами.

Во-первых, можно вернуть новый слайс из функции. Поправим немного код.

package main

import "fmt"

func f(slice []int) []int {
    return append(slice, 84)
}

func main() {
    s := []int{23, 42}
    s = f(s)
    fmt.Println(s)
}

(Go Playground)

Во-вторых, можно вместо слайса передавать указатель на слайс:

package main

import "fmt"

func f(slice *[]int) {
    *slice = append(*slice, 84)
}

func main() {
    s := []int{23, 42}
    f(&s)
    fmt.Println(s)
}

(Go Playground)

Задача 2

package main

import "fmt"

func main() {
    s := "bar"
    {
        s := "foo"
        fmt.Print(s)
    }
    fmt.Print(s)
}

Варианты ответа:

1. foofoo
2. foobar
3. compilation error: s redeclared in this block
4. compilation error: s undefined: s

Ответ

Вы когда-нибудь видели, чтобы кто-то вот так использовал скобки? Я — нет. Однако давайте разберёмся, что тут происходит.

Имеем две области видимости: внутри скобок и за их пределами. При этом с толку сбивают две одноимённых переменных. Такое называется variable shadowing и считается плохой практикой в Go. Существуют различные линтеры, которые выдают на подобный код предупреждение (например, этот линтер, который используется вместе с go vet, см. go help vet). Собственно, поэтому сначала программа напишет foo, а затем bar. Следовательно, правильный ответ будет под номером 2.

Задача 3

package main

import (
    "fmt"
    "sync"
)

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 100; i++ {
        wg.Add(1)
        go func() {
            fmt.Println(i)
            wg.Done()
        }()
    }
    wg.Wait()
}

Варианты ответа:

1. Числа от 1 до 99 по возрастанию
2. Числа от 1 до 99 в произвольном порядке
3. Вывод не определен
4. Ничего

Ответ

Проблема состоит из двух частей. Во-первых, счётчик цикла создаётся лишь один раз и затем просто изменяется на каждой итерации. Во-вторых, при захвате переменных из окружения в замыкание (анонимную функцию) они захватываются по указателю.

Т.о. захваченный счётчик цикла в каждой горутине один и тот же, и меняется во всех них разом. Поэтому вывод зависит от того, когда какая горутина успеет напечатать текст. Какой в этот момент будет счётчик, такой и напечатается. Большинство же горутин напечатает 100, т.к. такой цикл закончится гораздо быстрее, чем осуществится вывод.

Следовательно, правильный ответ будет под номером 3 — вывод не определен.

Чтобы получить цифры от 1 до 99 в произвольном порядке, есть два варианта.

Во-первых, можно сделать копию счётчика цикла (i := i) перед созданием замыкания:

package main

import (
    "fmt"
    "sync"
)

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 100; i++ {
        wg.Add(1)
        i := i
        go func() {
            fmt.Println(i)
            wg.Done()
        }()
    }
    wg.Wait()
}

(Go Playground)

Во-вторых, можно сделать в анонимной функции параметр i int и передавать счётчик цикла в замыкание через этот параметр (тогда он будет копироваться при этой передаче):

package main

import (
    "fmt"
    "sync"
)

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 100; i++ {
        wg.Add(1)
        go func(i int) {
            fmt.Println(i)
            wg.Done()
        }(i)
    }
    wg.Wait()
}

(Go Playground)

Задача 4

package main

import "fmt"

func main() {
    s := "Hello"

    defer func() {
        s = "World"
    }()
    fmt.Println(s)
}

Варианты ответа:

1. World
2. Hello
3. Hello World
4. compilation error: (func literal)() used as value

Ответ

Чтобы понять, что происходит в этом коде, надо знать особенности defer. Эта конструкция выполнится только по завершению функции. Тут нас сбивает с толку Println, которы стоит после defer. Однако, если мы попробуем упросить код и убрать всё, что может нас запутать, то получим что-то такое:

package main

import "fmt"

func main() {
    s := "Hello"
    fmt.Println(s)

    s = "World"
}

(Go Playground)

Теперь правильный ответ очевиден, он под номером 2.

Задача 5

package main

import "fmt"

func main() {
    a := []string{"a", "b", "c"}
    b := a[1:2]
    b[0] = "q"
    fmt.Println(a)
}

Варианты ответа:

1. [a b c]
2. [a q c]
3. [b c]
4. output is undefined

Ответ

На первый взгляд хочется спросить, а зачем тут вообще эта b? Мы же a печатаем.

Вот теперь самое время вспомнить, что слайсы в Go — это ссылочный тип данных. Следовательно, на строке b := a[1:2], мы присваиваем в переменную b ссылку на область памяти из переменной a. А затем (b[0] = "q") редактируем эту самую область памяти. В итоге имеем в результате правильный ответ под номером 2.

Если вы хотите именно скопировать слайс, то для этого придется использовать либо цикл, либо встроенную функцию copy.

Only registered users can participate in poll. Log in, please.
Сколько задач вы решили?
35.82% 548
23.88% 432
17.16% 323
7.46% 210
3.73% 15
11.94% Не знаю Go16
134 users voted. 15 users abstained.
Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
Total votes 9: ↑7 and ↓2+6
Comments7

Articles