트랙
/
Go
Go
/
연습 문제
/
라자냐 마스터
라자냐 마스터

라자냐 마스터

학습 연습 문제

소개

함수를 사용하면 코드를 재사용할 수 있는 하나의 단위로 묶을 수 있어요. 함수는 func 키워드, 함수 이름, 그리고 괄호 안에 쉼표로 구분된 0개 이상의 매개변수와 타입으로 이루어져 있어요.

함수 매개변수

모든 매개변수는 타입을 명시해야 해요. 매개변수에는 타입 추론이 없어요. 매개변수에는 기본값이 없어서 모든 함수 매개변수가 필수예요.

import "fmt"

// No parameters
func PrintHello() {
    fmt.Println("Hello")
}

// Two parameters
func PrintGreetingName(greeting string, name string) {
  fmt.Println(greeting + " " + name)
}

같은 타입의 매개변수는 함께 선언하고 뒤에 타입을 한 번만 적어줄 수 있어요.

import "fmt"

func PrintGreetingName(greeting, name string) {
  fmt.Println(greeting + " " + name)
}

매개변수와 인자

자주 헷갈리는 두 용어, parameters와 arguments를 잠깐 짚어볼까요? 함수 매개변수는 함수 시그니처에 정의된 이름이에요. 위의 PrintGreetingName 함수에서 greeting과 name이 바로 매개변수예요. 함수 인자는 함수를 호출할 때 매개변수에 전달되는 실제 값이에요. 예를 들어 아래 예제에서 "Hello"와 "Katrina"는 greeting과 name 매개변수에 전달되는 인자예요.

PrintGreetingName("Hello", "Katrina")

반환 값

함수 매개변수 뒤에는 0개 이상의 반환 값이 오는데, 이것도 타입을 명시해야 해요. 반환 값이 하나면 그냥 쓰고, 여러 개면 괄호로 감싸요. 함수에서 호출한 코드로 값을 반환할 때는 return 키워드를 사용해요. 함수 안에는 여러 개의 return 문이 있을 수 있어요. 함수는 이 return 문 중 하나에 도달하면 곧바로 실행이 끝나요. 함수에서 여러 값을 반환할 때는 쉼표로 구분해요.

func Hello(name string) string {
  return "Hello " + name
}

func HelloAndGoodbye(name string) (string, string) {
  return "Hello " + name, "Goodbye " + name
}

함수 호출하기

함수를 호출할 때는 함수 이름을 쓰고, 괄호 안에 함수의 각 매개변수에 맞는 인자를 전달해요.

import "fmt"

// No parameters, no return value
func PrintHello() {
    fmt.Println("Hello")
}
// Called like this:
PrintHello()

// One parameter, one return value
func Hello(name string) string {
  return "Hello " + name
}
// Called like this:
greeting := Hello("Dave")

// Multiple parameters, multiple return values
func SumAndMultiply(a, b int) (int, int) {
    return a+b, a*b
}
// Called like this:
aplusb, atimesb := SumAndMultiply(a, b)

이름 있는 반환 값과 네이키드 리턴

매개변수와 마찬가지로 반환 값에도 이름을 붙일 수 있어요. 이름 있는 반환 값을 사용하면, 인자 없는 return 문이 그 값들을 반환해요. 이것을 '네이키드' 리턴이라고 해요.

func SumAndMultiplyThenMinus(a, b, c int) (sum, mult int) {
    sum, mult = a+b, a*b
    sum -= c
    mult -= c
    return
}

값에 의한 전달과 참조에 의한 전달

값에 의한 전달과 참조에 의한 전달 개념도 짚고 넘어갈 필요가 있어요.

먼저 값에 의한 전달부터 살펴볼까요. 아래 예제에서 변수 val을 MultiplyByTwo 함수에 전달할 때, 우리는 val의 복사본을 전달한 거예요. 그래서 newVal은 갱신된 값 4를 가지지만, 원래 변수 val은 여전히 2예요. 내부적으로 Go는 원래 값의 복사본을 만들어서, 이 복사본, 즉 v만 함수에 의해 수정돼요.

val := 2
func MultiplyByTwo(v int) int {
    v = v * 2
    return v
}
newVal := MultiplyByTwo(val)
// newval is 4, val is still 2 because only a copy of its value was passed into the function

엄밀히 말하면 Go에서는 모든 인자가 값에 의해 전달돼요. 즉, 함수에 전달된 값이나 데이터의 복사본이 만들어져요. 그런데 함수에 전달된 데이터의 복사본을 만들고 싶지 않고 함수 안에서 그 데이터를 바꾸고 싶다면, 인자로 pointers를 사용해야 해요. 이것을 참조에 의한 전달이라고 해요.

포인터

참조에 의한 전달을 하려면 pointer를 사용해요. 함수에 pointer 인자를 전달하면, 데이터의 복사본만 다루는 대신 함수에 전달된 원본 데이터를 수정할 수 있어요.

지금은 함수 시그니처에서 타입 앞에 붙은 *로 포인터 타입을 알아볼 수 있다는 것만 알아두면 충분해요.

func HandlePointers(x, y *int) {
    // Some logic to handle integer values referenced by pointers x and y
}

pointer 개념이 헷갈려도 걱정 마세요. 나중에 포인터를 완전히 익힐 수 있도록 돕는 별도의 섹션이 있어요.

예외

slices와 maps는 위에서 말한 규칙의 예외라는 점에 주의해야 해요. slice나 map을 함수의 인자로 전달하면, 타입에 명시적인 *가 없어도 포인터 타입처럼 다뤄져요. 즉, 슬라이스나 맵을 함수에 전달해서 그 원본 데이터를 수정하면, 그 변경 사항이 원래 슬라이스나 맵에도 반영돼요.

지침

이번 연습 문제에서는 좋아하는 요리책에 나오는 근사한 라자냐를 준비하고 요리하는 것과 관련된 코드를 조금 더 작성해 봐요.

과제는 네 개예요. 첫 번째는 요리 자체에 관한 것이고, 나머지 세 개는 완벽한 준비에 관한 거예요.

1. 준비 시간 예측하기

다음에 만들 라자냐를 위해, 요리를 즐길 수 있도록 충분한 시간을 확보해 두고 싶어요. 라자냐에 어떤 층을 넣을지는 이미 계획해 두었어요. 이제 그것을 바탕으로 준비에 얼마나 걸릴지 추정하려고 해요.

층 목록을 []string으로, 층당 평균 준비 시간(분)을 int로 받는 함수 PreparationTime을 구현해요. 이 함수는 층 수를 기준으로 계산한 전체 준비 시간의 추정값을 int로 반환해야 해요. Go에는 함수의 기본값이 없어요. 평균 준비 시간이 0(int의 기본 초깃값)으로 전달되면 기본값 2를 사용해야 해요.

layers := []string{"sauce", "noodles", "sauce", "meat", "mozzarella", "noodles"}
PreparationTime(layers, 3)
// => 18
PreparationTime(layers, 0)
// => 12

2. 필요한 면과 소스의 양 계산하기

시간을 확보하는 것 외에도, 꿈에 그리던 라자냐를 만들 수 있을 만큼 소스와 면이 충분한지 확인하고 싶어요. 라자냐에 면 층이 하나 있을 때마다 면 50그램이 필요해요. 소스 층이 하나 있을 때마다 소스 0.2리터가 필요해요.

층 목록을 매개변수로 []string 형식으로 받는 함수 Quantities를 정의해요. 이 함수는 요리를 만드는 데 필요한 면과 소스의 양을 계산해요. 결과는 noodles를 int로, sauce를 float64로 하는 두 개의 값으로 반환해야 해요.

Quantities([]string{"sauce", "noodles", "sauce", "meat", "mozzarella", "noodles"})
// => 100, 0.4

3. 비밀 재료 추가하기

얼마 전에 친구를 방문해서 그곳에서 라자냐를 먹었어요. 정말 훌륭했고 뭔가 특별한 맛이 났어요. 친구가 재료 목록을 보내 주면서, 목록의 마지막 항목이 그 요리를 그렇게 특별하게 만든 "비밀 재료"라고 알려 줬어요. 이제 그 비밀 재료를 내 레시피에도 넣고 싶어요.

[]string 형식의 재료 목록 두 개를 매개변수로 받는 함수 AddSecretIngredient를 작성해요. 첫 번째 매개변수는 친구가 보내 준 목록이고, 두 번째는 내 레시피의 재료 목록이에요. 내 재료 목록의 마지막 원소는 항상 "?"예요. 이 함수는 그 원소를 친구 목록의 마지막 항목으로 바꿔야 해요. 참고: AddSecretIngredient는 아무것도 반환하지 않아요. 내 재료 목록을 직접 수정해야 해요. 친구의 재료 목록은 수정하면 안 돼요. 또한 slice는 함수에 포인터로 전달되기 때문에, AddSecretIngredient에 전달된 두 []string 인자를 변경하면 그대로 반영돼요.

friendsList := []string{"noodles", "sauce", "mozzarella", "kampot pepper"}
myList := []string{"noodles", "meat", "sauce", "mozzarella","?"}

AddSecretIngredient(friendsList, myList)
// myList => []string{"noodles", "meat", "sauce", "mozzarella", "kampot pepper"}

4. 레시피 양 조절하기

요리책에 적힌 양은 두 인분 정도의 라자냐밖에 만들지 못해요. 다음에는 더 많은 사람을 위해 요리하고 싶으니, 인분 수에 따라 필요한 양을 계산하려고 해요.

매개변수 두 개를 받는 함수 ScaleRecipe를 구현해요.

  • 2인분에 필요한 양을 담은 float64 슬라이스예요.
  • 만들고 싶은 인분 수예요.

이 함수는 원하는 인분 수에 필요한 양을 담은 float64 슬라이스를 반환해야 해요. 다만 원래 레시피는 그대로 두고 싶어요. 즉, 이 함수 안에서 quantities 인자는 수정되면 안 돼요.

quantities := []float64{ 1.2, 3.6, 10.5 }
scaledQuantities := ScaleRecipe(quantities, 4)
// => []float64{ 2.4, 7.2, 21 }
GitHub에서 편집 링크가 새 창이나 탭에서 열려요
Go Exercism

라자냐 마스터 문제를 시작해 볼 준비가 됐나요?

Exercism에 가입하고 Go 트랙을 개념 34개연습 문제 166개, 그리고 실제 사람의 멘토링과 함께 배우고 익혀 보세요. 모두 무료예요.