Estrutura de dados funcionais
Objetivos da programação funcional:
Código conciso
Reuso de código (tipicamente, funções)
Problema:
Como reduzir a escrita do código e manter a
legibilidade do código de forma
simultânea
?
Problemas com solução imperativa exige mutabilidade
Estrutura de dados funcionais
Solução do
paradigma
funcional:
Algoritmos que utilizam
operações funcionais a partir de
estrutura de dados funcionais!
Como escolher as estruturas mais
adequadas?
Em Scala, a decisão recai sobre três aspectos:
O uso de uma função como
predicado
Predicado: função que recebe um ou mais parâmetros e
retorna um booleano
O uso de funções de alta ordem
Objetivo: permitir a
composição
de funções
Uso de loops
implícitos
Métodos das coleções que implementam funções
puras
.
Código conciso e imutável.
Operações Funcionais
As principais estruturas de dados funcionais de
Scala apresentam um conjunto de operações em
comum:
Traversing
(percorrer a coleção);
Mapping
(transformação de uma coleção em outra
coleção);
Filtering
(extração de uma nova coleção a partir de um
predicado);
Folding
e
Reducing
(redução de uma coleção em uma
coleção menor ou em um valor).
Operação de Traversing
Objetivo: percorrer todos os elementos de uma
coleção.
Método principal: foreach
O método foreach recebe uma função como
argumento.
A função a ser passada deverá ter apenas um
parâmetro e não deve ter tipo de retorno (ou seja, deve
ser Unit).
O parâmetro da função deverá ser do mesmo tipo dos
elementos da coleção.
Operação de Traversing
Exemplo 1: imprimir os elementos de uma coleção
Formas concisas do foreach:
val x = List(1, 2, 3) // Ou Array, Set, Map, etc
x.foreach((i: Int) => println(i)) // imprime os elementos
x.foreach(i => println(i)) // por inferência
x.foreach(println(_)) // aplicação parcial da função x.foreach(println) // forma mais concisa da função
Operação de Traversing
Exemplo 2: percorrendo os caracteres de uma
String
"Teste".foreach(c => println(c))
Operação de Traversing
Exemplo 3: Atribuição e iteração simultânea
Operação de Traversing
Exemplo 4: percorrendo os elementos de um Map
val avaliacao = Map("Slayer" -> 10.0, "Metallica" -> 9.5, "Wesley Safadão" -> -1.0)
// usando o for tradicional
Operação de Traversing
Exemplo 4: percorrendo os elementos de um Map
val avaliacao = Map("Slayer" -> 10.0, "Metallica" -> 9.5, "Wesley Safadão" -> -1.0)
// usando o foreach e o case avaliacao.foreach {
case(artista, nota) => println(s"artista: $artista, nota: $nota")
Operação de Traversing
Exemplo 4: percorrendo os elementos de um Map
val avaliacao = Map("Slayer" -> 10.0, "Metallica" -> 9.5, "Wesley Safadão" -> -1.0)
// usando o foreach diretamente
avaliacao.foreach(x => println(s"artista: ${x._1}, nota: ${x._2}"))
Operação de Traversing
Exemplo 5: percorrendo as chaves e valores do Map
val avaliacao = Map("Slayer" -> 10.0, "Metallica" -> 9.5, "Wesley Safadão" -> -1.0)
// percorrendo as chaves
avaliacao.keys.foreach(println)
// percorrendo os valores
Operação de Mapping
Objetivo: transformar uma coleção em outra
aplicando um algoritmo para cada elemento da
coleção original.
Método principal: map
O método map recebe uma função como argumento.
Não confundir com a coleção Map!
O tipo dos elementos da nova coleção depende do tipo
de retorno da função passada como parâmetro do
Operação de Mapping
Exemplo 1: Converte uma String para maiúsculo
val nomes = Array("João", "José", "Maria")
val nomesMaiusculo = nomes.map(e => e.toUpperCase)
// forma concisa
Operação de Mapping
Exemplo 2: array com o tamanho das strings
val nomes = Array("João", "José", "Maria") val tamanhoNomes = nomes.map(_.length)
Operação de Mapping
Exemplo 3: Contando as ocorrências de caracteres
de uma String
val s = "abacate"
val ocorrencias = s map(x => s.count(_ == x))
// Obtendo resultados únicos com o método distinct s.distinct.map(x => (x, s.count(_ == x)))
// Mesmo resultado com o método groupBy
s.groupBy(c => c).map(e => (e._1, e._2.length))
// Idem, sem a função anônima do groupBy
s.groupBy(identity).map(e => (e._1, e._2.length))
Outras operações de Mapping
Operação de Mapping
Nome Exemplo Descrição
collect List(0, 1, 0) collect {case 0 =>"ok"}
Transforma cada elemento usando uma função parcial, mantendo apenas os
elementos que satisfazem a função.
flatMap List("chá,café") flatMap (_.split(','))
Transforma cada elemento usando uma função,
retornando uma única lista com os elementos
Operação de Filtering
Objetivo: filtrar os elementos de uma coleção
para criar uma nova coleção que atende a uma
condição .
Método principal: filter
O método filter requer que o predicado possua um
parâmetro do mesmo tipo dos elementos da coleção.
Se a condição lógica do predicado for atendida (true),
o elemento é “filtrado” e inserido na coleção de
retorno.
Operação de Filtering
Exemplo 1: Criando uma lista com números pares
val x = List.range(1, 10) // 1, 2, ..., 9
Operação de Filtering
Exemplo 2: Extraindo elementos de uma String
val nomes = Set("Ana", "José", "João", "Maria")
val x = nomes.filter(_.startsWith("J")) // José, João val y = nomes.filter(_.length > 4) // Maria
Operação de Filtering
Exemplo 3: Criando um novo Map a partir do
predicado
val estados = Map("AL" -> "Alagoas", "AM" -> "Amazonas", "PE" -> "Pernambuco")
val estados2 = estados filter { i => i._1 startsWith "A" } // retorna o mapa("AL" -> "Alagoas","AM" -> "Amazonas")
Outras operações de Filtering
Operação de Filtering
Nome Exemplo Descrição
distinct List(3, 5, 4, 3, 4).distinct Retorna uma coleção sem elementos duplicados drop List('a', 'b', 'c', 'd') drop 2 Remove os n primeiros
elementos da coleção flatten List(List(1, 2), List(3,4)).flatten Converte uma coleção de
coleções em uma única coleção
Partition List(1, 2, 3, 4, 5) partition(_<3) Particiona a coleção em uma tupla de coleções, de acordo com uma condição
reverse List(1, 2, 3).reverse Gera uma coleção com os elementos revertidos
Outras operações de Filtering (cont.)
Operação de Filtering
Nome Exemplo Descrição
slice List(2, 3, 5, 7) slice (1, 3) Particiona a coleção de acordo com os índices desejados
sortBy List("apple", "to") sortBy (_.size) Ordena a coleção de acordo com uma função
sorted List("apple", "to").sorted Ordena a coleção na ordem dos elementos
splitAt List(2, 3, 5, 7) splitAt 2 Particiona a coleção em uma tupla a partir de um índice take List(2, 3, 5, 7, 11, 13) take 3 Extrai os primeiros n
elementos da coleção
zip List(1, 2) zip List("a", "b") Combina as coleções em uma lista de tuplas
Operações de Folding e Reducing
Objetivo: percorrer todos os elementos de uma
coleção, realizando uma operação para cada par de
elementos adjacentes de forma recursiva.
Métodos principais: reduceLeft, foldLeft,
reduceRight e foldRight
Os métodos recebem como parâmetro uma função que
opera sobre dois elementos adjacentes da coleção, gerando
um novo resultado.
O resultado gerado é utilizado na operação com o próximo
elemento da coleção.
Operações de Folding e Reducing
• Reduz a coleção da esquerda para a direita, começando com os dois primeiros elementos da coleção.
reduceLeft
• Reduz a coleção da direita para a esquerda, começando com os dois últimos elementos da coleção.
reduceRight
• Idem ao reduceLeft, mas com um valor inicial sendo atribuído à operação.
foldLeft
• Idem ao reduceRight, mas com um valor inicial sendo atribuído à operação.
Operações de Folding e Reducing
Exemplo 1: Soma dos elementos de uma coleção
Vamos mostrar primeiro com os métodos reduce:
val a = Array(12, 6, 15, 2, 20, 9)
a.reduceLeft((x,y) => x + y) // retorna 64
a.reduceLeft(_ + _) // forma concisa, com função parcial
a.reduceLeft(_ * _) // retorna 388800 a.reduceLeft(_ min _) // retorna 2 a.reduceLeft(_ max _) // retorna 20
Operações de Folding e Reducing
Entendendo como funciona a redução
Vamos usar uma função auxiliar:
Usando o reduceLeft
def maiorValor (x: Int, y: Int) = { val maior = x max y
println(s"comparando $x com $y, o maior é $maior") maior
}
val a = Array(12, 6, 15, 2, 20, 9) a.reduceLeft(maiorValor)
Operações de Folding e Reducing
Entendendo como funciona a redução
val a = Array(12, 6, 15, 2, 20, 9) a.reduceLeft(maiorValor)
> comparando 12 com 6, o maior é 12 > comparando 12 com 15, o maior é 15 > comparando 15 com 2, o maior é 15 > comparando 15 com 20, o maior é 20 > comparando 20 com 9, o maior é 20 > res1: Int = 20
Operações de Folding e Reducing
A mesma operação com reduceRight
val a = Array(12, 6, 15, 2, 20, 9) a.reduceRight(maiorValor)
> comparando 20 com 9, o maior é 20 > comparando 2 com 20, o maior é 20 > comparando 15 com 20, o maior é 20 > comparando 6 com 20, o maior é 20 > comparando 12 com 20, o maior é 20 > res2: Int = 20
Operações de Folding e Reducing
Exemplo 2: Soma dos elementos de uma coleção
com os métodos fold:
val a = Array(1, 2, 3)
a.reduceLeft(_ + _) // retorna 6
a.foldLeft(20)(_ + _) // inicializa a operação com 20 e // ao final retorna 26
Operações de Folding e Reducing
Exemplo 3: Uso do fold com a função
maiorValor:
val a = Array(12, 6, 15, 2, 20, 9) a.foldLeft(19)(maiorValor)
> comparando 19 com 12, o maior é 19 > comparando 19 com 6, o maior é 19 > comparando 19 com 15, o maior é 19 > comparando 19 com 2, o maior é 19 > comparando 19 com 20, o maior é 20 > comparando 20 com 9, o maior é 20 res3: Int = 20
Operações de Folding e Reducing
Exemplo 4: Operando com outras coleções:
val nomes = Set("Ana", "José", "João", "Maria")
nomes.reduceLeft((x,y)=> if(x.length > y.length) x else y)
Outras operações de Folding e Reducing
Operação de Folding e Reducing
Nome Exemplo Descrição
max List(41, 59, 26).max Valor máximo da coleção min List(10.9, 32.5, 4.23, 5.67).min Valor mínimo da coleção product List(5, 6, 7).product Produto dos elementos da
coleção
sum List(5, 6, 7).sum Soma dos elementos da coleção contains List(34, 29, 18) contains 29 Avalia se um elemento pertence a
coleção
endsWith List(0, 4, 3) endsWith List(4,3) Avalia se uma coleção termina com os mesmos elementos de outra coleção
Outras operações de Folding e Reducing (cont.)
Operação de Folding e Reducing
Nome Exemplo Descrição
exists List(24, 17, 32) exists (_ < 18) Avalia se pelo menos um elemento da coleção atende ao predicado
forall List(24, 17, 32) forall (_ < 18) Avalia se todos os elementos da coleção atendem ao
predicado
startsWith List(0, 4, 3) startsWith List(0) Avalia se uma coleção inicia com os mesmos elementos de outra coleção
scanLeft List(4, 5, 6).scanLeft(0)(_ + _) Idem ao foldLeft, retornando uma coleção dos valores
scanRight List(4, 5, 6).scanRight(0)(_ + _) Idem ao foldRight, retornando uma coleção dos valores