Kirjautuminen

Haku

Tehtävät

Koodit: Python: Muotohaku sanalistasta

Kirjoittaja: Chiman

Kirjoitettu: 12.12.2012 – 12.12.2012

Tagit: algoritmit, teksti, koodi näytille, vinkki

Ohjelma hakee sanalistasta annettuun konsonantti-vokaali-sääntöön sopivat sanat. Käyttökohteena on esimerkiksi krypto-ristikoiden sanojen etsiminen, jolloin sanalistana voidaan käyttää esim. putkapostin tehtävänannosta löytyvää suomen kielen sanastoa.

Ohjelma käyttää työhakemiston sanalista.txt-tiedostoa lähteenä, jos sellainen on.

Ohjelma on tehty toimimaan 2- ja 3-sarjan Python-versioilla.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# sanaosumat.py

import sys

def _ehto_samat_merkit(i, j):
    return lambda sana: sana[i] == sana[j]

def _ehto_eri_merkit(i, j):
    return lambda sana: sana[i] != sana[j]

def _ehto_merkki_joukossa(i, joukko):
    return lambda sana: sana[i] in joukko

def _rakenna_sanaehdot(merkit):
    if sys.version < '3':
        vokaalit = unicode('aeiouyåäö', 'utf-8')
        konsot = unicode('bcdfghjklmnpqrstvwxz', 'utf-8')
    else:
        vokaalit = 'aeiouyåäö'
        konsot = 'bcdfghjklmnpqrstvwxz'

    # Tehdään sanapituuden täsmäystarkistus aluksi, jotta indeksitarkistukset
    # eivät voi aiheuttaa virheitä.
    ehdot = [lambda sana: len(sana) == len(merkit)]

    for i, m in enumerate(merkit):
        eka_sijainti = merkit.index(m)
        if len(m) > 1 and i != eka_sijainti:
            # Aiempi vastaava on jo ollut, tarkistukseksi vain identtisyys siihen.
            ehdot.append(_ehto_samat_merkit(i, eka_sijainti))
            continue

        if len(m) > 1:
            # Ensimmäinen tällainen yhdistelmä. Tee erisuuruustarkistus edeltäviin
            # saman merkkiryhmän eri numeroihin, esim. v1 != v2.
            merkitty = set()
            for j, n in enumerate(merkit[:i]):
                if len(n) > 1 and n[0] == m[0] and n != m and n not in merkitty:
                    ehdot.append(_ehto_eri_merkit(i, j))
                    merkitty.add(n)

        if m[0] == 'v':
            ehdot.append(_ehto_merkki_joukossa(i, vokaalit))
        elif m[0] == 'k':
            ehdot.append(_ehto_merkki_joukossa(i, konsot))

    return ehdot

def osuvat(lista, s):
    """iteroi listan sanat, jotka täyttävät argumentin s muodon

    Muoto koostuu kirjaimista kvx ja numeroista 0-9.
    k = konsonantti, v = vokaali, x = vokaali tai konsonantti
    Pelkkä k, v tai x ei sido kirjainta sanan muihin kirjaimiin.

    Kaikki muodon keskenään identtiset kirjain-numero-yhdistelmät edustavat
    keskenään samaa kirjainta. Vastaavasti eri kirjain-numero-yhdistelmät
    edustavat keskenään eri kirjaimia. Muodon välilyönneillä ei ole merkitystä.

    Esimerkit:
    'v1 x v1' sopii sanoihin 'aha' ja 'aaa' muttei sanaan 'asu'
    'v1 x v2' sopii sanoihin 'ajo' ja 'aie' muttei sanaan 'aha'
    'k1vk2v' sopii sanoihin 'kala' ja 'siru' muttei sanaan 'sisu'
    'x1vvx2' sopii sanoihin 'taas' ja 'säie' muttei sanaan 'noin'

    """
    import re

    # sallitaan vain oikeista yhdistelmistä koostuva muoto
    if not re.match(r'^( *[kvx][0-9]*)+ *$', s):
        raise ValueError('Virheellinen muoto')

    merkit = re.findall(r'([kvx][0-9]*)', s)
    ehdot = _rakenna_sanaehdot(merkit)
    for sana in lista:
        if all(ehto(sana) for ehto in ehdot):
            yield sana

def _hae_sanalista():
    try:
        if sys.version < '3':
            sanat = open('sanalista.txt').readlines()
            sanat = map(lambda x: x.strip().decode('latin1'), sanat)
        else:
            sanat = open('sanalista.txt', encoding='latin1').readlines()
            sanat = map(lambda x: x.strip(), sanat)
        # jos sanalista on utf-8 -muodossa, muuta se ylle
    except:
        if sys.version < '3':
            sanat = map(unicode, ['osu', 'aha', 'kari', 'kivi'])
        else:
            sanat = ['osu', 'aha', 'kari', 'kivi']
    return sanat

if __name__ == '__main__':
    if len(sys.argv) > 1:
        muoto = ''.join(sys.argv[1:])
        sanat = _hae_sanalista()
        tulos = ', '.join(osuvat(sanat, muoto))
        if tulos:
            if sys.version < '3':
                print(tulos.encode('utf-8'))
            else:
                print(tulos)
    else:
        muoto_ohje = osuvat.__doc__.split('\n', 1)[1]
        print('Käyttö: python %s MUOTO\n%s' % (sys.argv[0], muoto_ohje))

Käyttö komentoriviltä:
python sanaosumat.py k1 v k1 v k2
tulostaa:
bebop, kukin, sisal, sisar, sisin, tatar, tutor
kun käytössä on Ohjelmointiputkan versio kotus-sanalistasta.

Lisäys: Koodi muokattu pyynnöstä Python 3 -yhteensopivaksi.

Kirjoita kommentti

Muista lukea kirjoitusohjeet.
Tietoa sivustosta