Gerar uma lista de String ([String]) de uma lista de char e int [([Char], Int)] em haskell

Oct 04 2020

Eu tenho este código:

numeros :: Int -> Int -> [Int]
numeros x y 
    | x < y = [n | n <- [1..x]]
    | otherwise = [n | n <- [1..x+1], n /= y]

andares :: [([Char],Int)] -> [Int]
andares xs = [b | (a,b) <- xs]

lado :: [([Char],Int)] -> [[Char]]
lado xs = [a | (a,b) <- xs]


legendaCampainha :: Int -> Int -> [([Char],Int)] -> [String]
legendaCampainha x y xs = ...

Preciso gerar algo assim:

> legendaCampainha 10 4 [("Dto", 10), ("Esq", 8), ("Cent", 3)]

["1Dto","1Esq","1Cent","2Dto","2Esq","2Cent","3Dto","3Esq",
"3Cent","5Dto","5Esq","6Dto","6Esq","7Dto","7Esq","8Dto",
"8Esq","9Dto","9Esq","10Dto","11Dto"]

O 10 é o número de andares, 4 é o número azarado (então, os andares são 1,2,3,5,6 ... 11), "dto" "esq" "cent" são para dizer se o apartamento está ligado o lado direito, esquerdo ou meio, e os números à frente (10,8,3) são para dizer quantos apartamentos estão à direita, esquerda ou meio. Por exemplo, existem 10 apartamentos do lado direito ("dto") e 8 do lado esquerdo ("esq").

Já criei 3 funções para me ajudar a fazer isso, numeros são os números do andar sem o número do azar (neste caso é [1,2,3,5,6,7,8,9,10,11]. Andares são os números de quantos apartamentos estão em um lado específico (10,8,3). Lado é para ler quais são os lados, neste caso, ["Dto," Esq "," Cent "].

Só preciso de uma dica para finalizar a função legendaCampainha

Respostas

1 bradrn Oct 04 2020 at 18:05

Não vou resolver o seu problema para você, mas aqui vai uma dica conforme você solicitou: Isso será mais fácil se você definir mais uma função auxiliar, do tipo [Char] -> [Int] -> [Char]. Esta função pegará uma string e uma lista de andares e acrescentará cada número de andar à string. Então, por exemplo, dados dados "Dto"e [1,2,3,5,6], ele produzirá o resultado ["1Dto", "2Dto", "3Dto", "5Dto", "6Dto"]. Em seguida, você poderá terminar legendaCampainhaaplicando essa função a cada elemento de sua xsentrada e, em seguida, combinando os resultados.


Além disso, gostaria de falar um pouco sobre o resto do seu código. Parece absolutamente bom funcionalmente, mas quero dar uma olhada mais de perto em sua legibilidade.

Primeiro numeros:

numeros :: Int -> Int -> [Int]
numeros x y 
    | x < y = [n | n <- [1..x]]
    | otherwise = [n | n <- [1..x+1], n /= y]

Para mim, há uma coisa que noto imediatamente sobre essa função: a [n | n <- [1..x]]compreensão de lista. Se você olhar para ele, verá que está pegando a lista [1..x], obtendo cada valor da lista por vez e, em seguida, apenas retornando esse valor inalterado. Em outras palavras, [n | n <- [1..x]]poderia ser mais facilmente escrito de forma simples [1..x]. Muito mais fácil de ler!

Além disso, a segunda parte desta função pode ser um pouco simplificada, usando a função predefinida filter. Se você não encontrou essa função antes, filter fn listaplicará a fnfunção a cada elemento de liste, em seguida, excluirá cada elemento de listpara o qual fnretorna verdadeiro. O que corresponde exatamente ao que a compreensão da lista está fazendo! Em particular, você deseja filtrar com a (/= y)função, que recebe um único argumento e retorna Truequando esse argumento não é igual a y. Então você pode substituir [n | n <- [1..x+1], n /= y]por filter (/= y) [1..x+1].

Finalmente, você pode substituir os guardas com um if expressão: if x < y then [n | n <- [1..x]] else [n | n <- [1..x+1], n /= y]. Mas isso é uma questão de preferência pessoal e não qualquer outra coisa - algumas pessoas acham os guardas mais fáceis de ler, outras preferem suas expressões.

Próximo, andarese lado:

andares :: [([Char],Int)] -> [Int]
andares xs = [b | (a,b) <- xs]

lado :: [([Char],Int)] -> [[Char]]
lado xs = [a | (a,b) <- xs]

Da mesma forma que a compreensão da segunda lista, essas funções podem ser simplificadas pelo uso criterioso de funções predefinidas. Nesse caso, o relevante é map: map fn listserá aplicado fna cada valor de e list, em seguida, coletará esses valores em uma nova lista. Então, por exemplo map (+1) [1,5,10] == [2,6,11]. Além disso, há mais duas funções úteis que você deve conhecer: fstretornará o primeiro valor de uma tupla de 2 e sndretornará o segundo valor de uma tupla de 2. Juntando tudo isso, fica claro que andares xs = map snd xse lado xs = map fst xs- ou, mais simplesmente, andares = map snde lado = map fst.

E mais um comentário: Em Haskell, Stringé sinônimo de [Char]. Vejo que você já usou Stringem sua assinatura de tipo para legendaCampainha, mas o uso mais consistente pode tornar as assinaturas de tipo mais fáceis de entender quando você está trabalhando muito com strings.

Então, juntando todas as minhas sugestões, posso reescrever seu código da seguinte maneira:

numeros :: Int -> Int -> [Int]
numeros x y = if x < y then [1..x] else filter (/=y) [1..x+1]

andares :: [(String,Int)] -> [Int]
andares = map snd

lado :: [(String,Int)] -> [String]
lado = map fst

legendaCampainha :: Int -> Int -> [(String,Int)] -> [String]
legendaCampainha x y xs = ...

Agora, só quero dizer: tudo isso é, fundamentalmente, uma questão de preferência pessoal. Se você acha que seu código original foi mais fácil de ler e entender, então - ótimo! Use isso! Meu objetivo aqui era apenas sugerir algumas alternativas de que você poderia gostar mais e apresentar algumas outras áreas de Haskell das quais você pode não ter conhecimento antes.

(And finally, one more hint, now that I’ve talked a bit about map and filter: looking back at my suggested function at the beginning of the post, it should be possible to write it using only map, show and ++. Can you figure out how to do that?)