Introdução Link para o cabeçalho
Termo é um jogo de adivinhação de palavras. Diariamente, uma palavra de cinco letras é escolhida aleatoriamente e os usuários devem, através de dicas, identificar que palavra é essa. As regras completas estão no print abaixo:
Como eu já havia criado um grande dicionário de palavras do português brasileiro para um antigo posto do blog chamado A maior palavra da lÃngua portuguesa cujas letras estejam em ordem alfabética , resolvi utilizá-lo aqui novamente. A partir deste dicionário, quero procurar procurar dicas de palavras para jogar Termo.
Criando o Dicionário Link para o cabeçalho
Meu post anterior detalha um pouco melhor a criação do dicionário, mas basicamente eu baixei duas listas de palavras que encontrei na internet, juntei-as e removi as palavras duplicadas.
library(stringi)
library(tidyverse)
palavras_usp <-
read.csv(file = "https://www.ime.usp.br/~pf/dicios/br-utf8.txt",
header = FALSE) |>
mutate(V1 = stri_trans_general(str = V1, id = "Latin-ASCII"))
dim(palavras_usp)
## [1] 261798 1
palavras_github <-
read.csv("https://github.com/pythonprobr/palavras/blob/master/palavras.txt?raw=true",
header = FALSE) |>
mutate(V1 = stri_trans_general(str = V1, id = "Latin-ASCII"))
dim(palavras_github)
## [1] 320139 1
palavras <-
palavras_usp |>
bind_rows(palavras_github) |>
arrange(V1) |>
distinct()
dim(palavras)
## [1] 540523 1
Com este conjunto de 540523 palavras únicas, agora eu preciso filtrar apenas aquelas palavras com cinco letras para poder jogar Termo:
termo <-
palavras |>
mutate(tamanho = nchar(V1)) |>
filter(tamanho == 5) |>
select(palavra = V1) |>
mutate(palavra = tolower(palavra))
head(termo)
## palavra
## 1 abglt
## 2 aeead
## 3 aneel
## 4 ascii
## 5 aaiun
## 6 aarao
Mas além da lista compilada de palavras de cinco letras, eu necessito alguma técnica para saber qual palavra tentar na hora de preencher o Termo Como é interessante testar palavras cujas letras ocorram frequentemente na lÃngua portuguesa, seria interessante ter um score que diferenciasse palavras com letras mais e menos prováveis. Por exemplo, é esperado que uma palavra como SOMAR proporcione mais letras acertadas do que uma palavra como XEQUE. Afinal, intuitivamente sabemos que S, A e R são letras mais comuns do que X, Q e U, por exemplo.
Descrevo a criação do score na próxima seção.
Criação do Score Link para o cabeçalho
Pensando no problema da criação do score, descobri a página Frequência de Letras da Wikipedia, que me ajuda tremendamente nisso. A partir dela baixeu a tabela de frequência de letras para o português disponÃvel nesta página. Com esta tabela calculei um score para cada palavra do meu banco de dados. O código para isso está a seguir:
library(rvest)
library(janitor)
url <- "https://pt.wikipedia.org/wiki/Frequ%C3%AAncia_de_letras"
frequencia_letras <-
html_table(read_html(url)) |>
pluck(1) |>
clean_names() |>
mutate(frequencia = gsub("%", "", frequencia)) |>
mutate(frequencia = as.numeric(frequencia))
Neste teste que estou fazendo, criei o score simplesmente somando as frequências das letras de cada palavra, a fim de utilizar as maiores somas em meus chutes. Este valor não tem nenhum significado bem definido, sendo apenas uma pontuação artificial para cada palavra. Para realizar este cálculo, criei as funções valor_letra
, que determina a frequência de cada letra do alfabeto de acordo com a tabela obtida a partir da Wikipedia e valor_palavra
, que desmembra cada palavra e calcula o score dela, somando as frequências de cada letra.
valor_letra <- function(x){
frequencia <-
frequencia_letras |>
filter(letra %in% x) |>
pluck(2)
return(frequencia)
}
# funcao para encontrar o valor de cada palavra
valor_palavra <- function(palavra){
palavra_vetor <- unlist(strsplit(palavra, split = ""))
soma <- sum(valor_letra(palavra_vetor))
return(soma)
}
Aplicando isso no banco de dados, obtemos os seguintes resultados:
n <- dim(termo)[1]
score <- rep(NA, n)
for (j in 1:n){
score[j] <- valor_palavra(termo$palavra[j])
}
termo <-
termo |>
bind_cols(score = score) |>
tibble()
termo |>
arrange(desc(score)) |>
head(20)
## # A tibble: 20 × 2
## palavra score
## <chr> <dbl>
## 1 rosea 52.3
## 2 saroe 52.3
## 3 serao 52.3
## 4 anoes 50.8
## 5 aseno 50.8
## 6 naseo 50.8
## 7 senao 50.8
## 8 adeso 50.7
## 9 sedao 50.7
## 10 maseo 50.5
## 11 mesao 50.5
## 12 estao 50.1
## 13 tesao 50.1
## 14 aceso 49.6
## 15 acoes 49.6
## 16 coesa 49.6
## 17 ecoas 49.6
## 18 escoa 49.6
## 19 secao 49.6
## 20 areno 49.5
Note os maiores scores são das palavras ROSEA, SAROE e SERAO, anagramas formados pelas mesmas cinco letras bastante comuns na lÃngua portuguesa. Podemo comparar palavras distintas, como SOMAR e FUGIU, para ver como estão os scores dela:
termo |>
filter(palavra == "somar")
## # A tibble: 1 × 2
## palavra score
## <chr> <dbl>
## 1 somar 44.4
termo |>
filter(palavra == "fugiu")
## # A tibble: 1 × 2
## palavra score
## <chr> <dbl>
## 1 fugiu 13.1
Como esperado, o score de SOMAR é superior ao de FUGIU. Afinal, intuitivamente, as frequências das letras S, O, M, A e R parecem ser maiores do que as de F, U, G e I, além de ser o somatório de 5 letras em vez de 4. Com esta medida rudimentar, podemos passar à aplicação desta técnica à palavra selecionada para o Termo de hoje, dia 7 de março de 2024.
Como as palavras com maiores scores são ROSEA, SAROE e SERAO, todas as três com a mesma pontuação, eu começarei meu Termo com SERAO.
Já é possÃvel perceber que as letras S, E, R e O não fazem parte da palavra de hoje. Portanto, basta aplicar uma expressão regular no objeto termo
para manter apenas as palavras que possuem A em alguma posição e não possuem S, E, R e O para obtermos as nossas próximas palavras candidatas:
termo_02 <-
termo |>
filter(grepl("a", palavra)) |>
filter(!grepl("s", palavra)) |>
filter(!grepl("e", palavra)) |>
filter(!grepl("r", palavra)) |>
filter(!grepl("o", palavra)) |>
arrange(desc(score))
termo_02 |>
head(10)
## # A tibble: 10 × 2
## palavra score
## <chr> <dbl>
## 1 andim 35.6
## 2 mandi 35.6
## 3 minda 35.6
## 4 nadim 35.6
## 5 dunia 35.5
## 6 duina 35.5
## 7 iandu 35.5
## 8 iduna 35.5
## 9 indua 35.5
## 10 undai 35.5
Vou usar ANDIM como meu próximo chute, pois ela é a palavra de maior pontuação que satisfaz este critério:
Agora vemos que D, I e M são letras que não estão na palavra final. Fazendo uma nova filtragem, temos
termo_03 <-
termo_02 |>
filter(!grepl("d", palavra)) |>
filter(!grepl("i", palavra)) |>
filter(!grepl("m", palavra)) |>
arrange(desc(score))
termo_03 |>
head(10)
## # A tibble: 10 × 2
## palavra score
## <chr> <dbl>
## 1 cantu 32.5
## 2 tunal 31.4
## 3 culna 31.0
## 4 nucal 31.0
## 5 culta 30.3
## 6 caput 30
## 7 nguta 30.0
## 8 hunta 29.9
## 9 bantu 29.7
## 10 cangu 29.5
É importante tomar cuidado a parti de agora, pois já temos três informações a respeito da letra A:
- a letra A está na palavra
- a letra A não está na posição 1
- a letra A não está na posição 4
Adicionando isso ao código anterior, junto com o fato de N estar na palavra mas não poder estar na segunda posição, e atualizando o objeto termo_03
, temos:
termo_03 <-
termo_02 |>
filter(!grepl("d", palavra)) |>
filter(!grepl("i", palavra)) |>
filter(!grepl("m", palavra)) |>
filter(!grepl("an[a-z][a-z][a-z]", palavra)) |>
filter(!grepl("[a-z][a-z][a-z]a[a-z]", palavra)) |>
filter(grepl("n", palavra)) |>
arrange(desc(score))
termo_03 |>
head(10)
## # A tibble: 10 × 2
## palavra score
## <chr> <dbl>
## 1 cantu 32.5
## 2 culna 31.0
## 3 nguta 30.0
## 4 hunta 29.9
## 5 bantu 29.7
## 6 cangu 29.5
## 7 chuna 29.5
## 8 cunha 29.5
## 9 junta 29.0
## 10 naut. 28.6
Como CANTU, CULNA, NGUTA e HUNTA não são palavras constantes no Termo, jogamos com BANTU:
Agora precisamos atualizar nossa última filtragem para manter apenas as palavras da lista que satisfazem todas as outras caracterÃsticas, não possuem as letras T e U e que começam com BAN:
termo_04 <-
termo_03 |>
filter(!grepl("d", palavra)) |>
filter(!grepl("i", palavra)) |>
filter(!grepl("m", palavra)) |>
filter(!grepl("an[a-z][a-z][a-z]", palavra)) |>
filter(!grepl("[a-z][a-z][a-z]a[a-z]", palavra)) |>
filter(grepl("n", palavra)) |>
filter(!grepl("t", palavra)) |>
filter(!grepl("u", palavra)) |>
filter(grepl("ban[a-z][a-z]", palavra)) |>
arrange(desc(score))
termo_04 |>
head(10)
## # A tibble: 2 × 2
## palavra score
## <chr> <dbl>
## 1 banca 24.6
## 2 banha 22
E agora há apenas duas candidatas: BANCA e BANHA. Como só usamos três tentativas até agora, é garantido que acertemos a palavra final, pois teremos testado no máximo cinco palavras:
Bingo! Palavra descoberta.
Logicamente há formas de melhorar o desempenho deste método. A primeira que me ocorre é procurar e utilizar o dicionário criado pelo próprio Termo para escolher as suas palavras. Outra seria procurar um corpus do português com a frequência das palavras, e não das suas letras, para evitar eventuais discrepâncias que certamente existem no score que criei.
Outra forma é adaptar o método que o canal 3 Blue 1 Brown usou para a lÃngua inglesa, via teoria da informação, de modo a obter as melhores palavras para testar a cada passo do Termo.