Генератор за хайкута

Краен срок
13.12.2022 18:00

Срокът за предаване на решения е отминал

Предизвикателството е просто - искаме от вас да направите безкраен генератор на хайку редове.

Първо, какво представляват хайкутата?

Mandatory wikipedia quote:

Хайку е най-късата поетична форма в световната литература. Въпреки че се състои само от три реда, съдържащи съответно 5 – 7 – 5 срички, то може да изрази и дълбоко чувство, и проблясък на интуицията. В хайку няма символи. То отразява живота в неговото свободно движение. Притежава чистотата, завършеността и дори празнотата на една музикална нота.

За нашите цели, фокусът ще е единствено върху правилната форма на хайкутата:

  • Първи ред - 5 срички
  • Втори ред - 7 срички
  • Трети ред - 5 срички

Пример:

Кратък хайку ред
Леко дълъг хайку ред
И пак пет срички

Как броим сричките?

С цел да не ви вкарваме в сферата на лексикалния анализ, решихме, че е по-добре да пишете хайкута на български, отколкото на английски (колко срички има в думата "through"?). Обмисляхме японски, но решихме, че няма да можем да прочетем творбите ви лесно, затова останахме на български.
С други думи - една сричка се определя от наличието на гласна буква. Правилата за сричкуване са малко по-сложни от това, но тъй като нас няма да ни вълнува дали сричките са отворени или затворени, как точно разцепваме съгласните и прочие, а само колко са на брой - правилото "гласна = сричка" ни върши работа:

  • две - една сричка
  • едно - две срички
  • четири - три срички
  • и т.н.

И в крайна сметка генераторът

Искаме генераторът generate_haiku_lines(corpus) да работи по следния начин:

corpus = {'мъгла': ['пада', 'непротивоконституционствувателствувайте'], ..., 'пада': ['тук', 'някъде'], ...}
haiku_writer = generate_haiku_lines(corpus)
next(haiku_writer) # Мъгла пада тук -> 5 срички
next(haiku_writer) # Мъгла пада някъде -> 7 срички
# следващият пак би бил 5 срички

corpus-ът е речник, индикиращ кои думи се срещат често в последователност - "мъгла" и "пада", както и "мъгла" и "непротивоконституционствувателствувайте" често се използват в комбинация. Перифразирано - всяка дума, която е ключ на речника, показва какви са възможните думи, които да стоят след нея.
Искаме да използваме тази информация, за да генерираме последователно хайку редове от по 5, 7, 5 срички, използвайки тази дефиниция на логическите връзки между думите. Забележете, че първата буква на всеки хайку ред е Главна.

По-сериозен пример:

corpus = {'първата': ['ми'], 'ми': ['работа', 'двайсет'], 'работа': ['беше'], 'беше': ['да', 'страшен'], 'да': ['вдигна', 'бъда', 'не', 'не', 'легализирам', 'го', 'напишеш'], 'вдигна': ['бензина'], 'бензина': ['с'], 'с': ['лев', 'тия', 'тая'], 'т': ['за'], 'за': ['мене', 'мен,', 'гласа,', 'това', 'всичко', 'всичко'], 'мене': ['беше'], 'страшен': ['кеф'], 'от': ['тоя'], 'тоя': ['лев', 'чичко'], 'лев': ['седемдесет'], 'седемдесет': ['стотинки'], 'стотинки': ['са'], 'са': ['за'], 'мен,': ['а'], 'а': ['трийсете'], 'трийсете': ['ги'], 'ги': ['нося'], 'нося': ['лично'], 'лично': ['на', 'депутата', 'депутата'], 'на': ['асен', 'дългия,', 'дебелия', 'двамата'], 'знам,': ['няма'], 'няма': ['как'], 'как': ['да', 'успя'], 'бъда': ['кратъки'], 'кратъки': ['да'], 'не': ['спомена', 'те', 'наяде'], 'спомена': ['нещо'], 'нещо': ['мило'], 'мило': ['за'], 'гласа,': ['който'], 'който': ['лекува'], 'лекува': ['запек'], 'чува': ['се'], 'се': ['десислава'], 'десислава': ['атанасова'], 'атанасова': ['шефке,'], 'шефке,': ['мно'], 'мно': ['ме'], 'ме': ['кефиш'], 'кефиш': ['с'], 'тия': ['твоите'], 'твоите': ['цайси', 'хора'], 'заклевам': ['се,'], 'се,': ['при', 'ма', 'ето'], 'при': ['други'], 'други': ['обстоятелства'], 'обстоятелства': ['сеш'], 'сеш': ['се'], 'копейка,': ['к'], 'к': ['е'], 'е': ['твоята', 'вкусен', 'една', 'да', 'виновен', 'виновен'], 'твоята': ['прогноза'], 'хора': ['ще'], 'ще': ['си', 'си', 'ти'], 'си': ['праснат', 'траем,'], 'праснат': ['ли'], 'ли': ['четвърта', 'е', 'се', 'се,'], 'четвърта': ['доза'], 'догодина': ['обещавам'], 'обещавам': ['да'], 'те': ['тормозя'], 'дай': ['ми'], 'двайсет': ['гласа'], 'гласа': ['да'], 'легализирам': ['коза'], 'траем,': ['красива'], 'красива': ['резиденция,'], 'резиденция,': ['знаем,'], 'знаем,': ['обичам'], 'обичам': ['възрожденци'], 'възрожденци': ['под'], 'под': ['наем'], 'последно,': ['кажи'], 'кажи': ['честно,', 'честно'], 'честно,': ['мон'], 'мон': ['амур,'], 'амур,': ['толко'], 'толко': ['ли'], 'вкусен': ['този'], 'този': ['руски'], 'руски': ['шнур'], 'ей,': ['бройлер,'], 'бройлер,': ['ще'], 'ти': ['купя', 'помогна,'], 'купя': ['бойлер'], 'нека': ['ти', 'сам'], 'помогна,': ['стига'], 'стига': ['с'], 'тая': ['мизерия'], 'това': ['е', 'нека'], 'една': ['мистерия'], 'мистерия': ['как'], 'успя': ['да'], 'го': ['поемеш', 'тоя', 'вече'], 'поемеш': ['целия'], 'целия': ['едновременно'], 'едновременно': ['и'], 'и': ['на', 'на', 'нищо'], 'дългия,': ['и'], 'моята': ['лесна,'], 'лесна,': ['има'], 'има': ['аерогара'], 'трудното': ['е'], 'напишеш': ['коментара'], 'сам': ['изям'], 'изям': ['кафявата'], 'кафявата': ['попара'], 'попара': ['в'], 'в': ['държавата'], 'държавата': ['на'], 'двамата': ['шопара'], 'виж': ['кво,', 'кво,'], 'кво,': ['според', 'според'], 'според': ['мен', 'мен'], 'мен': ['за', 'за'], 'всичко': ['е', 'е'], 'виновен': ['лично', 'лично'], 'депутата': ['христо', 'христо'], 'честно': ['побърка'], 'побърка': ['ли'], 'батко': ['не'], 'наяде': ['се,'], 'ма': ['поне'], 'поне': ['наспа'], 'наспа': ['ли'], 'ето': ['продаде'], 'продаде': ['се'], 'мани': ['го'], 'ей': ['го'], 'вече': ['8'], '8': ['месеца'], 'месеца': ['и']}

haiku_writer = generate_haiku_lines(corpus)

for i in range(1, 10):
    print(next(haiku_writer))
    if i % 3 == 0:
        print('\n') # За визуално разграничаване

# Output:
Лев седемдесет
Попара в държавата
Кво според мен за

Виновен лично
Вкусен този руски шнур
Заклевам се при

Ей го поемеш
Да вдигна бензина с лев
Нека сам изям

Забележки:

  • Генераторът е безкраен!
  • Няма да тестваме с речници, които правят невъзможна генерацията на хайку (например {'биатлонистите': ['стачкуваха'], 'стачкуваха': ['твърде'], 'твърде': ['целеустремено', 'противоконституционно']} - в този речник няма никаква комбинация от думи, която да дава 5 или 7 срички).
  • Горното обаче не ви гарантира, че първоначалният random избор на дума ще ви отведе до валидна комбинация (например ако попаднете на думата "дюнера" в речника {'дюнера': ['е', 'тигана'], 'е': ['тука'], 'тигана': ['има'], 'има': ['праскови', 'е']} - греда... но всъщност има няколко валидни реда - "тигана има", "има праскови", "има е тука").
  • Не е задължително всяка от думите в списъците, които имаме за стойности, да присъства като ключ.
  • Не мислете за пунктоация.
  • Всички думи в инпут-а са lowercase.
  • Не питайте дали "Ю" и "Я" са гласни, моля (дюля)... :D

"Ю" и "Я" са гласни!

Любопитно:

  • В някои езици сричките могат да имат само съгласни звуци - сричкообразуващи съгласни звуци (сонорни съгласни). Пример - "Strč prst skrz krk" (чешки / словашки).
  • Речникът, описан по-горе, е силно опростена версия на нещо наречено "Верига на Марков" и е похват, често използван при machine learning.

Решения

Виктор
  • Коректно
  • 3 успешни тест(а)
  • 0 неуспешни тест(а)
Виктор
import random
def generate_haiku_lines(markov_chain):
line = 1
while True:
if line % 2:
yield generate_line(markov_chain, syllables=5)
else:
yield generate_line(markov_chain, syllables=7)
line = line + 1 if line != 3 else 1
def count_syllables(word):
return sum(' '.join(word).count(vowel) for vowel in 'аеиоуъюя')
def generate_line(markov_chain, syllables):
first = random.choice(list(markov_chain.keys()))
try:
result = ' '.join(add_words(markov_chain,
[first],
syllables)).capitalize()
except (IndexError, RecursionError):
return generate_line(markov_chain, syllables) # Retry
else:
return result
def add_words(markov_chain, words_in_line, syllables):
if count_syllables(words_in_line) == syllables:
return words_in_line
try:
possible_words = markov_chain[words_in_line[-1]][:]
except KeyError:
# No entry for the last word, backtrack one step (possible RecursionError)
return add_words(markov_chain, words_in_line[:-1], syllables)
random.shuffle(possible_words) # In place...
for next_word in possible_words:
words_in_new_line = words_in_line + [next_word]
current_syllables = count_syllables(words_in_new_line)
if current_syllables == syllables:
return words_in_new_line
elif current_syllables < syllables:
return add_words(markov_chain, words_in_new_line, syllables)
else:
# No valid combination, backtrack one step (possible RecursionError)
return add_words(markov_chain, words_in_line[:-1], syllables)
...
----------------------------------------------------------------------
Ran 3 tests in 0.462s

OK
Йордан Глигоров
  • Коректно
  • 3 успешни тест(а)
  • 0 неуспешни тест(а)
Йордан Глигоров
import random
consonants = ['а', 'ъ', 'о', 'у', 'е', 'и', 'я', 'ю']
def count_syllables(word: str):
return len(list(filter(lambda x: x in consonants, word)))
result = ""
limit = 5
def generate_haiku_lines(corpus: dict):
global limit
while(True):
word = random.choice(list(corpus.keys()))
dfs(word, corpus, [])
if result != "" and result is not None and count_syllables(result) == limit:
print(result)
limit = 7 if limit == 5 else 5
yield result
#if a haiku line is not found generate the next random word and do the same
def dfs(word: str, corpus: dict, sentence: list):
sentence.append(word)
curr_sentence = " ".join(sentence)
curr_syllables = count_syllables(curr_sentence)
if curr_syllables > limit:
return ""
if curr_syllables == limit:
global result
result = curr_sentence
return curr_sentence
if word in corpus:
for next_word in corpus[word]:
dfs(next_word, corpus, sentence)
sentence.pop()
...
----------------------------------------------------------------------
Ran 3 tests in 0.037s

OK
Александър Сариков
  • Коректно
  • 3 успешни тест(а)
  • 0 неуспешни тест(а)
Александър Сариков
import random
# приемам че 'Ю' и 'Я' са гласни според нашите правила
VOWELS = 'аъоуеиюя'
def generate_line_with_syllables(line, word_list, word_dict_ref, current_syllables, expected_syllables):
shuffled_word_list = random.sample(word_list, len(word_list))
for word in shuffled_word_list:
vowels_in_word = sum([word.count(vowel) for vowel in VOWELS])
new_syllabes_cnt = current_syllables + vowels_in_word
if new_syllabes_cnt == expected_syllables:
line.append(word)
return line
elif new_syllabes_cnt < expected_syllables:
if word in word_dict_ref.keys():
line.append(word)
if generate_line_with_syllables(line, word_dict_ref[word], word_dict_ref,
new_syllabes_cnt, expected_syllables):
return line
line.pop()
def generate_haiku_lines(corpus):
while True:
yield ' '.join(generate_line_with_syllables(
[], list(corpus.keys()), corpus, 0, 5) or []).capitalize()
yield ' '.join(generate_line_with_syllables(
[], list(corpus.keys()), corpus, 0, 7) or []).capitalize()
yield ' '.join(generate_line_with_syllables(
[], list(corpus.keys()), corpus, 0, 5) or []).capitalize()
...
----------------------------------------------------------------------
Ran 3 tests in 0.035s

OK
Радостин Маринов
  • Коректно
  • 3 успешни тест(а)
  • 0 неуспешни тест(а)
Радостин Маринов
import random
class HaikuWriter:
haiku_syllables_per_line = (5, 7, 5)
def __init__(self, corpus):
self._corpus = corpus
self._current = 0
def __iter__(self):
self._current = 0
return self
def __next__(self):
needed_syllables = self.haiku_syllables_per_line[self._current]
self._current = (self._current + 1) % 3
words = list(self._corpus.keys())
random.shuffle(words)
for word in words:
result = self._search_children(needed_syllables, word, word)
if result is not None:
return result.capitalize()
return None
def _search_children(self, needed_syllables, word, current_line):
if self._count_of_syllables(current_line) == needed_syllables:
return current_line
if self._count_of_syllables(current_line) > needed_syllables:
return None
if word not in self._corpus:
return None
children = list(self._corpus[word])
random.shuffle(children)
for child in children:
result = self._search_children(needed_syllables, child, f"{current_line} {child}")
if result is not None:
return result
return None
@staticmethod
def _count_of_syllables(word):
syllables = ['а', 'ъ', 'о', 'у', 'е', 'и', 'ю', 'я']
return sum(1 for char in word if char in syllables)
def generate_haiku_lines(corpus):
return HaikuWriter(corpus)
...
----------------------------------------------------------------------
Ran 3 tests in 0.035s

OK
Стилиян Иванов
  • Некоректно
  • 2 успешни тест(а)
  • 1 неуспешни тест(а)
Стилиян Иванов
import random
def number_of_syllables(word):
return sum([1 for letter in word if letter in ['а', 'ъ', 'о', 'у', 'е', 'и', 'ю', 'я']])
def generate_haiku_lines(corpus):
lines_count = 0
while True:
syllables_count = 5 if lines_count % 3 in [0,2] else 7
starting_word = random.choice(list(corpus.keys()))
option_available = False
if number_of_syllables(starting_word) == syllables_count:
option_available = True
lines_count += 1
yield starting_word.capitalize()
continue
if not option_available:
for result in [starting_word + ' ' + second_word for second_word in corpus[starting_word]]:
if number_of_syllables(result) == syllables_count:
option_available = True
lines_count += 1
yield result.capitalize()
break
if not option_available:
for result in [starting_word + ' ' + second_word + ' ' + third_word for second_word in corpus[starting_word] if second_word in corpus for third_word in corpus[second_word]]:
if number_of_syllables(result) == syllables_count:
option_available = True
lines_count += 1
yield result.capitalize()
break
.E.
======================================================================
ERROR: test_minimal (test.TestHaikuGenerator)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/storage/deedee/data/rails/pyfmi-2022/releases/20221115154139/lib/language/python/runner.py", line 63, in thread
    raise TimeoutError
TimeoutError

----------------------------------------------------------------------
Ran 3 tests in 2.034s

FAILED (errors=1)
Никола Михайлов
  • Коректно
  • 3 успешни тест(а)
  • 0 неуспешни тест(а)
Никола Михайлов
import itertools
import re
from collections import deque
from random import randint
VOWELS_REGEX = '[аеоуъиюя]'
ROWS = {5: [], 7: []}
def count_vowels(word):
return len(re.findall(VOWELS_REGEX, word))
def find_row(syllables, corpus, current_word, current_row):
if syllables == 5:
ROWS[5].append(' '.join(current_row).capitalize())
if syllables == 7:
ROWS[7].append(' '.join(current_row).capitalize())
return
if syllables > 7:
return
if current_word in corpus:
for word in corpus[current_word]:
current_row.append(word)
find_row(syllables + count_vowels(word), corpus, word, current_row)
current_row.pop()
def generate_haiku(corpus):
current_row = deque()
for word in corpus.keys():
current_row.append(word)
find_row(count_vowels(word), corpus, word, current_row)
current_row.clear()
def generate_haiku_lines(corpus):
generate_haiku(corpus)
sequence = itertools.cycle([5, 7, 5])
for wanted_syllable in sequence:
yield ROWS[wanted_syllable][randint(0, len(ROWS[wanted_syllable]) - 1)]
...
----------------------------------------------------------------------
Ran 3 tests in 0.044s

OK