Jerzy Ficowski (ten akowiec od "Dziś prawdziwych Cyganów już nie ma") napisał kiedyś taki wiersz:
Pewien żarłok nienażarty
raz wygłodniał nie na żarty.
I wywiesił szyld na płocie,
że ochotę ma na płocie.
Tutaj na brak ryb narzeka,
bo daleko rybna rzeka.
Więc się zgłosił pewien żebrak
i rzekł żarłokowi, że brak:
płoci, karpi oraz śledzi,
ale rzeki pilnie śledzi,
i gdy tylko będzie w stanie,
to o świcie z łóżka wstanie,
po czym ruszy na Pomorze
i w zdobyciu ryb pomoże.
Odtąd żarłok nasz jedynie,
zamiast smacznych ryb je dynie.
Jak widać, jeden z manewrów, jaki stosuje, polega na tym, żeby znaleźć takie trójki polskich słów a, b, c, żeby a + b = c. Wziąłem więc przy sobocie moją fajną listę polskich słów i napisałem program, który szuka takich trójek (znaczy, ejajowi kazałem napisać, sam bym nie wymyślił takiego wydajnego algorytmu). Program na początku znajdował mnóstwo trójek trywialnych ("nie + ładnie = nieładnie") albo z użyciem słów tak egzotycznych, że aż dziwię się, że mam takie w mojej liście słów ("umie + rając = umierając"). Więc dodałem do programu jeszcze czarną listę słów, które ma unikać, i oto przedstawiam wam efekt.
Oto program, który szuka takich trójek. Patrzcie, całkiem zręczny kod:
#!/usr/bin/env python3
from collections import Counter
words = set(open('slowa.txt').read().splitlines())
blacklist = set(open('blacklist.txt').read().splitlines())
n = len(words)
found = 0
stats = Counter()
results = []
for idx, c in enumerate(words, 1):
for i in range(1, len(c)):
a, b = c[:i], c[i:]
if a in words and b in words and a not in blacklist and b not in blacklist:
results.append((a, b, c))
found += 1
stats[a] += 1
stats[b] += 1
stats[c] += 1
if idx % 10000 == 0:
print(f"\r{idx}/{n} znaleziono: {found}", end='', flush=True)
with open('wyniki_posortowane_po_a.txt', 'w') as out:
for a, b, c in sorted(results, key=lambda abc: abc[0]):
out.write(f"{a} + {b} = {c}\n")
with open('wyniki_posortowane_po_b.txt', 'w') as out:
for a, b, c in sorted(results, key=lambda abc: abc[1]):
out.write(f"{a} + {b} = {c}\n")
print(f"\r{n}/{n} znaleziono: {found}")
print("Zapisano do wyniki_posortowane_po_a.txt i wyniki_posortowane_po_b.txt")
with open('statystyki.txt', 'w') as f:
for word, count in stats.most_common():
f.write(f"{word}\t{count}\n")
print(f"Statystyki ({len(stats)} słów) zapisano do statystyki.txt")
Oto wynik jego działania: lista posortowana po słowie a i lista posortowana po słowie b. Jest tam sporo niezbyt ciekawych trójek ("zielono + górski = zielonogórski"), ale są też całkiem ciekawe, na przykład:
dar + mowy = darmowy
de + batom = debatom
akt + orki = aktorki
błąd + zimy = błądzimy
bok + ser = bokser
brud + zimy = brudzimy
cent + rów = centrów
cent + rum = centrum
cię + żarówka = ciężarówka
cię + żar = ciężar
gry + źli = gryźli
kas + truje = kastruje
doda + tnie = dodatnie
wita + mina = witamina
zdrad + zając = zdradzając
trzy + mający = trzymający
Niektóre to klasyka ("wita + mina = witamina"), są takie, które sam kiedyś już odkryłem (o kimś skąpym a wygadanym można powiedzieć, że kiedy Bóg obdarzał ludzi wysyłanych na Ziemię do wcielenia się w noworodki talentami, to on stojąc w kolejce zobaczył okienko z tabliczką "dar mowy" i myślał, że tam coś za darmo będą dawać), a są i takie, które widzę pierwszy raz ("brud + zimy = brudzimy"). Fajnie. Można używać tego do układania różnych gier słów, na przykład że bywają takie debaty, kiedy po jednej stronie jest de, a po drugiej są baty.
Ficowski stosował też czwórki słów takie, żeby a + b + c = d, na przykład "nie + na + żarty = nienażarty". Akurat tej trójki ja nie znajdę, bo ja blacklistuję słowo "nie", ale ciekawie byłoby poszukać innych. Więc zmieniłem lekko ten program, żeby szukał takich czwórek - i oto efekt.
Oto program, który szuka takich czwórek. Oto wynik jego działania: lista posortowana po słowie a, lista posortowana po słowie b, lista posortowana po słowie c. Dosyć słabe poznajdował te czwórki, ale niektóre są ok, na przykład:
dwu + nas + tu = dwunastu
tak + sów + karze = taksówkarze
Spróbowałem też znaleźć takie czwórki, żeby a + b = c + d. Tu problem algorytmicznie okazał się trudniejszy (jak nie kombinować to O(n²), a u mnie n=250000), nawet ejaje nie dawały mu rady, dopiero Opus 4.6 napisał ten nietrywialny a ciekawy kod:
#!/usr/bin/env python3
from collections import defaultdict
words = set(open('slowa.txt').read().splitlines()) - set(open('blacklist.txt').read().splitlines())
print("Faza 1: buduję indeks środków...", flush=True)
# Dla każdego aktywnego środka m, zapamiętaj pary (b, d) takie że b = m+d, b in words, d in words
# middle_to_bd[m] = [(b, d), ...]
middle_to_bd = defaultdict(list)
for b in words:
for i in range(1, len(b)):
d = b[i:]
if d in words:
middle_to_bd[b[:i]].append((b, d))
print(f" znaleziono {len(middle_to_bd)} aktywnych środków", flush=True)
print("Faza 2: szukam pełnych rekonstrukcji...", flush=True)
# Dla każdego słowa c, jeśli c[:i] (=a) in words i c[i:] (=m) ma wpisy w middle_to_bd,
# to a+b = c+d gdzie b = m+d
n = len(words)
count = 0
for idx, c in enumerate(words, 1):
if idx % 10000 == 0:
print(f" postęp: {idx}/{n}", file=__import__('sys').stderr, flush=True)
for i in range(1, len(c)):
a = c[:i]
m = c[i:]
if a in words and m in middle_to_bd:
for b, d in middle_to_bd[m]:
print(f"{a}+{b} = {c}+{d} ({a}{b})")
count += 1
print(f"\nZnaleziono {count} rekonstrukcji.", file=__import__('sys').stderr)
Pełnego wyniku jego działania nie zamieszczam, bo znajduje takich czwórek całe gigabajty, do tego w większości nieciekawych. Na przykład:
wystawa+niespowodowana = wystawanie+spowodowana (wystawaniespowodowana)
szwagierko+wisiałeś = szwagierkowi+siałeś (szwagierkowisiałeś)
oddycha+niegrzejącą = oddychanie+grzejącą (oddychaniegrzejącą)
wyciąga+niekurcząca = wyciąganie+kurcząca (wyciąganiekurcząca)
rzecze+nieugryzieniach = rzeczenie+ugryzieniach (rzeczenieugryzieniach)
napisania+chwaliłybyście = napisaniach+waliłybyście (napisaniachwaliłybyście)
mali+narodziliśmy = malina+rodziliśmy (malinarodziliśmy)
wago+niekłuciu = wagonie+kłuciu (wagoniekłuciu)
A oto lista losowych tysiąca wyników tego programu.
Tak.
Przy takim automatycznym generowaniu gier słownych problemem jest często to, że program znajdzie dużo kombinacji, które formalnie spełniają wszelkie warunki, ale większość z nich jest nieciekawa. Spróbowałem więc, czy dałoby się użyć ejaja do odsiania ziarna od plew.
Użyłem takiego prompta:
Istnieją takie trójki słów a, b, c, że a+b=c. Na przykład "kot + lina = kotlina". Niektóre z nich są ciekawe, zaskakujące, zabawne - na przykład wspomniane "kot + lina = kotlina" czy "wita + mina = witamina". Ale są też nieciekawe, trywialne - na przykład "nie + ładny = nieładny" czy "zielono + górski = zielonogórski". Nieciekawe są te, gdzie mamy po prostu normalne słowotwórstwo (jak "nie + ładny = nieładny" czy "co + raz = coraz") albo kiedy użyte słowa są tak egzotyczne, że aż trzeba się pozastanawiać, czy to naprawdę jest legalne polskie słowo (jak "de + tal = detal"). Dam ci teraz listę takich trójek. Dla każdej z nich daj mi najpierw ocenę opisową, a potem ocenę fajności w skali 0-9, gdzie 0 to trójka bardzo nieciekawa, a 9 to trójka bardzo ciekawa.
"""
kraj + obraz = krajobraz
włocha + tą = włochatą
gry + źli = gryźli
kas + truje = kastruje
prawdo + podobieństwo = prawdopodobieństwo
doda + tnie = dodatnie
tym + czasowy = tymczasowy
ok + rycia = okrycia
prawo + sławny = prawosławny
korzy + stałabym = korzystałabym
tam + tą = tamtą
tego + rocznego = tegorocznego
biało + ruski = białoruski
dar + mowy = darmowy
de + batom = debatom
akt + orki = aktorki
błąd + zimy = błądzimy
brud + zimy = brudzimy
wita + mina = witamina
"""
Dałem ten prompt Gemini 3 fast, a on wystawił takie oceny:
| Trójka (a + b = c) | Ocena opisowa | Fajność (0-9) |
|---|---|---|
| kraj + obraz = krajobraz | Klasyczna zbitka słowotwórcza. "Obraz kraju" to niemal dosłownie krajobraz. | 1 |
| włocha + tą = włochatą | Zabawne przesunięcie akcentu i formy gramatycznej. | 7 |
| gry + źli = gryźli | Bardzo mocna pozycja. Połączenie gry i emocji w drapieżnego ssaka. | 9 |
| kas + truje = kastruje | "Kas" jest dość egzotyczne (jako skrót lub forma dopełniacza), co obniża ocenę. | 3 |
| prawdo + podobieństwo = prawdopodobieństwo | Czysta, podręcznikowa złożeńcówka. Nuda lingwistyczna. | 0 |
| doda + tnie = dodatnie | Ciekawe połączenie piosenkarki i czynności cięcia dające termin matematyczny. | 8 |
| tym + czasowy = tymczasowy | Zbyt blisko naturalnego pochodzenia słowa (tym + czas). | 1 |
| ok + rycia = okrycia | "Ok" to zapożyczenie, ale transformacja w "okrycie" jest nieoczywista. | 5 |
| prawo + sławny = prawosławny | Dosłowne tłumaczenie z greckiego. Bardzo przewidywalne. | 0 |
| korzy + stałabym = korzystałabym | "Korzy" nie jest samodzielnym słowem, to tylko podział morfologiczny. | 2 |
| tam + tą = tamtą | Po prostu odmieniony zaimek. Bardzo trywialne. | 0 |
| tego + rocznego = tegorocznego | Standardowe złożenie przymiotnikowe. Brak punktów za styl. | 0 |
| biało + ruski = białoruski | Przymiotnik od nazwy państwa – czysta geografia. | 0 |
| dar + mowy = darmowy | Genialne! Zupełna zmiana kontekstu z metafizycznego na materialny. | 9 |
| de + batom = debatom | "De" to przedrostek, a "batom" brzmi egzotycznie. Średnio czytelne. | 2 |
| akt + orki = aktorki | Bardzo dobre. Dokument prawny + orka rolnicza = zawód artystyczny. | 8 |
| błąd + zimy = błądzimy | Poetyckie i zaskakujące przejście rzeczowników w czasownik. | 8 |
| brud + zimy = brudzimy | Podobny, świetny mechanizm co w przypadku "błądzimy". | 8 |
| wita + mina = witamina | Klasyk gatunku. Personifikacja miny, która wita. | 7 |
Moim zdaniem bardzo dobrze ocenił, człowiek nie zrobiłby tego lepiej.
Nie.
Próbowałem prosić różne ejaje (używałem tylko darmowych), żeby napisały wiersz używając wybranych, ciekawych trójek a, b, c (wybranych i podanych przeze mnie). Wychodziło słabiutko. Na przykład Gemini 3 fast - chyba najlepszy z tych, co próbowałem - napisał tak:
Pechowa randka
Zabrał ją w miasto, bo był dość darmowy,
Miał gest szeroki, choć brakło mu dar mowy.
Weszli do baru, gdzie w mroku, przy centrum,
Kelner im podał stek krwawy – sam cent rum.
On chciał zaimponować, jak wielki bokser,
Lecz tylko sapał i pocił się, bok ser.
Potem milczeli, oddani debatom,
Ulegając błędom, nie zaś de batom.
Gdy wyszli na deszcz, rzekł: „Znów brudzimy,
W ten czas, gdy w mroku tak bez celu błąd zimy”.
Ona zaś, patrząc jak on wciąż kastruje
Wątek rozmowy, rzekła: „Nic, nie kas truje”.
Dobra nasza, ludzie!