Kirjautuminen

Haku

Tehtävät

Keskustelu: Yleinen keskustelu: Mikä on pienin yhteismäärä, jos prosenttiosuudet tiedetään?

Sivun loppuun

Chiman [03.08.2015 13:29:33]

#

Kaipaako joku kevyttä algoritmipähkinää kesäpäiväänsä?

Monivalintaisten kyselyiden tuloksia julkaistaessa kerrotaan eri vastausvaihtoehtojen keräämät (mahdollisesti pyöristetyt) prosenttiosuudet, mutta vastausten tarkka kokonaismäärä saatetaan jättää kertomatta. Siihen liittyen olen joskus miettinyt eleganttia algoritmia pienimmän mahdollisen kokonaismäärän laskemiseen.

Esimerkki: A-, B- ja C-vaihtoehtojen vastausosuudet ovat 23, 37 ja 40 prosenttia. Miten monta vastausta vähintään annettiin?

Oikea vastaus on 30, koska 7/30 pyöristyy 23 prosenttiin, 11/30 37:ään ja 12/30 tuottaa tasan 40 prosenttia. Lisäksi 7 + 11 + 12 = 30.

Kun osuudet on annettu kokonaisina prosentteina, pienin yhteismäärä löytyy viimeistään sadasta, koska tällöin kokonaislukujen osamääristä löytyvät kaikki mahdolliset prosenttiosuudet.

Tämä ei sinänsä ole mikään vaikea ongelma, koska raaka voima tehoaa käytännössä aina. Vaikka prosenttiosuudet annettaisiin kahden desimaalin tarkkuudella, pienin osuva kokonaismäärä löytyisi aina viimeistään kymmenen tuhannen kohdalta.

Jaska [03.08.2015 18:18:15]

#

Tekisin raa'alla voimalla. Jos pienin kokonaismäärä on n, niin esimerkiksi tapauksessa pitäisi löytää kolme kokonaislukua väleiltä [0.225n,0.235n[, [0.365n,0.375n[, [0.395n,0.405n[. Aika nopeasti nuo tarkastaa.

Grez [03.08.2015 18:29:05]

#

Chiman kirjoitti:

Kun osuudet on annettu kokonaisina prosentteina, pienin yhteismäärä löytyy viimeistään sadasta, koska tällöin kokonaislukujen osamääristä löytyvät kaikki mahdolliset prosenttiosuudet.

Veikkaisin että kolmen tapauksessa viimeistään 101:stä, koska pyöristetyt prosenttimäärät voi olla yli 100, mutta kolmen määrän tapauksessa kai kuitenkin väliltä 99 - 101.

Chiman [04.08.2015 16:24:09]

#

Jaska kirjoitti:

Tekisin raa'alla voimalla. Jos pienin kokonaismäärä on n, niin esimerkiksi tapauksessa pitäisi löytää kolme kokonaislukua väleiltä [0.225n,0.235n[, [0.365n,0.375n[, [0.395n,0.405n[. Aika nopeasti nuo tarkastaa.

Tämä vaikutti yksinkertaisimmalta ja riittävän tehokkaalta tavalta. Miljoonaan asti laskenta ehtii noin kahdessa sekunnissa.

$ ./prosenttiosuudet.py 28.713 4.7569
7568 on pienin mahdollinen yhteismäärä, jolle annetut prosenttiosuudet täsmäävät
2173 / 7568 = 28.7130 %
360 / 7568 = 4.7569 %

Koodi ei ole niin erikoinen eikä esimerkillinen, että laittaisin sen näkyviin.

groovyb [04.08.2015 17:09:53]

#

Eikös tuossa pidä huomioida vielä tuo loppu ~67%? Lähinnä tarkistussumman omaisesti. *edit* se tietenkin voi koodissa olla huomioitava,ei vain näy outputissa.

Chiman [04.08.2015 17:20:57]

#

groovyb kirjoitti:

Eikös tuossa pidä huomioida vielä tuo loppu ~67%? Lähinnä tarkistussumman omaisesti.

Harkitsin sitä, mutta päädyin vain kohdistamaan eri prosenttilukemat ensimmäiseen sopivaan yhteismäärään. Tuo tarkistus voisi olla erillisenä komentorivioptiona.

Chiman [06.08.2015 13:28:01]

#

Lisäsin ominaisuuksia. Tässä tulee pari käyttöesimerkkiä ja koodi. Koodivinkiksi tämä ei taida soveltua hyvin rajallisen käyttötarpeensa takia.

$ ./prosenttiosuudet.py 27 67 5.232
669 on mahdollinen kokonaismäärä, jolle annetut prosenttiosuudet täsmäävät
178..183 / 669 = 27 %
445..451 / 669 = 67 %
35 / 669 = 5.232 %

Optiolla -k vaaditaan, että osuuksista kertyy koko 100 %, jolloin vaihtelurajojen sisältä löytyy vain yksi ratkaisu:

$ ./prosenttiosuudet.py -k 27 67 5.232
669 on mahdollinen kokonaismäärä, jolle annetut prosenttiosuudet täsmäävät
183 / 669 = 27 %
451 / 669 = 67 %
35 / 669 = 5.232 %

Käytännön esimerkki: "45 prosenttia kansanedustajista kannattaa...", eli miten moni?

$ ./prosenttiosuudet.py -t 200 45
200 on mahdollinen kokonaismäärä, jolle annetut prosenttiosuudet täsmäävät
89..90 / 200 = 45 %

Siis 89 tai 90 kansanedustajaa. (-t = tarkka määrä, etsitään ratkaisut vain kokonaismäärälle 200)

#!/usr/bin/env python3

from sys import argv, exit
from math import ceil
from optparse import OptionParser  # Python 3.1 ja uudemmat
import itertools

class Prosenttiosuus:
    def __init__(self, s, sep='.'):
        '''s: liukuluku merkkijonona väliltä [0.0, 100.0]'''
        osat = s.split(sep)
        if len(osat) > 2:
            raise ValueError('Liikaa desimaalierottimia')
        if not (''.join(osat)).isnumeric():
            raise ValueError('Vain numerot sallittu desimaalierottimen lisäksi')

        self.osuus = float(s) / 100
        if not (0.0 <= self.osuus <= 1.0):
            raise ValueError('Yksittäisen osuuden pitää olla nollan ja sadan välissä')
        self.desimaaleja = len(osat[1]) if len(osat) == 2 else 0

    def pyoristysrajat(self):
        dx = 0.5 / (100 * 10 ** self.desimaaleja)
        return (self.osuus - dx, self.osuus + dx)

    def __str__(self):
        return '{0:.{1}f} %'.format(100 * self.osuus, self.desimaaleja)


def sovita_osumat_kokonaismaaraan(kokmaara, osumat):
    minimi, maksimi = [sum(x) for x in zip(*osumat)]
    if not (minimi <= kokmaara <= maksimi):
        return False

    # ratkaisu löytyi, tutkitaan kavennetaanko osumarajoja
    maksimista_vajaa = maksimi - kokmaara
    minimista_yli = kokmaara - minimi
    for vali in osumat:
        alaraja, ylaraja = vali
        # yhden luvun alaraja voi olla ylärajan alle korkeintaan sen verran kuin mitä
        # tarkka kokonaismäärä on kaikkien osumarajojen yhteenlaskettua maksimia pienempi
        vali[0] = max(ylaraja - maksimista_vajaa, alaraja)
        vali[1] = min(alaraja + minimista_yli, ylaraja)

    return True

def kokonaismaarat(osuudet, haarukka, taysi=False, lkm=None):
    '''palauta kokonaisluku, jolle löytyy annettuja prosenttiosuuksia vastaavat kokonaisluvut

    jos taysi = True, edellytä että osuuksien summa täsmää kokonaismäärään
    jos vahintaan on kokonaisluku, iteroi kaikki ratkaisut siihen asti, muuten loputtomasti

    '''
    rajat = [o.pyoristysrajat() for o in osuudet]
    osumia = 0
    for kokmaara in haarukka:
        osumat = []
        for ala, yla in rajat:
            x_min, x_max = ceil(ala * kokmaara), ceil(yla * kokmaara)
            if x_min == x_max:
                break  # ei kokonaislukua välissä
            osumat.append([x_min, x_max - 1])  # prosenttiosuutta vastaava kokonaislukuväli
        else:
            # kaikille prosenttiosuuksille löytyi vastaavat kokonaisluvut
            if (not taysi) or sovita_osumat_kokonaismaaraan(kokmaara, osumat):
                yield (kokmaara, osumat)
                osumia += 1

        if lkm is not None and osumia == lkm:
            break

def tulosta_vastaus(pym, osuudet, rajat):
    print('%d on mahdollinen kokonaismäärä, jolle annetut prosenttiosuudet täsmäävät' % pym)
    for o, r in zip(osuudet, rajat):
        if r[0] == r[1]:
            # vain yksi kokonaisluku osuu annettuun prosenttiosuuteen
            print('{0} / {1} = {2}'.format(r[0], pym, o))
        else:
            print('{0}..{1} / {2} = {3}'.format(r[0], r[-1], pym, o))

def luo_argumenttijasennin():
    kuvaus = 'Etsii pienimmän/t kokonaisluvun, johon annetut prosenttiosuudet sopivat. ' + \
             'Anna osuudet prosentteina, esim. 3 tai 74.25.'
    kaytto='usage: %prog [optiot] [osuudet]'
    jasennin = OptionParser(usage=kaytto, description=kuvaus)
    jasennin.add_option('-k', '--kaikki', dest='kaikki', action='store_true', default=False, \
                      help='tarkista että osuuksien summa on 100 % eli kaikki vaihtoehdot ovat mukana')
    jasennin.add_option('-n', '--lukumaara', dest='lkm', type='int', default=1, \
                      help='näytettävien ratkaisujen lukumäärä (oletus = 1)')
    jasennin.add_option('-y', '--ylaraja', dest='yraja', type='int', default=0, \
                      help='hae ratkaisut tähän ylärajaan asti (oletus = ei rajaa). ' + \
                           'Jos tämä on annettu, määräoptiota ei huomioida.')
    jasennin.add_option('-a', '--alaraja', dest='araja', type='int', default=1, \
                      help='hae ratkaisut tästä alarajasta asti (oletus = 1).')
    jasennin.add_option('-t', '--tarkka', dest='tarkka', type='int', default=0, \
                      help='hae mahdollinen ratkaisu vain tästä määrästä')
    return jasennin

def main():
    jasennin = luo_argumenttijasennin()
    (opt, args) = jasennin.parse_args()

    try:
        osuudet = list(map(Prosenttiosuus, args))
    except ValueError as e:
        print('Virhe argumenteissa: %s' % e)
        exit(-1)

    if not args:
        jasennin.print_help()
        return

    if opt.kaikki:
        # tarkista että osuuksien summa on 100 % pyöristysrajat huomioiden
        rajat = (o.pyoristysrajat() for o in osuudet)
        minimi, maksimi = [sum(x) for x in zip(*rajat)]
        if not (minimi <= 1.0 <= maksimi):
            print('Virhe: Annettujen prosenttiosuuksien summa ei ole 100')
            exit(-1)

    if opt.tarkka:
        rajat = [opt.tarkka]
    elif opt.yraja:
        rajat = range(opt.araja, opt.yraja + 1)
    else:
        rajat = itertools.count(opt.araja)

    for pym, osumat in kokonaismaarat(osuudet, rajat, opt.kaikki, opt.lkm):
        tulosta_vastaus(pym, osuudet, osumat)

if __name__ == '__main__':
    main()

Grez [06.08.2015 13:42:10]

#

Mitäs jos vielä laitat tuen erilaisille pyöristystyyleille. :D Vaikka pyöristys alaspäin (desimaalien leikkaus) ja IEEE 754 mukainen hollantilainen / pankkiirin pyöristys

Macro [06.08.2015 14:06:00]

#

Chiman kirjoitti:

Koodivinkiksi tämä ei taida soveltua hyvin rajallisen käyttötarpeensa takia.

Sinne vaan, muutenkin niin vähän uusia tullut viime vuosina, että hyvä jos joku käy vähän puhaltamassa pölyjä pois.

Metabolix [06.08.2015 16:12:53]

#

Hieno koodi.

Macro kirjoitti:

Chiman kirjoitti:

Koodivinkiksi tämä ei taida soveltua hyvin rajallisen käyttötarpeensa takia.

Sinne vaan, – –

Vaatisi kyllä kohtalaisesti viilausta ja selitystä, jotta tästä saisi varsinaisen koodivinkin. Mutta jos Chiman haluaa, kyllä tuon voi laittaa omat tuotokset -osioon.

Jos vanhat koodivinkit tuntuvat huonoilta, reippaasti vain kommentoimaan sinne ja tekemään itse parempia. Poistetaan sitten ihan surkeimpia, ja kehityskelpoisista mutta bugisista saa kyllä tehdä parempia versioita tilalle.


Sivun alkuun

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta