-
Notifications
You must be signed in to change notification settings - Fork 265
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Minha solução para o desafio 06. #1177
Open
AlexPHorta
wants to merge
25
commits into
OsProgramadores:master
Choose a base branch
from
AlexPHorta:dev
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 19 commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
2d25e23
Trazendo do celular
018f083
Fazendo a última função.
d78880a
Apagando comentários.
d46a83d
Chegando lá
3b7ad69
Filtra as possíveis partições para serem testadas.
de59b5e
Lidando com palavras de uma letra só
9960622
Força bruta não funciona
b8b06ea
Filtragem dos anagramas funciona, mas não é suficiente.
e975600
Bem melhor sem gerar outro dicionário.
2c11dcb
Evitando entradas duplicadas na hora de gerar os anagramas.
26e61d9
Muito melhor, mas ainda não suficiente.
417a897
Finalizado?
7205078
Corrigindo import
2560e2f
Opções para a linha de comando.
3583110
Pequenas correções na linha de comando
0e16a40
README pronto
1c409ef
Linter
1485a76
Corrige link no README
c26c34b
Finalizei as docstrings
b441462
Reverting to 2560e2f
880e36a
Changing argparse stuff
5ad9cd8
More tests in prep
65eb6ef
Better docstrings
57ab189
Linter
6ada9e6
Final README
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
# README | ||
|
||
## Desafio 6 (Linguagem: Python) | ||
|
||
O desafio consiste em localizar anagramas de uma expressão, de no máximo 16 caracteres, fornecida pelo usuário na linha de comando. Esses anagramas deverão ser compostos por palavras obtidas no arquivo 'words.txt', fornecido pelo proponente do desafio. | ||
|
||
Minha solução foi escrita usando a linguagem Python, nas versões 3.12 e 3.13, e não utiliza bibliotecas externas. Ela consiste em, primeiramente, reduzir o campo de busca ao mínimo usando uma série de funções (aquelas relacionadas em _shrink_search_field_). Após, utilizei um algoritmo obtido na web (Livre para uso, segundo o autor.) para encontrar todas as partições ([Função de Partição](https://pt.wikipedia.org/wiki/Fun%C3%A7%C3%A3o_de_parti%C3%A7%C3%A3o_(matem%C3%A1tica))) possíveis para o número de caracteres contidos na expressão fornecida pelo usuário e escrevi uma função (_shrink_partitions_) para eliminar as que não fossem adequadas para a obtenção dos anagramas. | ||
|
||
Com as partições definidas, escrevi uma função (_find_in_partition_) que busca os anagramas no arquivo 'words.txt', utilizando um dicionário para mapeamento dos totais de caracteres contidos na expressão e nas diversas palavras a serem testadas. | ||
|
||
## Modo de Usar | ||
|
||
Abra um terminal e execute o programa, assim: | ||
|
||
```bash | ||
$ python desafio06.py VERMELHO | ||
ELM HO REV | ||
ELM OH REV | ||
ELM HOVER | ||
LEVER OHM | ||
OHM REVEL | ||
HELM OVER | ||
HELM ROVE | ||
HOLM VEER | ||
``` | ||
|
||
Para ajuda sobre o uso do programa, digite no terminal `python desafio06.py --help`. Os testes podem ser executados com `python test_desafio06.py` (Optei por deixá-los em um arquivo separado.). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,326 @@ | ||
import functools | ||
import itertools | ||
import operator | ||
import string | ||
|
||
|
||
WORDS = 'words.txt' | ||
|
||
|
||
def print_anagrams(expression, words_file): | ||
"""Print all anagrams from expression, found in words_file. | ||
|
||
Positional arguments: | ||
expression -- the expression to be used for the search of anagrams | ||
words_file -- path to the file with words | ||
""" | ||
with open(words_file) as words: | ||
_anagrams = list(m for m in find_matches(expression, words)) | ||
if len(_anagrams) == 1 and len(_anagrams[0]) == 0: | ||
print(f"Nenhum anagrama encontrado para a expressão: {expression}") | ||
for anagram in _anagrams: | ||
for a in anagram: | ||
print(' '.join(a)) | ||
|
||
def prep(expression): | ||
"""Prepare user input to be used by the program. | ||
|
||
Positional arguments: | ||
expression -- the expression to be used for the search of anagrams | ||
""" | ||
if expression == "": | ||
return expression | ||
|
||
res = "" | ||
|
||
for c in expression: | ||
if c not in string.ascii_letters: | ||
continue | ||
res += c.upper() | ||
|
||
return res | ||
|
||
def find_matches(expression, words_file): | ||
"""Find all anagram matches for, found in words_file. | ||
|
||
Positional arguments: | ||
expression -- the expression to be used for the search of anagrams | ||
words_file -- path to the file with words | ||
""" | ||
candidates = shrink_search_field(expression, words_file) | ||
res = [] | ||
grouped_by_len = group_by_len(candidates) | ||
valid_partitions = shrink_partitions(expression, grouped_by_len) | ||
|
||
for v in valid_partitions: | ||
res.extend(find_in_partition(expression, v, grouped_by_len)) | ||
|
||
yield res | ||
|
||
def find_in_partition(expression, partition, grouped): | ||
"""Search possible matches in each partition group. | ||
|
||
Positional arguments: | ||
expression -- the expression to be used for the search of anagrams | ||
partition -- the expression's partition to be used for the search | ||
grouped -- dictionary with the word matches, grouped by length | ||
""" | ||
letters_in_expression = quant_letters(expression) | ||
group_lengths = [range(len(grouped[p])) for p in partition] | ||
|
||
group_letters = {} | ||
for k, v in grouped.items(): | ||
letters = [quant_letters(w) for w in v] | ||
group_letters.update({k:letters}) | ||
|
||
res = [] | ||
|
||
for g in itertools.product(*group_lengths): | ||
letters_in_prod = dict(letters_in_expression) | ||
words = [] | ||
fits = True | ||
selected_words = sorted([grouped[partition[i]][j] for i, j in enumerate(g)]) | ||
if selected_words in words: | ||
continue | ||
for i, j in enumerate(g): | ||
word = grouped[partition[i]][j] | ||
word_sum = group_letters[partition[i]][j] | ||
for k, v in word_sum.items(): | ||
letters_in_prod[k] -= v | ||
if letters_in_prod[k] < 0: | ||
fits = False | ||
break | ||
if not fits: | ||
break | ||
words.append(word) | ||
if not fits: | ||
continue | ||
words = sorted(words) | ||
if words not in res: | ||
res.append(words) | ||
|
||
return res | ||
|
||
def group_by_len(candidates): | ||
"""Group the words by length. | ||
|
||
Positional arguments | ||
candidates -- a list with the possible words to make the anagrams | ||
""" | ||
candidates = sorted(candidates, key=len) | ||
res = {} | ||
grouped = [] | ||
for _, g in itertools.groupby(candidates, len): | ||
grouped.append(list(g)) | ||
lengths = [len(l[0]) for l in grouped] | ||
for l, g in zip(lengths, grouped): | ||
res[l] = g | ||
return res | ||
|
||
def shrink_partitions(expression, grouped): | ||
"""Remove all partitions that dont't need to be checked. | ||
|
||
Positional arguments | ||
expression -- the expression to be used for the search of anagrams | ||
grouped -- dictionary with the word matches, grouped by length | ||
""" | ||
partitions = list(accel_asc(len(expression))) | ||
available = list(grouped.keys()) | ||
res = [] | ||
|
||
# If a partition includes words whose length is not in one of the available | ||
# partitions, don't include it. | ||
for partition in partitions: | ||
for e in partition: | ||
if e not in available: | ||
break | ||
else: | ||
res.append(partition) | ||
|
||
rem = [] | ||
no_solo = not_solo(expression, grouped) | ||
|
||
for r in res: | ||
if set(r) <= set(no_solo): | ||
rem.append(r) | ||
|
||
# If there's a group 1, remove all partitions that have more ones than the length of group 1. | ||
ones = 1 in available | ||
if ones: | ||
gg = grouped.get('1') | ||
len_ones = len(gg) if gg is not None else 1 | ||
for r in res: | ||
if r.count(1) > len_ones: | ||
rem.append(r) | ||
|
||
for r in rem: | ||
try: | ||
remover = res.index(r) | ||
del res[remover] | ||
except ValueError: | ||
continue | ||
|
||
return res | ||
|
||
def not_solo(expression, grouped): | ||
"""Return length groups that don't include all the letters in expression. | ||
|
||
Positional arguments | ||
expression -- the expression to be used for the search of anagrams | ||
grouped -- dictionary with the word matches, grouped by length | ||
""" | ||
in_expression = set(expression) | ||
res = [] | ||
for k, g in grouped.items(): | ||
g = [list(i) for i in g] | ||
available = set(functools.reduce(operator.iconcat, g, [])) | ||
remaining = in_expression - available | ||
if len(remaining) > 0: | ||
res.append(k) | ||
return res | ||
|
||
def shrink_search_field(expression, words_file): | ||
"""Shrink the search field for possible anagrams, before considering | ||
partitions of expression. | ||
|
||
Positional arguments | ||
expression -- the expression to be used for the search of anagrams | ||
words_file -- path to the file with words | ||
""" | ||
res = [] | ||
for w in words_file: | ||
w = w.strip() | ||
le = sieve_less_or_equal(expression, w) | ||
sw = sieve_starts_with(expression, w) | ||
rm = sieve_remaining(expression, w) | ||
ql = sieve_number_of_letters(expression, w) | ||
if all((le, sw, rm, ql)): | ||
res.append(w) | ||
return res | ||
|
||
def sieve_number_of_letters(expression, word): | ||
"""Check if a word is contained in expression. Remove those that can't be. | ||
|
||
Positional arguments | ||
expression -- the expression to be used for the search of anagrams | ||
word -- the word to be compared | ||
""" | ||
expression_quant = quant_letters(expression) | ||
word_quant = quant_letters(word) | ||
for k, v in word_quant.items(): | ||
if expression_quant.get(k) is not None and v > expression_quant[k]: | ||
return False | ||
return True | ||
|
||
def quant_letters(a_word): | ||
"""Return a mapping of the number of the different letters in a word. | ||
|
||
Positional arguments | ||
a_word -- the word to be mapped | ||
""" | ||
keys = set(a_word) | ||
quanto = {k:a_word.count(k) for k in keys} | ||
return quanto | ||
|
||
def sieve_remaining(expression, word): | ||
"""Remove any word that has letters that are not in expression. | ||
|
||
Positional arguments | ||
expression -- the expression to be used for the search of anagrams | ||
word -- the word to be evaluated | ||
""" | ||
letters = set(expression) | ||
uppercase = set(string.ascii_uppercase) | ||
remaining = uppercase - letters | ||
return not any((l in remaining) for l in word) | ||
|
||
def sieve_starts_with(expression, word): | ||
"""Return only words that begin with one of the letters in expression. | ||
|
||
Positional arguments | ||
expression -- the expression to be used for the search of anagrams | ||
word -- the word to be evaluated | ||
""" | ||
letters = set(expression) | ||
return any(word.startswith(l) for l in letters) | ||
|
||
def sieve_less_or_equal(expression, word): | ||
"""Exclude words that are lengthier than expression. | ||
|
||
Positional arguments | ||
expression -- the expression to be used for the search of anagrams | ||
word -- the word to be evaluated | ||
""" | ||
return len(expression) >= len(word) | ||
|
||
# Peguei de https://jeromekelleher.net/generating-integer-partitions.html | ||
def accel_asc(n): | ||
"""Yield all partitions of a given integer. | ||
E.g.: The partitions of three are: 1,1,1; 1,2; 2,1; 3 | ||
|
||
Positional arguments | ||
n -- the integer to be used | ||
""" | ||
a = [0 for i in range(n + 1)] | ||
k = 1 | ||
y = n - 1 | ||
while k != 0: | ||
x = a[k - 1] + 1 | ||
k -= 1 | ||
while 2 * x <= y: | ||
a[k] = x | ||
y -= x | ||
k += 1 | ||
l = k + 1 | ||
while x <= y: | ||
a[k] = x | ||
a[l] = y | ||
yield a[:k + 2] | ||
x += 1 | ||
y -= 1 | ||
a[k] = x + y | ||
y = x + y - 1 | ||
yield a[:k + 1] | ||
|
||
|
||
if __name__ == "__main__": | ||
|
||
import gettext | ||
import sys | ||
|
||
def translate(text): | ||
text = text.replace("usage", "modo de usar") | ||
text = text.replace("positional arguments", "argumentos posicionais") | ||
text = text.replace("show this help message and exit", | ||
"Exibe esta mensagem de ajuda e termina a execução.") | ||
text = text.replace("error", "erro") | ||
text = text.replace("options", "opções") | ||
text = text.replace("the following arguments are required", | ||
"os seguintes argumentos são obrigatórios") | ||
return text | ||
gettext.gettext = translate | ||
|
||
import argparse | ||
|
||
parser = argparse.ArgumentParser( | ||
description="Imprime todos os anagramas de TERMO encontrados no\ | ||
arquivo 'words.txt' (Que deverá estar na mesma pasta do programa.)." | ||
) | ||
|
||
parser.add_argument( | ||
"TERMO", | ||
nargs = 1, | ||
help="O termo a ser usado para a busca de anagramas." | ||
) | ||
|
||
args = parser.parse_args() | ||
|
||
if len(args.termo) == 0: | ||
parser.print_help() | ||
|
||
if args.termo: | ||
try: | ||
assert len(args.termo) == 1, "Número excessivo de argumentos." | ||
print_anagrams(sys.argv[1], WORDS) | ||
except Exception as exc: | ||
raise ValueError(f"{exc}") from exc |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dá erro se eu digito números como argumento.
Você tem que tratar isso:
➜ testes-desafios git:(master) ✗ python3 des6.py 902823
Traceback (most recent call last):
File "/home/marcelo/testes-desafios/des6.py", line 318, in
if len(args.termo) == 0:
^^^^^^^^^^