Percursos
/
Swift
Swift
/
Exercícios
/
Double-Null0111: As closures são para sempre
Double-Null0111: As closures são para sempre

Double-Null0111: As closures são para sempre

Exercício de aprendizagem

Introdução

Os closures em Swift são blocos de código autónomos que podem receber parâmetros para desencadear o seu cálculo e devolver valores. Os closures também capturam valores do seu ambiente e usam-nos nos seus cálculos. Como são autónomos, podem ser passados de um lado para o outro num programa como qualquer outro valor, ou atribuídos a constantes e variáveis.

Os closures podem parecer muito com as funções de Swift, e realmente parecem, e por uma boa razão. Em Swift, as funções são casos especiais de closures: têm obrigatoriamente um nome e são definidas com uma sintaxe ligeiramente diferente.

Embora, tecnicamente, as funções em Swift sejam closures, quando as pessoas falam de «closures» em Swift estão a referir-se a expressões de closure. As expressões de closure escrevem-se com uma lista de parâmetros, seguida de um tipo devolvido e da palavra-chave in, seguida do corpo do closure, tudo entre um par de chavetas:

{ (a: Double, b: Double) -> Double in
  return (a + b) / 2.0
}

Isto define uma expressão de closure do tipo (Double, Double) -> Double, que pode ser executada de forma semelhante a uma função deste tipo, aplicando-a a parâmetros do tipo adequado:

{ (a: Double, b: Double) -> Double in
  return (a + b) / 2.0
}(10.0, 15.0)
// returns 12.5

Como não é muito prático escrever a expressão de closure completa sempre que se quer executá-la, os closures podem ser atribuídos a nomes, como qualquer outro valor em Swift. Depois, podem ser chamados aplicando o nome aos parâmetros:

let mean = { (a: Double, b: Double) -> Double in
  (a + b) / 2.0
}

mean(13.0, 100.0)
// returns 56.5

Como se vê acima, os closures, tal como as funções normais, podem omitir a palavra-chave return se o corpo do closure for uma única expressão.

Na maioria das vezes, os closures são usados com funções de ordem superior, quer passados como parâmetros, quer devolvidos como resultados:

[11, 75, 3, 99, 53].contains(where: { (x: Int) -> Bool in x > 100 })
// returns false

["apple", "ball", "carrot"].sorted(by: { (s1: String, s2: String) -> Bool in s1.count < s2.count })
// returns ["ball", "apple", "carrot"]

func makeAdder(base: Int) -> (Int) -> Int {
  { (x: Int) -> Int in base + x }
}

Instruções

Olá de novo, Agente Double-Null0111.

As forças da UMBRA estão outra vez em ação, e cabe-te a ti frustrar o seu mais recente plano. O nosso departamento de informações informa-nos de que esses Minions estão a planear rebentar uma bomba fétida no baile do Governador.

Como os Minions são muito desastrados, muitas vezes armam acidentalmente as bombas fétidas nos seus laboratórios e escritórios. E, como continuam a esquecer-se de como desarmar as bombas fétidas, implementaram um sistema que gera as instruções de desarmamento, que indicam a ordem em que os fios devem ser cortados a partir do número de identificação da bomba.

A tua tarefa é escrever o código que te permitirá desarmar a bomba fétida no baile antes de ela rebentar.

1. Escreve um closure para trocar dois fios

Cada bomba fétida tem três fios de cores diferentes. Escreve um closure que recebe um tuplo (String, String, String) e devolve o tuplo com a ordem dos dois primeiros elementos trocada. Atribui este closure ao nome flip.

É importante notar que o tipo tem de ser o ChangeClosure (ou um tipo sendable); a definição do tipo é dada no ficheiro stub. Caso contrário, o código não compila.

flip(("red", "yellow", "blue"))
// Returns ("yellow", "red", "blue")

2. Escreve um closure para rodar os fios

Escreve um closure que recebe um tuplo (String, String, String) e devolve o tuplo com a ordem dos fios rodada para a esquerda. Atribui este closure ao nome rotate.

É importante notar que o tipo tem de ser o ChangeClosure (ou um tipo sendable); a definição do tipo é dada no ficheiro stub.

rotate(("red", "yellow", "blue"))
// Returns ("yellow", "blue", "red")

3. Implementa um gerador de baralhamento de fios

Para descobrires a ordem em que deves cortar os fios da bomba fétida, vais precisar de implementar uma função que gera a função de baralhamento adequada com base no número de identificação da bomba fétida.

Implementa a função:

makeShuffle(
  flipper: @escaping ((String, String, String)) -> (String, String, String),
  rotator: @escaping ((String, String, String)) -> (String, String, String)
) -> ([UInt8], (String, String, String)) -> (String, String, String)

que recebe como parâmetros de entrada um closure que troca dois fios e um closure que roda os três fios, e devolve um closure. Este closure devolvido recebe o número de identificação da bomba fétida e a ordem dos três fios, e calcula depois a ordem em que os fios devem ser cortados. O número de identificação é composto por oito bits, e cada bit determina se os fios devem ser trocados ou rodados.

Para cada bit do número de identificação, começando pelo bit mais à direita, aplicas o closure flipper ao tuplo de fios se o bit for 0, e aplicas o closure rotator se for 1, obtendo o novo estado dos fios. Depois de aplicares os closures adequados aos oito bits do número de identificação, o estado final dos fios é a ordem em que devem ser cortados.

let shuffler = makeShuffle(flipper: flip, rotator: rotate)
// Returns ([UInt8], (String, String, String)) -> (String, String, String)
shuffler([1, 0, 0, 1, 1, 0, 1, 1], ("red", "yellow", "blue"))
// Returns ("red", "blue", "yellow")
Editar via GitHub A ligação abre numa nova janela ou separador
Swift Exercism

Estás pronto para começar Double-Null0111: As closures são para sempre?

Inscreve-te no Exercism para aprenderes e dominares Swift com 35 conceitos116 exercícios, e mentoria humana real, tudo grátis.