Percursos
/
Go
Go
/
Exercícios
/
Mestre da lasanha
Mestre da lasanha

Mestre da lasanha

Exercício de aprendizagem

Introdução

Uma função permite agrupar código numa unidade reutilizável. É composta pela palavra-chave func, pelo nome da função e por uma lista, separada por vírgulas, de zero ou mais parâmetros e respetivos tipos, entre parênteses curvos.

Parâmetros de função

Todos os parâmetros têm de ser tipados explicitamente; não há inferência de tipos para os parâmetros. Não existem valores predefinidos para os parâmetros, por isso todos os parâmetros de função são obrigatórios.

import "fmt"

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

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

Parâmetros do mesmo tipo podem ser declarados em conjunto, seguidos de uma única declaração de tipo.

import "fmt"

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

Parâmetros vs. argumentos

Vamos ver rapidamente dois termos que muitas vezes se confundem: parameters e arguments. Os parâmetros de função são os nomes definidos na assinatura da função, como greeting e name na função PrintGreetingName acima. Os argumentos de função são os valores concretos que passamos aos parâmetros de função quando invocamos a função. Por exemplo, no exemplo abaixo, "Hello" e "Katrina" são os argumentos passados aos parâmetros greeting e name:

PrintGreetingName("Hello", "Katrina")

Valores devolvidos

Aos parâmetros da função seguem-se zero ou mais valores devolvidos, que também têm de ser tipados explicitamente. Um único valor devolvido fica sem parênteses; vários valores devolvidos são envolvidos em parênteses. Os valores são devolvidos ao código que fez a chamada através da palavra-chave return. Uma função pode ter várias instruções return. A execução da função termina assim que encontra uma dessas instruções return. Se uma função tiver de devolver vários valores, estes são separados por vírgulas.

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

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

Invocar funções

Invocar uma função faz-se indicando o nome da função e passando argumentos para cada um dos parâmetros da função, entre parênteses.

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)

Valores devolvidos nomeados e return nu

À semelhança dos parâmetros, os valores devolvidos também podem ter nome, se quisermos. Se usarmos valores devolvidos com nome, uma instrução return sem argumentos devolve esses valores. É a isto que se chama um return "nu".

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

Passagem por valor vs. passagem por referência

Também é importante clarificar o conceito de passagem por valor e de passagem por referência.

Primeiro, vamos clarificar a passagem por valor. No exemplo abaixo, quando passamos a variável val à função MultiplyByTwo, passamos uma cópia de val. Por causa disso, newVal tem o valor atualizado 4, mas a variável original val continua a ser 2. Nos bastidores, o Go faz essencialmente uma cópia do valor original, para que apenas essa cópia, também conhecida como v, seja modificada pela função.

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

Em rigor, no Go todos os argumentos são passados por valor, ou seja, é feita uma cópia do valor ou dos dados fornecidos à função. Mas se não quiseres fazer uma cópia dos dados que são passados a uma função e quiseres alterar esses dados dentro da função, deves usar pointers como argumentos, ou seja, passagem por referência.

Ponteiros

Usamos um pointer para conseguir a passagem por referência. Ao passar argumentos do tipo pointer para uma função, podemos modificar os dados subjacentes que são passados à função, em vez de operar apenas sobre uma cópia desses dados.

Por agora, basta saberes que os tipos ponteiro se reconhecem pelo * à frente do tipo na assinatura da função.

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

Se o conceito de pointer te parecer confuso, não há problema. Temos uma secção dedicada mais à frente no currículo, para te ajudar a dominar os ponteiros.

Exceções

Repara que slices e maps são exceções à regra mencionada acima. Quando passamos um slice ou um map como argumentos a uma função, são tratados como tipos ponteiro, mesmo que não haja um * explícito no tipo. Isto significa que, se passarmos um slice ou um map a uma função e modificarmos os seus dados subjacentes, as alterações refletem-se no slice ou map original.

Instruções

Neste exercício vais escrever mais algum código relacionado com a preparação e a confeção da tua fantástica lasanha do teu livro de receitas preferido.

Tens quatro tarefas. A primeira está relacionada com a confeção propriamente dita; as outras três são sobre a preparação perfeita.

1. Estima o tempo de preparação

Para a próxima lasanha que vais preparar, queres garantir que reservas tempo suficiente para poderes desfrutar da confeção. Já planeaste que camadas a tua lasanha vai ter. Agora queres estimar quanto tempo a preparação vai levar, com base nisso.

Implementa uma função PreparationTime que aceita um slice de camadas como []string e o tempo médio de preparação por camada, em minutos, como um int. A função deve devolver a estimativa do tempo total de preparação, com base no número de camadas, como um int. Go não tem valores por omissão para funções. Se o tempo médio de preparação for passado como 0 (o valor inicial por omissão de um int), deve usar-se o valor por omissão de 2.

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

2. Calcula as quantidades de massa e molho necessárias

Além de reservares o tempo, também queres garantir que tens molho e massa suficientes para cozinhar a lasanha dos teus sonhos. Por cada camada de massa na tua lasanha, vais precisar de 50 gramas de massa. Por cada camada de molho na tua lasanha, vais precisar de 0,2 litros de molho.

Define a função Quantities, que recebe um slice de camadas como parâmetro, do tipo []string. A função determina então a quantidade de massa e de molho necessárias para preparar a tua refeição. O resultado deve ser devolvido como dois valores: noodles como um int e sauce como um float64.

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

3. Adiciona o ingrediente secreto

Há algum tempo, visitaste um amigo e comeste lasanha em casa dele. Estava incrível e tinha algo de especial. O amigo enviou-te a lista de ingredientes e disse-te que o último item da lista era o "ingrediente secreto" que tornava a refeição tão especial. Agora também queres adicionar esse ingrediente secreto à tua receita.

Escreve uma função AddSecretIngredient que aceita dois slices de ingredientes do tipo []string como parâmetros. O primeiro parâmetro é a lista que o teu amigo te enviou; o segundo é a lista de ingredientes da tua própria receita. O último elemento da tua lista de ingredientes é sempre "?". A função deve substituí-lo pelo último item da lista do teu amigo. Nota: a AddSecretIngredient não devolve nada; deves modificar diretamente a tua lista de ingredientes. A lista com os ingredientes do teu amigo não deve ser modificada. Além disso, como o slice é passado para uma função como ponteiro, as alterações aos dois argumentos []string passados para AddSecretIngredient são feitas diretamente.

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. Ajusta a receita

As quantidades indicadas no teu livro de receitas só dão para uma lasanha de duas porções. Como queres cozinhar para mais pessoas da próxima vez, queres calcular as quantidades para diferentes números de porções.

Implementa uma função ScaleRecipe que recebe dois parâmetros.

  • Um slice de valores float64 necessários para 2 porções.
  • O número de porções que queres cozinhar.

A função deve devolver um slice de float64 com as quantidades necessárias para o número de porções pretendido. No entanto, queres manter a receita original. Isto significa que o argumento quantities não deve ser modificado nesta função.

quantities := []float64{ 1.2, 3.6, 10.5 }
scaledQuantities := ScaleRecipe(quantities, 4)
// => []float64{ 2.4, 7.2, 21 }
Editar via GitHub A ligação abre numa nova janela ou separador
Go Exercism

Estás pronto para começar Mestre da lasanha?

Inscreve-te no Exercism para aprenderes e dominares Go com 34 conceitos166 exercícios, e mentoria humana real, tudo grátis.