Hei,
Olen saanut kurssin harjoitustyönä tehtäväksi ohjelman, joka:
1. tarkistaa suomalaisen kansallisen viitenumeron
2. luo suomalaisen kansallisen viitenumeron
3. luo käyttäjän syöttämän määrän viitteitä ja tulostaa ne tiedostoon.
Koodini on tässä vaiheessa seuraava:
using System; namespace Harjoitustyö { class Program { static void Main(string[] args) { Console.WriteLine("Kansallinen viitenumero"); Console.WriteLine("Valitse toiminto [1-3]: "); int userAddedReferenceNumber = 0; Console.WriteLine($"1. Haluan tarkastaa kansallisen viitenumeron"); Console.WriteLine($"2. Haluan luoda kansallisen viitenumeron"); Console.WriteLine($"3. Haluan määrittää monta viitenumeroa luodaan ja tulostetaan tiedostoon"); { int caseSwitch = int.Parse(Console.ReadLine()); switch (caseSwitch) { case 1: Console.WriteLine("Syötä viitenumero"); userAddedReferenceNumber = int.Parse(Console.ReadLine()); Console.WriteLine($"{userAddedReferenceNumber}"); Console.WriteLine("Viitenumero on oikein"); break; case 2: Console.WriteLine("Viitenumero luotu"); break; default: int viitteet = 0; int i = 0; Console.WriteLine("Syötä tiedostoon tulostettavien viitteiden lukumäärä"); viitteet = int.Parse(Console.ReadLine()); for (i = 0; i < viitteet; i++) Console.WriteLine("viitteet tulostetaan tiedostoon"); break; } } } } }
Ongelmani on tehdä ns. "kova ohjelmointi" eli, kuinka tarkistan syötetyn viitteen oikeellisuuden, kuinka luon (satunnaisen) uuden viitenumeron ja, kuinka saan ohjelman tulostamaan ja luomaan viitteet käyttäjän syöttämän lukumäärän mukaisesti ja tiettyyn haluttuun tiedostoon.
Ohjelmoinnin perusteet kurssilla on kyllä käyty asioita melko laajastikin läpi, mutta loppuharjoitustyö on sen verran laaja, että pyydän apua. En tarkoita välttämättä valmista toimivaa koodia vaan vinkkejä, neuvoja ja apuja, kuinka em. toiminnot saadaan ohjelmoitua.
Ensimmäinen vinkki, että ei kannata muuntaa viitettä kokonaisluvuksi (int.Parse) koska viite voi olla jopa 20 numeroa (19+tarkistusnumero) pitkä eikä näin ollen sovi 32-bittiseen numeromuuttujaan. Lisäksi siitä ei ole sanottavaa hyötyä, koska joudut käsittelemään joka tapauksessa yksittäisiä numeroita. Tilanne olisi eri esimerkiksi henkilötunnuksen tarkistenumerossa, sillä siinä tarkiste lasketaan jakojäännöksenä numerosta sellaisenaan.
Tässä dokumentissa on selostettu kuinka tarkistenumero lasketaan.
https://www.finanssiala.fi/maksujenvalitys/
Tuon lukemisen jälkeen pitäisi pystyä koodaamaan tarkisteen laskeminen ja tarkistaminen millä tahansa osaamallaan ohjelmointikielellä. Jos osaaminen on vähintään tyydyttävällä tasolla niin pitäisi mennä alle 15 minuuttia - eli mielestäni ei ihan rehellisesti voi puhua laajasta tehtävästä.
Grez kirjoitti:
(25.11.2018 21:40:05): Ensimmäinen vinkki, että ei kannata muuntaa...
Tuon ohjeen on jakanut opettajakin. Osaamista ei kyllä kurssilla ole tullut sen vertaa, että 15 minuutissa koodaa puuttuvat rivit niiden toimien. Opettajaltakin olen asiaa kysynyt ja sanonut, että en suoraa vastausta edes halua, mutta itselle jopa "perusteet" -kurssilla annettu tehtävä on haastava. En edes tiedä, mistä lähtee koodia jatkamaan.
No itse koodaisin tarkistusnumeron laskevan funktion jotenkin tälleen:
public static string ViitenumeroTarkisteella(string alku) { //"Summa" eli riittävän iso 10 jaollinen luku, josta vähennellään int s = 10000; //Viitestandarin mukaiset painotukset lopusta lukien (pl. tarkiste) //Toistuu 3 merkin välein var painotus = new int[] { 7, 3, 1 }; //painojen laskuri int i = 0; //Merkkijono merkkitaulukoksi var merkit = alku.ToCharArray(); //Käydään merkit läpi lopusta alkuun (jolloin ei tarvitse välittää etunollista) for (int c = merkit.Length - 1; c >= 0; c--) { //Vähennetään "summasta" merkin arvo kerrottuna vastaavalla painolla s -= painotus[(i++) % 3] * (merkit[c] & 0xf); } //Palautetaan viitteen alkuosa lisättynä lasketulla tarkisteella return alku + (s % 10).ToString(); }
Edit: Lisätty kommentit
Edit2: Tässä vielä "optimoimattomampi" versio, joka vastaa tarkemmin tapaa jota tuossa speksissä käytetään. Lisäksi se ei hyödynnä Unicode merkistön numeron erityispiirrettä (että 4 alinta bittiä on suoraan numero)
public static string ViitenumeroTarkisteella(string alku) { int summa = 0; //Viitestandarin mukaiset painotukset lopusta lukien (pl. tarkiste) var painotus = new int[] { 7, 3, 1 }; //painojen laskuri int painoIndeksi = 0; //Käydään merkit läpi lopusta alkuun (jolloin ei tarvitse välittää etunollista) for (int c = alku.Length - 1; c >= 0; c--) { summa += painotus[painoIndeksi++] * int.Parse(alku.Substring(c, 1)); if (painoIndeksi >= painotus.Length) { painoIndeksi = 0; } } //Palautetaan viitteen alkuosa lisättynä lasketulla tarkisteella return alku + ((10 - summa % 10) % 10).ToString(); }
Jos tuon saan toimimaan, arvosana ykkönen on taattu. Kuinka kutsun tuota aliohjelmafunktiota Case 1:ssä?
Itse satunnaisen viitenumeron generoiminen on asia täysin erikseen sekä myös tuo käyttäjän syöttämän viitemäärän luonti ja tiedostoon tulostaminen.
Arvosana 5 edellyttää, että ohjelma toimii kaikkineen.
No case 1:n osalta voit joko tehdä tarkistavan funktion tai käyttää tuota samaa funktiota ja antaa sille käyttäjän syötteen ilman viimeistä merkkiä ja sitten verrata sen antamaa tulosta käyttäjän syötteeseen.
Miten?
Sanot että et halua valmista koodia. Ei tässä nyt voi enää hirveästi vähemmän valmista antaa.
Grez kirjoitti:
Tuon lukemisen jälkeen pitäisi pystyä koodaamaan tarkisteen laskeminen ja tarkistaminen millä tahansa osaamallaan ohjelmointikielellä. Jos osaaminen on vähintään tyydyttävällä tasolla niin pitäisi mennä alle 15 minuuttia - eli mielestäni ei ihan rehellisesti voi puhua laajasta tehtävästä.
Alla 8th koodi halutun pituisen satunnaisen viitenumeron generoimiseen, tarkisteen lisäämiseen viitenumeroon ja valmiin viitenumeron tarkistamiseen. Bonuksena viitenumero saadaan jaoteltuna viiden numeron ryhmiin helpottamaan luettavuutta. Virheenkäsittely on minimaalinen eli viitteen sijasta palautetaan null, jos yritetään generoida vääränlaista viitenumeroa.
Pystyykö C# lyhyempään ja selkeämpään toteutukseen? ;-)
: strip-leading-zeros \ s -- s /^0+/ s:/ "" a:join ; : muotoile \ s -- s s:rev "5:5b" unpack drop ' >s a:map " " a:join s:rev ; : numeroja? \ s -- s f dup /[^0-9]/ r:match nip 0 n:= if true else false then ; : rand0-9 rand-pcg n:abs 10 n:mod ; : rand1-9 rand-pcg n:abs 9 n:mod n:1+ ; : random-viite? \ n -- viite >r r@ 3 19 n:between not if rdrop null else ( 0 n:= if rand1-9 else rand0-9 then >s ) 0 r@ n:1- loop r> a:close "" a:join then ; : +tarkiste? \ viite -- viite+tarkiste " " "" s:replace! strip-leading-zeros numeroja? if dup s:len 3 19 n:between not if 2drop null else "" s:/ a:new >r ( >n [ 1, 3, 7 ] rot 3 n:mod a:@ nip n:* r@ swap a:push drop ) a:each drop r> ' n:+ 0 a:reduce dup 10 n:/ n:ceil 10 n:* n:int swap n:- 10 n:mod >s s:+ then else drop null then ; : tarkasta \ viite -- f " " "" s:replace! strip-leading-zeros numeroja? if -1 s:@ "" swap s:+ >n >r s:len n:1- 1 s:- s:len 3 19 n:between not if drop false else "" s:/ a:new >r ( >n [ 1, 3, 7 ] rot 3 n:mod a:@ nip n:* r@ swap a:push drop ) a:each drop r> ' n:+ 0 a:reduce dup 10 n:/ n:ceil 10 n:* n:int swap n:- 10 n:mod r> n:= then else drop null then ; : tee-viite \ n -- viitenumero n:1- random-viite? null? if ;; then +tarkiste? muotoile ; \ \ -------------------------------------------------------------------------------------------------------------------------- \ : app:main 12 tee-viite . cr bye ;
Sanoisin että pystyy. Ja pystyy myös selkeämpään.
using System; using System.Text.RegularExpressions; public class Program { public static void Main() { Console.WriteLine(TeeViite("1234567")); //Tarkistenumero omaan viitepohjaan Console.WriteLine(TeeViite("123456789012345")); //iso pohja Console.WriteLine(TeeViite("85")); //0 padding Console.WriteLine(TeeViite()); //Kokonaan generoitu Console.WriteLine(TeeViite("abcdf")); //Väärä muoto - null palautuu (saman tuloksen antaa pelkkä 0 tai 012 jne missä 0 on alkunumero) /* Jos 0(tai useampi 0) haluttaisiin trimmata alusta null palautuksen sijaan, metodissa olisi pohja = pohja.TrimStart("0"); */ } static string TeeViite(string pohja = null) { pohja = string.IsNullOrEmpty(pohja) ? new Random().Next(1000000,9999999).ToString() : pohja; if(!new Regex(@"(?=^\d+$)(?=^[1-9])").IsMatch(pohja)) {return null;} pohja = pohja.Length < 3 ? pohja.PadRight(3, '0') : pohja; int tarkistenumero = 0, k = 1, j = 0; var viitepohja = ""; var kerroin = "731"; for(int i=pohja.Length - 1; i >= 0 ; i--) { var merkki = pohja[i]; tarkistenumero += int.Parse(kerroin[j % 3].ToString()) * int.Parse(merkki.ToString()); viitepohja = k % 5 == 0 ? $"{merkki} {viitepohja}" : merkki + viitepohja; j++; k++; } tarkistenumero = (10 - tarkistenumero % 10) % 10; return viitepohja + tarkistenumero.ToString(); } } /* Printti (muutettu Grezin huomaamasta k = 1): 123 45672 1 23456 78901 23450 8507 457 34920 (mikä sitten generoituukaan) {null} */
Selkeys kai on osittain katsoja silmässä. Jos on tottunut C-tyylisiin kieliin (C, C++, C#, Java, JS yms) niin varmaan mikä vaan näyttää selkeämmältä kuin tuo Jalskin koodi. Jos taas on tottunut katselemaan vain 8th koodia niin ehkä tuo Jalskin koodi voi näyttää selkeämmältä.
groovyb kirjoitti:
Printti:
12 34567 2
Sanoisin että tuo välien generointi ei mene ihan viitestandardin mukaisesti. Äkkiseltään tulee mieleen että auttaisikohan, jos alustaisi k:n 1:ksi.
Eli tarkistusnumeron kuuluisi olla osa viimeistä ryhmää, ei erikseen. Tyyliin 123 45672.
Myös 12345 672 olisi speksin mukaan sallittu. Olettaisin kuitenkin että tuolla toteutuksella lopusta päin ryhmittely käy näppärämmin. Lisäksi se on myös yleisemmin käytetty.
k = 1 palauttaa seuraavaa:
123 45672 1 23456 78901 23450 8507 457 34920
En tutustunut standardiin mitenkään, tein vaan tuon jaottelun ihan päästäni :D
Muutan alustuksen esimerkkiin
groovyb kirjoitti:
Sanoisin että pystyy. Ja pystyy myös selkeämpään.
Muokkasin omaa 8th toteutustani hieman ja lisäsin tee-viite sanan. Tekemäsi C# toteutus näyttää selkeältä. Toteutusten eroina on, että omani hyväksyy syötteessä olevat ryhmittelyvälit numerosarjoissa ja satunnaisesti generoidulle viitteelle saa annettua pituuden.
Olen saanut seuraavan koodin aikaan, kiitos luentojen ja teidän:
using System; using System.IO; namespace Harjoitustyö { class Program { static void Main(string[] args) { string userAddedReferenceNumber = ""; string viitenumero = ""; do { Console.WriteLine(); Console.WriteLine("Kansallinen viitenumero"); Console.WriteLine("Valitse toiminto [1-3]: "); Console.WriteLine($"1. Haluan tarkastaa kansallisen viitenumeron"); Console.WriteLine($"2. Haluan luoda kansallisen viitenumeron"); Console.WriteLine($"3. Haluan määrittää monta viitenumeroa luodaan ja tulostetaan tiedostoon"); bool isNumber = int.TryParse(Console.ReadKey().KeyChar.ToString(), out int caseSwitch); if (isNumber) //int caseSwitch = int.Parse(Console.ReadLine()); { switch (caseSwitch) { case 1: Console.WriteLine(); Console.WriteLine("Syötä viitenumero kokonaisuudessaan"); viitenumero = Console.ReadLine(); string output = viitenumero.Substring(viitenumero.Length - 1, 1); Console.WriteLine($"{output}"); string alkuosa = viitenumero.Substring(0, viitenumero.Length -1); Console.WriteLine($"{alkuosa}"); string output2 = ViitenumeroTarkisteella(alkuosa); if (output == output2.Substring(output2.Length-1,1)) Console.WriteLine("Viitenumero on oikein"); else Console.WriteLine("Viitenumero on virheellinen"); break; case 2: Console.WriteLine(); Console.WriteLine("Syötä viitenumeron alkuosa"); userAddedReferenceNumber = Console.ReadLine(); Console.WriteLine($"{ViitenumeroTarkisteella(userAddedReferenceNumber)}"); Console.WriteLine("Viitenumero luotu"); break; case 3: int viitteet = 0; int i = 0; string referenceNumber = ""; string refNumbers = ""; Console.WriteLine(); Console.WriteLine("Syötä tiedostoon tulostettavien viitteiden lukumäärä"); viitteet = int.Parse(Console.ReadLine()); Console.WriteLine("Syötä viitteen alkuosa"); referenceNumber = Console.ReadLine(); for (i = 0; i < viitteet; i++) { refNumbers += $"{ViitenumeroTarkisteella(referenceNumber + i)}\n"; } string path = @"C:\TEMP\referencenumber.json"; WriteToFile(path, refNumbers); Console.WriteLine("Viitenumerot luotu! Tarkista kansio C:/TEMP/referencenumber.json"); break; default: Console.WriteLine("Virhe!!!"); break; } } //Console.Clear(); } while (true); } static void Delay() { for (int i = 0; i < 3; i++) { Console.Write("."); System.Threading.Thread.Sleep(1000); } Console.WriteLine(); } public static string ViitenumeroTarkisteella(string alku) { int summa = 0; //Viitestandarin mukaiset painotukset lopusta lukien (pl. tarkiste) var painotus = new int[] { 7, 3, 1 }; //painojen laskuri int painoIndeksi = 0; //Käydään merkit läpi lopusta alkuun (jolloin ei tarvitse välittää etunollista) for (int c = alku.Length - 1; c >= 0; c--) { summa += painotus[painoIndeksi++] * int.Parse(alku.Substring(c, 1)); if (painoIndeksi >= painotus.Length) { painoIndeksi = 0; } } //Palautetaan viitteen alkuosa lisättynä lasketulla tarkisteella return alku + ((10 - summa % 10) % 10).ToString(); } /// <summary> /// Write to file by using StreamWriter Class /// Create a new file /// string filePath is /// </summary> /// <param name="filePath"></param> static void WriteToFile(string filePath, string data) { using (StreamWriter sw = new StreamWriter(filePath)) { sw.WriteLine("Tulostetut viitenumerot"); sw.WriteLine(DateTime.Now.ToString()); sw.WriteLine($"{data}"); } } } }
Käytännössä ohjelma/koodi on muuten valmis, mutta puuttuu vielä virhetilanteiden käsittely Case2 ja Case3, jos käyttäjä esimerkiksi syystä tai toisesta syöttää "a" tai aakkosellisen merkkijonon eikä oletusarvoisesti numeerista merkkijonoa.
yksi tapa testata, onko käyttäjä syöttänyt pelkkiä numeroita:
bool OnkoVainNumeroita(string str) { foreach (char ch in str) { if (ch < '0' || ch > '9') return false; } return true; }
Eli funktio palauttaa false jos annettu string sisältää jotain muuta kuin numeroita. Jos syöte on pelkkiä numeroita, niin sitten true.
Tai sitten ihan regexillä tai linq:llä.
//Linq var tulos = merkkijono.All(char.IsDigit); //true jos kaikki merkit numeroita //Regex var tulos2 = new Regex(@"^\d+$").IsMatch(merkkijono); //true jos kaikki merkit numeroita
Laitetaan tässä vielä lopullinen ratkaisu, teidän sekä opettajan avustuksella virheen tunnistuksineen:
using System; using System.IO; namespace Harjoitustyö { class Program { static void Main(string[] args) { string userAddedReferenceNumber = ""; string viitenumero = ""; do { Console.WriteLine(); Console.WriteLine("Kansallinen viitenumero"); Console.WriteLine("Valitse toiminto [1-3]: "); Console.WriteLine($"1. Haluan tarkastaa kansallisen viitenumeron"); Console.WriteLine($"2. Haluan luoda kansallisen viitenumeron"); Console.WriteLine($"3. Haluan määrittää monta viitenumeroa luodaan ja tulostetaan tiedostoon"); bool isNumber = int.TryParse(Console.ReadKey().KeyChar.ToString(), out int caseSwitch); if (isNumber) //int caseSwitch = int.Parse(Console.ReadLine()); { switch (caseSwitch) { case 1: Console.WriteLine(); Console.WriteLine("Syötä viitenumero kokonaisuudessaan"); viitenumero = Console.ReadLine(); string output = viitenumero.Substring(viitenumero.Length - 1, 1); Console.WriteLine($"{output}"); string alkuosa = viitenumero.Substring(0, viitenumero.Length - 1); Console.WriteLine($"{alkuosa}"); string output2 = ViitenumeroTarkisteella(alkuosa); if (output == output2.Substring(output2.Length - 1, 1)) Console.WriteLine("Viitenumero on oikein"); else Console.WriteLine("Viitenumero on virheellinen"); break; case 2: Console.WriteLine(); Console.WriteLine("Syötä viitenumeron alkuosa"); userAddedReferenceNumber = Console.ReadLine(); if (IsValidReferenceNumber(userAddedReferenceNumber)) { Console.WriteLine($"{ViitenumeroTarkisteella(userAddedReferenceNumber)}"); Console.WriteLine("Viitenumero luotu"); } else Console.WriteLine("Syöte on virheellinen!"); break; case 3: int viitteet = 0; int i = 0; string referenceNumber = ""; string refNumbers = ""; Console.WriteLine(); do { try { Console.WriteLine("Syötä tiedostoon tulostettavien viitteiden lukumäärä"); viitteet = int.Parse(Console.ReadLine()); break; } catch (Exception ex) { Console.WriteLine(ex.Message); } } while (true); do { Console.WriteLine("Syötä viitteen alkuosa"); referenceNumber = Console.ReadLine(); if (IsValidReferenceNumber(referenceNumber)) { for (i = 0; i < viitteet; i++) { refNumbers += $"{ViitenumeroTarkisteella(referenceNumber + i)}\r\n"; } string path = @"C:\TEMP\referencenumber.txt"; WriteToFile(path, refNumbers); Console.WriteLine("Viitenumerot luotu! Tarkista kansio C:/TEMP/referencenumber.txt"); } else Console.WriteLine("Virheellinen syöte"); } while (i < viitteet); break; default: Console.WriteLine(" Virhe!!!"); break; } } //Console.Clear(); } while (true); } static void Delay() { for (int i = 0; i < 3; i++) { Console.Write("."); System.Threading.Thread.Sleep(1000); } Console.WriteLine(); } /// <summary> /// Tarkistaa syötetyn viitenumeron pituuden ja, että sisältää sallittuja merkkejä /// Palauttaa arvon true tai false /// </summary> /// <param name="check"></param> /// <returns></returns> static bool IsValidReferenceNumber(string check) { bool retValue = false; if (check.Length >= 3 && check.Length <= 19) retValue = true; retValue = int.TryParse(check, out int refNumber); return retValue; } public static string ViitenumeroTarkisteella(string alku) { int summa = 0; //Viitestandarin mukaiset painotukset lopusta lukien (pl. tarkiste) var painotus = new int[] { 7, 3, 1 }; //painojen laskuri int painoIndeksi = 0; //Käydään merkit läpi lopusta alkuun (jolloin ei tarvitse välittää etunollista) for (int c = alku.Length - 1; c >= 0; c--) { summa += painotus[painoIndeksi++] * int.Parse(alku.Substring(c, 1)); if (painoIndeksi >= painotus.Length) { painoIndeksi = 0; } } //Palautetaan viitteen alkuosa lisättynä lasketulla tarkisteella return alku + ((10 - summa % 10) % 10).ToString(); } /// <summary> /// Write to file by using StreamWriter Class /// Create a new file /// string filePath is /// </summary> /// <param name="filePath"></param> static void WriteToFile(string filePath, string data) { using (StreamWriter sw = new StreamWriter(filePath)) { sw.WriteLine("Tulostetut viitenumerot"); sw.WriteLine(DateTime.Now.ToString()); sw.WriteLine($"{data}"); } } } }
JaTu1984 kirjoitti:
/// <summary> /// Tarkistaa syötetyn viitenumeron pituuden ja, että sisältää sallittuja merkkejä /// Palauttaa arvon true tai false /// </summary> /// <param name="check"></param> /// <returns></returns> static bool IsValidReferenceNumber(string check) { bool retValue = false; if (check.Length >= 3 && check.Length <= 19) retValue = true; retValue = int.TryParse(check, out int refNumber); return retValue; }
Hyväksyikö opettaja oikeasti tämän? Kokeileppa antaa sille syötteeksi vaikka 19 numeron merkkijono, missä kaikki numerot ovat yhdeksikköjä. Luulisin, että tulos on false ja overflow. Grez asiasta mainitsikin jo heti ensimmäisessä vastauksessaan...
Aihe on jo aika vanha, joten et voi enää vastata siihen.