Kirjautuminen

Haku

Tehtävät

Keskustelu: Ohjelmointikysymykset: C++: Laskukone

Sivun loppuun

Azure [09.05.2007 22:04:07]

#

Olen tässä alkanu projektina kehittää komentorivi pohjaista laskinta jonka tehokkuus kasvaa uuden oppimisen vahdilla. Nykyään se pystyy laskemaan kahden ja kolmen luvun vähennys ja plus laskuja. Yritin miettiä, että miten saisin ohjelmastani sellasen että ei tarvitsisi tehdä miljoonia koodi rivejä, mutta se pystyisi laskemaan suuriakin lausekkeita. Kuten 1+1+1+1+1+1...jne siihen asti kunnes tulee laskun lopettava on (=) merkki. Pitääkö siihen käyttää string merkkijonoja sitten kun olen tullu siihen oppimis vaiheeseen, vai onko muitakin keinoja? Itse mietiskelin jotain silmukka raikenteita, mutta ei tehny tulosta, koska samalla muuttujalle ei voi minun tietääkseni välin väliä antaa uusia arvoja toisen katoamatta. Neuvoja?

Olen pahoillani etten voi asiaa havainnollistaa näyttämällä nykyistä koodiani, koska kotonani ei tällä hetkellä netti toimi...

Mazzimo [09.05.2007 22:30:51]

#

Rivin tulee olla merkkijono. Parsit koodin läpi ja jaat sen osiin: operaattoreihin (+ - *....) ja numeroihin. Sijoitat parsitut palaset (sanotaan tokenet) vektoriin. Voit luoda kantaluokan "Token", josta periytät "OperatorToken" ja "ValueToken". Tämän jälkeen iteroit listaa/vektoria läpi etsien "OperatorToken"-luokan luotuja jäseniä. Näiden operaattorien ympärillä tulee olla kummallakin puolella ValueToken. Suoritat operaattorin annetuilla arvoilla, pyyhit kohdan (vasen arvo + operaattori + oikea arvo) pois listasta ja lisäät niiden paikalle ValueTokenen, jolla on operaattorin suorituksesta saatu paluuarvo. Käyt tällä tavalla koko listan läpi operaattori operaattorilta niiden prioriteettijärjestyksessä (+ - * /...).

Lopulta tulisi olla vain yksi ValueToken listassa jäljellä, joka on laskun tulos.

EDIT: Teen huomenna sinulle pienen esimerkkikoodin. :)
EDIT2: Sulkuihin vinkki: rekursio. ;)

Antti Laaksonen [09.05.2007 22:32:20]

#

Kun laskettava lauseke on luettu merkkijonoon (ihan tavallinen C:n char-taulukko kelpaa), sitä pitää alkaa purkaa niin pieniin osiin, että laskun saa loppujen lopuksi laskettua C:n perusmerkinnöillä. Eli merkkijonosta täytyy tunnistaa luvut (peräkkäin olevia numeroita) ja laskutoimitukset (+, -, *, /) sekä mahdollisesti sulut.

Jos lausekkeessa on pelkkiä yhteen- ja vähennyslaskuja, laskut voi suorittaa helposti vasemmalta oikealle, mutta jos mukana voi olla myös kerto- ja jakolaskuja, täytyy alkaa kiinnittää huomiota laskujärjestykseen, eli missä järjestyksessä lukujen väliset laskut tehdään. Vielä monimutkaisemmaksi tilanne muuttuu, jos lausekkeessa voi olla myös sulkuja.

Silmukkoja tarvitaan, kuten epäilitkin, ja heti aluksi voisi olla hyvä erottaa laskulausekkeen osat erilliseen taulukkoon, jolloin niitä on helpompi käsitellä. Suosittelen kuitenkin, että tutkit ensin kynällä ja paperilla, kuinka ohjelman tulisi toimia, niin moni asia selkeytyy. Miten laskisit itse laskulausekkeen arvon, jos pystyisit näkemään merkkejä yksi kerrallaan ja käytössä olisi tarvittava määrä muuttujia, joita voi yhdistellä laskutoimituksilla?

Mazzimo [10.05.2007 16:59:57]

#

Lupasin eilen pienen esimerkin, mutta siitä tulikin 250 rivinen jättiläinen..
Koodi löytyy täältä: http://mureakuha.com/paste/?e0d4afa5f8e032cf3b1dff3a743e7ffd
Koodi on helppolukuista, mutta siinä on käytetty virtuaalifunktioita ja polymorfismia aika paljon hyväksi, joten aloittelijalla voi olla vaikeuksia tajuta ideaa. Tuolla idealla kuitenkin uusin ominaisuuksien lisääminen on todella helppoa.

Nykyisestä koodista löytyy seuraavat ominaisuudet:
- +-*/ prioriteetteineen
- alkeellinen syntaksitarkistus (assertio)

Pekka Karjalainen [10.05.2007 19:16:28]

#

Jos antaa tyhjän tai välilyönneistä koostuvan syötteen, ohjelma antaa virheilmoituksen. En sano siksi, että nyt pitäisi heti korjata. Se on vain hankala erikoistapaus muistaa tarkistaa.

Mazzimo [10.05.2007 20:57:52]

#

Itse asiassa ei ole. Laittaa vain ennen assert( tokens.size() == 1 ); -kohtaa "if( tokens.size() )" ja tulostukset ja assertiot tämän if-lauseen sisään, ohjelma osaa ottaa huomioon myös tyhjät rivit.

Pekka Karjalainen [11.05.2007 11:19:49]

#

No, tarkoitin, että se unohtuu monesti ensimmäisestä versiosta, kuten sinulle kävi. Helppohan se sitten on, kun sen muistaa. :-)

Vertailun vuoksi Bjarne Stroustrupin C++-kirjassa on esimerkki rekursiivisesti laskeutuvasta laskimesta, jossa on symbolitaulu muuttujia varten (map-olio stringeistä doubleihin). Siihen Bjarne-setä kuluttaa yhteensä noin 150 riviä, mutta hän ei käytäkään olio-ominaisuuksia vielä siinä vaiheessa kirjaa ollenkaan.

Itse asiassa hän myöhemmin parantaa ohjelmaa käyttämään nimiavaruuksia ym. paremmin.

Mainitsen nyt lähinnä siksi, että se on varsin opettavainen esimerkki ja hyvä katsoa, jos kirja on saatavilla. Ongelmaahan voi toki lähestyä monilla tavoilla.

(Recursive descent (rekursiivinen laskeutuminen?) on perustapa toteuttaa yksinkertainen jäsentäjä.)

tesmu [11.05.2007 12:01:21]

#

http://mureakuha.com/paste/?5c5f76d0c4324ab543193c01c1fceb9a <--- 208 rivinen parserihirmu
Toimii näin
using namespace Calc;
double retu;
string matikka="5+5*(4-2)/5";
MathNode *foo;
foo=parse_math(matikka);
retu = foo->eval();

Ja näin retu muuttujassa on laskettuna matematiikka...

Mazzimo [11.05.2007 14:43:21]

#

Kopeekka kirjoitti:

Ongelmaahan voi toki lähestyä monilla tavoilla.

Kyllä. Olen varma, että yksinkeraisempiakin tapoja on. Sovelsin vain hieman ohjelmointikieleni kääntäjän koodia ja muutin sen hieman lyhyempään muotoon. Ohjelmointikielessä kun asiat eivät tuppaa olemaan näin yksinkertaisia (on muuttujia, funktioita, operaattoreita, avainsanoja, merkkijonoja, vakioita...). Siksi jokaisen tokenen parsinnassa on ensiarvoisen tärkeää, että saa jo parsintavaiheessa tarpeeksi tietoa eri tokeneista. Tämän jälkeen kaikki käy paljon helpommin. :)

Azure [14.05.2007 15:36:00]

#

Hmmmm... Kiitos teille kaikille hyvistä vinkeistä, mutta nyt kun minulla ei se netti toimi ja ei jaksa alkaa noita koodeja ihan heti tuonne kotikoneelle viemään muistitikussa niin, miten se muuten? Minkä veroisilla aloittelijan tiedoilla vois jo saada pienen pähkäilyn jälkeen tuollaisen aikaan.Itse osaan aika hyvin nyt muuttujat, vakiot,perusfunktiot,perusluokat ja jotain silmukoista. Pitäskö opetella hyvin kirjassani oleva pitkähkö osoittimista kertova kappale vai siirtyä suoraan taulukkoihin ja sitten palata niihin osoittimiin. Eli kysyn vain, että kumpi olisi aloittelijalle parempi vaihtoehto opetella ensin osoittimet ja sitten taulukot vai toisin päin?

feenix [14.05.2007 16:17:28]

#

Jos haluaa tehdä laskimen siten että se on oikeasti tehokas ja toimiva, siihen on helppo tapa: RPN. Eli siis reverse polish notation (tai postfix toiselta nimeltään, "normaali" kaava on infix).

infix: 2+3*5+7*(2+9) => postfix 2 3 5 * + 7 2 9 + * +

Postfixiä lasketaan pinosta ja esimerkistä on helppo tajuta miten se toimii:

- lue listaa token kerrallaan
- jos token on numero, laita se pinoon
- jos token on operaattori, ota kaksi arvoa pinosta ja laske operaattorin mukaan, laita tulos pinoon

Lopulta jäljellä on yksi tulos ja laskenta on valmis. Muunnos infix->postfix on myös helppo ja jätetään harjoitustehtäväksi. Operaattorijärjestyskin menee automaattisesti oikein eikä tarvitse skannata koko listaa läpi.

Eräs C#-implementaationi hallitsee muuttujat, funktiot, perusoperaatiot jne ja on pituudeltaan samaa luokkaa noiden "pastehirviöiden" kanssa ;) Tietysti riippuu funktioiden määrästä miten paljon tarvitaan koodia.

Niin ja varsinkin jos pitää laskea useaan kertaan sama kaava (vaikka eri muuttujien arvoilla), on huomattavasti tehokkaampaa muuntaa postfixiksi eikä parsia joka laskukerta lauseketta läpi. Ja tietysti sieventimen tekeminen on myös helppoa, ei tarvitse tehdä muuta kuin katsoa onko operaattoria ennen kaksi numeroa ja jos on, laskee ne valmiiksi ja tunkee jonoon. Infixinä hieman ikävämpää joskus (postfix-muunnoksessa voi nääs heittää sulutkin mäkeen helposti, infixissä vaatii enemmän logiikkaa ihmetellä voiko).

Esim: (2+3)+(7*(b-2)) => 2 3 + 7 b 2 - * => 5 7 b 2 - *


Sivun alkuun

Vastaus

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

Tietoa sivustosta