Anagrammid ja Python III

Üks võimalik rekursiivne lahendus:

Otsime fraasi f anagramme nii, et lahutame fraasist sõnu maha ja otsime siis (fraas-sõna) anagramme.

Defineerime funktsiooni X mis võtab sisse fraasi f ja ettearvutatud sõnastiku S

- vaatame, kas f on ise S sees sisalduv sõna

- kui on, siis on üks anagramm leitud. jätame meelde!

- teeme tsükli üle kõikide sõnastiku S sõnade s

- leiame jääk-fraasi f-s

- arvutame välja jäägile vastava uue sõnalisti: selles on sõnad, mis võivad olla (f-s) anagrammiks või anagrammi osadeks

- kutsume X välja, fraasiks on siis (f-s) ja sõnastikuks uus sõnastik

Rekursiooni lõpp ehk baas on see, kui sisendfraas jääb nii lühikeseks, et temast enam anagrammi leida ei saa.

Kui sisend on "paks kurinurm", siis käik on umbes:

X (lõbuskurinurm) = "lõbus" + X (kurinurm) = "lõbus" + "kuri" + X(nurm), ja kuna viimane on sõna, siis anagramm ongi "lõbus kuri nurm".

Veel:

- peame alati teadma rekursiooni hetke taset. Me ei taha liiga sügavale minna / liiga mitmeosalisi anagramme leida) ja võimaliku anagrammi seni leitud osiseid.

- peame edasi andma juba leitud anagrammi osad.

Programm ise

# Anagrammide leidmine
#
# Leiab mitmeosalisi anagramme
# Rekursiivne
#
# sõnade allikaks https://www.eki.ee/tarkvara/wordlist/lemmad2013.txt
import re
anagrammi_max_pikkus = 3
sona_min_pikkus = 4
# teeme sõnalistist dictionary
# "räsi" on sõna sorditud kuju: kala => aakl
# dictionary 'key' on räsi ja 'value' on kõikide sama räsiga sõnade list[]
sonalist = dict()
ff = open("lemmad2013.txt", "r", encoding="utf8")
for x in ff:
    sona = x.lower().strip().replace(" ", "")
    if len(sona)>=sona_min_pikkus:
        sona_rasi = ''.join(sorted(sona))
        if sona_rasi in sonalist:
            sonalist[sona_rasi].append(sona)
        else:
            sonalist[sona_rasi] = [sona]
ff.close()
print("Saime sõnastikust", str(len(sonalist)), "erineva räsiga sõna.")
# defineerime funktsiooni, mis leiab, kas kõik S2 tähed on S1 sees olemas
# s1_contains_s2("kalad", "laad") => True
#
def s1_contains_s2(s1, s2):
    l1 = len(s1)
    l2 = len(s2)
    if(l2>l1):
        return False
    for x in s2:
        s1 = s1.replace(x, '', 1)
    return (len(s1) == l1 - l2)
# defineerime funktsiooni, mis lahutab kõik S2 tähed S1-st
# s1_minus_s2("kalad", "kala") => "d"
#
def s1_minus_s2(s1, s2):
    for x in s2:
        s1 = s1.replace(x, '', 1)
    return(s1)
# rekursiivne alam-sõne leidja
#
# sisend:
# - ff: sisendfraas, mille anagrammi leitakse. string, tühikuteta
# - sonad: list räsidest, mis seal sees võivad veel olla. puhh on eelnevalt välja arvutatud
# - seni_leitud: list seni leitud sõnaosadest
# - level: kui sügaval me oleme ehk mitmendat anagrammi osa-sõna me leiame
#   level on piiramiseks, et ei leitaks igavaid lühisõnadest koosnevaid anagramme
def lammuta(ff, sonad, seni_leitud, level):
    ff = ''.join(sorted(ff))      # viime oma fraasi räsi kujule
    # kui leiame fraasile listist täpse vaste, siis olemegi leidnud anagrammi
    # lisame selle anagrammid hulka
    # duplikaatide eemaldamiseks teeme veidi keemiat:
    # teisendame leitud anagrammi listiks, sorteerime, teisendame tagasi stringiks
    if ff in sonad:
        anagrammid.add(" ".join(sorted((seni_leitud + ' ' + ff).split())))
    # rohkematest sõnadest koosnevaid anagramme me leida ei taha, naaseme
    if level >= anagrammi_max_pikkus:
        return
    # järelejäänud fraas on lühike, sellest endast ei saa enam mitmeosalist annagrammi olla, naaseme
    if len(ff)< sona_min_pikkus*2:
        return
    # rekursioon
    # - võtame kõik fraasis sisalduda võivad räsid
    # - lahutame nad ükshaaval sisend-fraasist
    # - rakendame lammuta() funktsiooni jäägi (fraas-räsi) peal
    for x in sonad:
        if (len(ff)-len(x)) >= sona_min_pikkus:
            jaak = s1_minus_s2(ff, x)
            # nüüd tuleb teha uus list räsidest, mis võivad olla jaak sees
            sonad_2 = [ k for k in sonad if s1_contains_s2(jaak, k) ]
            if (len(sonad_2) > 0):
                lammuta(jaak, sonad_2, seni_leitud + ' ' + x, level+1)
                
# Põhitsükkel
while(True):
    # muutujad:
    # - fraas - fraas, mille anagramme me otsime
    # - fraas2 - eelmise tühikute ja suurtähtedeta vorm
    # - sonalist - dictionary kõikidest räsidest ja neile vastavate sõnade listidest
    # - voimalikud - list räsidest, mis võivad fraas2 sees olla
    # - anagrammid - algselt tühi list[] leitavatest anagrammidest
    fraas = input("Kirjuta midagi: ")
    fraas2 = fraas.lower().strip().replace(' ','')
    voimalikud = [k for k in sonalist.keys() if s1_contains_s2(fraas2, k)]
    anagrammid = set()
    print("Fraas:", fraas)
    print("Fraasi normkuju:", fraas2)
    print("Võimalikud anagrammi osad:", len(voimalikud))
    print()
    
    # Lükkame rekursiooni käima!
    lammuta(fraas2, voimalikud, "", 1)
    print ("Leidsime", len(anagrammid), "erinevat anagrammi.")
    # anagrammid[] on nüüd hulk stringidest
    # sorteerime need kõigepealt pikkuse järgi, lühemad anagrammid ette - kõibepealt kaheosalised, siis kolmeosalised jne
    for x in sorted(anagrammid, key=len):
        # anagrammid on hulk (set), selle element on x on list
        # tuleb teda veidi kenamaks formaatida ja räsidele sõnastikust vasted otsida
        ss = ""
        for y in x.split():
            ss = ss + re.sub("[,\']", "", str(sonalist[y])) + " "
        print(ss)
        
    print()