16  Aula de exercícios sobre Strings

Nesta aula, vamos explorar funções que manipulam strings e criar testes para verificar sua correção. Em algumas funções, vamos notar que há diversas formas de se obter o mesmo resultado

16.1 Concatenação de letras

A primeira função concatena concatena as primeiras duas e as últimas duas letras de uma string.

function concatena(s::String)::String
    if length(s) < 2
        return "Erro: tamanho da string menor do que 2"
    end
    resposta = s[1:2]*s[end-1:end]
    return resposta
end
concatena (generic function with 1 method)

Awui utulizamos s[1:2] para obter as duas primeiras letras de s, que é uma forma mais concisa de acessar mais de um índice de um objeto. Alternativamente, poderíamos acessar esses dois índices separadamente com o comando s[1]*s[2].

Para verificar se a função está funcionando corretamente, podemos utilizar o seguinte teste:

using Test

function testeConcatena()
  @test concatena("Ola Bom Dia") == "Olia"
  @test concatena("oi") == "oioi"
  @test concatena("tre") == "trre"
  @test concatena("a") == "Erro: tamanho da string menor do que 2"
  @test concatena("a123") == "a123"
end
testeConcatena (generic function with 1 method)

16.2 Inversão de String

Devemos criar uma função que interte uma string, retornando os caracteres na ordem reversa.

function inverte(s::String)::String
    # Inicializamos uma string vazia
    inversa=""

    # Intervalo de lenght(s) até 1, a passos de -1
    for i in length(s):-1:1
        # Concatena cada caractere na ordem inversa
        inversa*=s[i]
    end

    return inversa
end
inverte (generic function with 1 method)

Para obter o resultado que desejamos, fazemos um laço for que itera do último índice da string, representado por length(s), até o primeiro, concatenando os caracteres nessa ordem na string de retorno. O laço é configurado para decrementar o índice a cada iteração, especificando -1 como passo. Isso nos permite acessar cada caractere da string de trás para frente. E em cada iteração, concatenamos o caractere atual, s[i], à string inversa. Dessa forma, os caracteres são adicionados na ordem inversa.

Agora podemos criar uma função de teste para verificar o funcionamento da nossa função inverte.

using Test

function testeInverte()
  @test inverte("123") == "321"
  @test inverte("x") == "x"
  @test inverte("SOS") == "SOS"
  @test inverte("tres") == "sert"
end
testeInverte (generic function with 1 method)

16.2.1 Função reverse

É interessante notar que Julia já fornece uma função chamada reverse, que pode ser utilizada para inverter tanto vetores quanto strings. Por exemplo:

reversa = reverse("exemplo")
"olpmexe"

Neste exemplo, a função reverse recebe como parâmetro apenas o objeto a ser invertido, mas no caso de vetores, podemos ainda informar exatamente o intervalo que desejamos que seja invertido.

vetor = [1, 2, 3, 4, 5]
reversa = reverse(vetor, 2, 4)
5-element Vector{Int64}:
 1
 4
 3
 2
 5

16.3 Modificação de String

A terceira função modifica altera uma string que termina com “ing” para adicionar “ly” ou, caso contrário, adiciona “ing”.

function modifica(s::String)::String
    if length(s) < 3
        return "Erro: tamanho da string menor do que 3"
    end
    
    if s[end-2:end] == "ing"
        s = s*"ly"
    else    
        s = s*"ing"                
    end


    return s
end
modifica (generic function with 1 method)

Neste exemplo, verificamos manualmente os últimos três caracteres da string s. No entanto, Julia oferece uma função mais prática e legível chamada endswith, que podemos usar para simplificar essa verificação.

function modifica(s::String)::String
    if length(s) < 3
        return "Erro: tamanho da string menor do que 3"
    end
    
    if endswith(s, "ing")
        s = s*"ly"
    else    
        s = s*"ing"                
    end

    return s
end
modifica (generic function with 1 method)

Vamos então escrever o teste que verifica o correto funcionamento das funções anteriores

using Test
function testaModifica()
  @test modifica("doing") == "doingly"
  @test modifica("sing") == "singly"
  @test modifica("run") == "runing"
  @test modifica("talk") == "talking"
end
testaModifica (generic function with 1 method)

16.4 Rearranjo de letras

A segunda função rearranja recebe uma string e devolve uma string que contém as letras minúsculas primeiro, seguidas pelas letras maiúsculas.

Podemos verificar se uma letra é maiúscula ou minúscula usando a tabela ASCII, que codifica caracteres em números inteiros. Na tabela, as letras maiúsculas estão no intervalo de 65 a 90, e as letras minúsculas no intervalo de 97 a 122.

Para saber mais sobre a tabela ASCII você pode acessar essa página.

function rearranja(s::String)::String
    maiusculos=""
    minusculos=""

    for i in 1:length(s)
        if Int(s[i]) >= 65 && Int(s[i]) <= 90 
            maiusculos = maiusculos*s[i]
        elseif Int(s[i]) >= 97 && Int(s[i]) <= 122
            minusculos = minusculos*s[i]
        end 
    end

    return minusculos*maiusculos
     
end
rearranja (generic function with 1 method)

Uma abordagem mais legível é utilizar as funções islowercase e isuppercase, que verificam se uma letra é minúscula ou maiúscula, respectivamente.

function rearranja(s::String)::String
    maiusculos=""
    minusculos=""

    for i in 1:length(s)
        if isuppercase(s[i]) 
            maiusculos = maiusculos*s[i]
        elseif islowercase(s[i])
            minusculos = minusculos*s[i]
        end 
    end

    return minusculos*maiusculos
     
end
rearranja (generic function with 1 method)

Podemos então escrever o teste para nossas funções.

using Test

function testaRearranja()
  @test rearranja1("PaRaLelO") == "aaelPRLO"
  @test rearranja1("ELEfantE") == "fantELEE"
  @test rearranja1("Olá") == "lO"
  @test rearranja1("13La2") == "aL"
  @test rearranja2("PaRaLelO") == "aaelPRLO"
  @test rearranja2("ELEfantE") == "fantELEE"
  @test rearranja2("Olá") == "láO"
  @test rearranja2("13La2") == "aL"
end
testaRearranja (generic function with 1 method)

16.5 Encontrar a maior palavra

Nossa última função deve receber uma lista de palavras e retornar a maior delas, junto de seu tamanho.

function maior_palavra(vetor::Vector{String})
    # Inicialmente, a maoior palavra que encontramos é uma string vazia
    maior_palavra = ""
    maior_tamanho = 0

    for palavra in vetor
        #  Verifica se a palavra atual é maior que a maior encontrada até agora
        if length(palavra) > maior_tamanho 
            maior_palavra = palavra
            maior_tamanho = length(palavra)
        end
    end

    return maior_palavra, maior_tamanho

end
maior_palavra (generic function with 1 method)

Apesar de parecer correto, esse código não lida com o caso de haver mais de uma palavra com o maior tamanho. Como por exemplo:

vetor = ["boa", "bem", "oi"]
maior_palavra(vetor)
("boa", 3)

Nesse caso, apenas a palavra “boa” será retornada, mesmo que “bem” tenha o mesmo tamanho. Para consertar a função devemos alterar a variável em que guardamos a maior palavra, para que possamos armazenar mais de uma palavra, para isso vamos usar um vetor de strings.

function maiores_palavras(vetor::Vector{String})
    maiores_palavras = String[]
    maior_tamanho = 0

    for palavra in vetor
        # Se a palavra é maior do que o maior tamanho salvo, 
        # então todas as palavras que estão no vetor maior_palavra são menores do que a palavra atual
        if length(palavra) > maior_tamanho 
            # Limpa o vetor e salva a palavra atual
            maiores_palavras = String[]
            push!(maiores_palavras, palavra)
            maior_tamanho = length(palavra)

        # Se é igual ao tamanho salvo, então é do mesmo tamanho que as palavras já salvas no vetor maiores_palavras,
        # apenas damos push na palavra atual
        elseif length(palavra) == maior_tamanho
            push!(maiores_palavras, palavra) 
        end
    end

    return maiores_palavras, maior_tamanho

end
maiores_palavras (generic function with 1 method)

Assim podemos escrever testes para esta última função .

using Test

function testeMaioresPalavras()
    vetor1 = ["gato", "elefante", "cachorro"]
    @test maiores_palavras(vetor1) == (["elefante", "cachorro"], 8)  
    
    vetor2 = ["a", "ab", "abc"]
    @test maiores_palavras(vetor2) == (["abc"], 3)        
    
    vetor3 = ["bem", "boa", "bom", "oi"]
    @test maiores_palavras(vetor3) == (["bem", "boa", "bom"], 3)       

    vetor4 = ["", " ", "teste"]
    @test maiores_palavras(vetor4) == (["teste"], 5)      

    vetor5 = String[]
    @test maiores_palavras(vetor5) == ([], 0)              

    vetor6 = ["a", "ab", "abc", "xyz", "xy"]
    @test maiores_palavras(vetor6) == (["abc", "xyz"], 3)  
end
testeMaioresPalavras (generic function with 1 method)

16.6 Retorno de múltiplos valores

Como visto no exercício anterior, Julia permite que uma função retorne múltiplos valores. Isso permite que você envie mais de um resultado ao chamar uma função, tornando o código mais conciso e fácil de entender. Essa funcionalidade é especialmente útil em situações onde você precisa de mais de um resultado, como em operações matemáticas, decomposições, ou processamento de dados.

Para retornar múltiplos valores em Julia, você pode simplesmente separá-los por vírgulas. Aqui está um exemplo simples:

function troca(a,b)
    aux = a
    a = b
    b = aux

    return a, b
end
troca (generic function with 1 method)

Ao chamar essa função, você pode capturar os múltiplos valores retornados em variáveis separadas:

a, b = troca(1, 10)
(10, 1)