Fejadag

Mmmmm… Decadent!

Azt hiszem minden férfi életében eljön egy pont, hogy a családja biztoságának megteremtése, és az otthonteremtés lesz legfőbb célja. A biztonság azonban relatív, és minden fenyegetésre felkészülni nem csak lehetlen, de nagyon költséges is. Engem is érdekel valmelyest – az itthoni átlagnál talán jobban – a krízishelyzetekre felkészülés, vagy ahogy angolul mondják: prepping. Ahogy fontos, hogy legyen pénzügyi tartaléka egy háztartásnak, úgy fontos szerintem az is, hogy néhány napi ivóvíz, némi tartós élelem legyen otthon, ha valami váratlan krízishelyzet miatt átmeneti fennakadások volnának az ellátásban. Nem kell az amerikai bunkerépítő, raktárttöltögető szélsőségekre gondolni, nem érdemes atomháborúra készülni, és sok mindent raktározni sem. Egy ipari katasztrófa, árvíz, vihar azonban ha nem is gyakori, de valós fenyegetés, ami miatti akár több napra kimaradhat a közműellátás, kiürülhetnek a boltok. Egy ilyen helyzet jó, ha nem éri teljesen készületlenül az embert. Nem kell raktárhelyiségre gondolni, elég némi kétszersültre, pár készétel-konzervre, aszalt gyümölcsre, pár zsugor vízre, és egy turista gázfőzőre gondolni, mint a kockázatot olcsón kezelő megoldásra.

A tartósítás története…

Napjainkban a tartós élelmiszer olcsó, könnyen hozzáférhető, és megfelelő tárolás mellett évekig nem kell aggódni a fogaszthatósága miatt. Hány évig? Akár évtizedekig!

A tartósítás tudománya ősi, de az elmúlt 200 év nagy áttöréseket hozott benne. Már az ókorban számos tartósítási folyamat ismert volt, és ezek a középkorban sem változtak gyökeresen. Azonban mint oly sok tudományt az emberiség történetében, a hétköznapi élet igényein túl a hadviselés különleges igényei adtak lökést a tartósítás fejlőésének is. Míg az ókorban óta magokat és szárított javakat vittek a katonák, valamint egész csordákat hajtottak a sereggel együtt, az újkorban ez megváltozott. Franciaországban célirányos kutatást indítottak a hadsereg élelmezének megreformálására, mert a korábbi módszerek nem voltak képesek a tömeghadsereg igényeit stabilan kiszolgálni. A korábbi élelmezési módszerek a hadsereg mobilitását is korlátozták, emellett gyakran nem biztosítottak kellő változatosságot így hiánybetegségekhez vezettek. Amikor a francia hadsereg több embert vesztett az élelmezés menyiségi és minőségi hiányosságai miatt, mint az ellenség miatt, lépni kellett. 1795-ben pályázatot írt ki az francia belügyminisztérium a hadsereg élelmezési problémáinak enyhítésére. 1802-ben Nicolas Appert cukrász új módszert dolgozott ki a tartósításra: : légmentesen zárt üvegedényben forralással tartósította az élelmiszert. Az olyannyira működőképesnek bizonyult, hogy 1810-ben Napóleon meg is jutalmazta új tartósítási találmányáért. Angliában Peter Durand fejlesztette tovább a technológiát: fém edénybe zárta az élelmiszert, így a ma ismert fémkonzerv első változata is megszületett. 1813-tól már nagyüzemileg állytották elő a fém konzerveket. Így lehetővé vált az élelem (egy részének) hátországban elkészítése, és kisebb logisztikai gondot jelentett, mint a hozzávalók frissen, akár élve, saját lábán helyszínre szállítása.

A tartós ételek gyártása sokáig nem fejlődött számottevően, míg nem a 2. világháború során az Egyesült Államokban kidolgoztak egy új élelmiszer tartósítási módot: a fagyasztva szárítást, más nevén a liofilezést vagy liofilizálást. Ősidők óta ismert tartósítási eljárás a szárítás, viszont a fagyasztva szárítás jobban megőrzi a tartósított élelmiszert mint a melegítéses szárítás, azonban a liofilizálásához a vákuumtechnológa fejlettsége is szükséges volt, ugyanis a fagyasztott élelem körüli alacsony nyomás miatt szublimál el a víz, szárítva ki az élelmet. A módszer alapját 1906-ban két francia tudós találta fel, Jacques-Arsène d’Arsonval és segédje Frédéric Bordas. A sors fintora, hogy ezt a technológiát is az angolszászok vitték sikerre, ahogy tették a konzervgyártással is.

Míg a konzervek azért maradnak fogyaszthatóak, mert a bennük levő mikroorganizmusokat a pasztőrözés elpusztítja, és a légmentes zárás miatt tisztán tartja további mikroorganizmusoktól, addig a szárított ételeken a mikroorganizmusok víz híján nem maradnak életben, és nem tudják lebontani az elélmiszert. A szírított ételek szárazon tartása tehát létfontosságú a frissentartásukhoz, így általában ezeket is víz és légmentesen csomgolják. A liofilizált étellel a hétköznapoban az instant kávé formájában találkozhatunk a legkönnyebben.

Az Egyesült Államok a 2. világháború után szuperhatalomként a katonai erejéra nagy gondot kellett fordítson. Az élelmezés terén nem álltak tehát meg a kutatások, és a katonai doktrínák alakulásával együtt eljutottunk oda, hogy míg a hadseregünk a gulyáságyú-konzerv tengelyen képzelte az egyéni élelmezést, az USA-ban megszületett az MRE - Meal Ready to Eat. Ez egy katona egésznapi fejadagját jelenti egy csomagban. Reggeli, ebéd, vacsora, egyben. Mára ez a koncepció elterjedt, és a NATO országokon át még Oroszország hadereje is ebben a koncepcióban oldja meg alakulatainak élelmezését.

A BBC írt egy rövid cikket az MRE kialakulásáról, és annak hatásáról a hétköznapi élelmiszerek előállításának technológiájáról.

…és történésze

Most, hogy a tartósítás módszereit, fejlődését áttekintettük eljutunk írásom eredei ihletőjéhez, a Steve1989MREInfo YouTube csatornához! Steve ugyanis kortárs, és régi katonai fejadagokat kóstol, és erről a Youtube-on közvetít! Egy egész hétvégén át néztem a videóit, és olvasgattam a tartósítás történetéről, illetve arról, hogy mi szükséges egy válságcsomagba élelmiszernek 1 vagy akár 3 nap átvészeléséhez közepes-nehéz fizikai munkavégzés mellett. Még nem vagyok a biztos tudás birtokában, de ha megszereztem, arról mindenképp beszámolok! Arról pedig Steve számol be nekünk, hogy mi a legrégebbi ehető konzerv.

Néhány érdekesebb videó Steve csatornájáról:

2. világháborús német amfetaminos csokoládé

Koreai háborús amerikai MRE

Mai orosz sarkvidéki Spetznaz menü, sertés-agy pástétommal. Decadent!

Mai olasz menü alkoholos aperitiffel és fogkefével.

Gyermekkorom óta bámulatba ejtenek a neon fényreklámok. Az alkalmazott design nyelv, mely részint a technológia korlátaiból, részint a korszakból fakad nagyon érdekes. A nosztalgikus érzés is tagadhatatlanul szerepet játszik ebben. Sajnos kevés fényrekreklámot volt módom működés közben megcsodálni, mert már alig-alig néhány működött gyerekkoromban is, és Budapestre is ritkán látogattam, különösen estefelé. Otthon a városi áruház volt neon Coop Áruház felirattal kivilágítva.

Coop áruház fényreklámja

Egy időben fotókkal próbáltam dokumentálni a fényreklámokat, ssupán akkor kattintottam el pár képet, amikor helyzet adódott. Magamtól nem kerestem a neon fényeket, ma pedig lassanként nincs is mit keresni már…

Libella kávézó Pingvin söröző Textil üzlet

Eközben Oroszországban…

Oroszországban is élnek, akiknek fontosak ezek az emlékek, így igaz, hogy csak digitális trükkökkel, de egyszer még újra “bekapcsolták” a pétervári fényreklámokat a One minute before sunset | Минута до заката című kisfilm készítői.

Hivatkozások

Találni pár gyűjtést a budapesti neonfényekről, de nem ismerek egy átfogó gyűjteményt sem a témában, viszont látható, hogy másokat is érdekel a téma. Az elektrotechnikai múzeumnak van egy kiállítása a témában, amit még nem sikerült megtekintenem eddig.

Neon reklámok a Nők Lapja oldalán

Fényreklmokról a Magyar Nemzet Online oldalán

Origo cikk a témában

Kiterjedtebb album az Indafotón, sajnos főleg kikapcsolt neonokkal

Az oldallal elég keveset foglalkoztam, az is elég hektikusan. Ennek sok oka volt, de az is szerepet játszott ebben, hogy a Pretzel (és amúgy a Jekyll is) meglehetősen lassan renderelte az oldalt le. Ezzel annyira megtörte a publikálás *flow*ját, hogy egyszerűen leszoktam a bloggolásról.

Korábban már hallottam a Hugo nevű Go nyelven íródott, állítólag veszettül gyors statikus oldal generátorról. Sajnos ez sem fedi pontosan az igényeimet, ahogy a piacon egyetlen termék sem, viszont sajátot írni nem igazán fűllött a fogam, úgyhogy úgy döntöttem, hogyha már kompromisszumot kötök, akkor azt ne a sebességben tegyem.

Oldal portolás tapasztalatai

A tartalom migrálása viszonylag fájdalommentes volt, néhány dologtól eltekintve:

  1. A post path generálásnál ha nem bírálom felül a slug-ot, akkor elég rosszul választ néha a cím alapján, pl. .-ra, vagy akár ... végződő könyvtárneveket is készít valahogy, ami azért gond, mert winapi-n keresztül nem lehet könnyen törölni (persze létrehozni sem szabadna, hogy könnyen menjen).
    Az rmdir /q/s \\?\C:\blog\fura...\index.html módon törölhető azonban, és a front matterben a slug felülbírálásával oldható meg ez a gond.
  2. Úgy általában ott fáj a legjobban a Hugo, ahol a Go kilátszik a felszín alól. Ez azért sok helyen előfordul a portolás során.
    Például a dátumkezelés a Go-ban elég gyenge, így a Hugo-ban is tragikus. A Go nem kezeli a lokalizált dátum stringeket. A dátum mezők megadása elve teljesen autisztikus. Érthető a dolog, de mégis brainfuck, a C/Java/C# %Y kicsit jobb konstans az év mezőre, mint a 2006. Ízlelgessük ezt a dokumentációból:

    “Mon Jan 2 2006”
    Returns: Fri Mar 3 2017

  3. Nincs semmilyen content pipeline, sem buid hook annak integrálására. LESS alapú stíluslapom kézzel kell fordítani a módosítások után.

  4. Néhány dologról alig van dokumentáció. Példa: RSS file nevének testreszabása.

  5. Template nyelvben a logikát fájdalmasan autisztikus RPN módon kell írni.

  6. Nem tudtam case sensitive kimenő path gyártásra rávenni, amiatt törnek egyes korábbi permalinkjeim. A permalink gyártás elég rugalmatlan és korlátozott képességű

  7. Nem lehet a markdown parser AST-ből kimenetet gyártó visitort hookolni. Például képekhez css class hozzáadására. A Go nyelv sajátosságai miatt meg sem próbálták a Hugot pluginezhetővé tenni.

Vannak azonban nagyon jó dolgok is benne:

  1. a Go nyelv sajátosságai miatt egy darab bináris az egész!
  2. Az oldalak deklaratívan bejegyezhetik maguk menübe.
  3. Jövőben láthatóvá váló, idővel lejáró oldalak.
  4. Statikus metaadat tárolása.
  5. Rugalmas menürendszer
  6. Többnyelvű oldalak támogatása
  7. Iszonyat gyors!

Ezekkel a funkciókkal az én blogomnál sokkal összetetteb igényeket is ki tud elégíteni. Akár céges arculati oldal, konferencia, vagy egyéb komolyabb felhasználási esetekkel rendelkező szervezetet is hasznát veheti.

Sebesség

Ki is próbáltam a generátort, és valóban veszett gyorsak bizonyult. Miután a tartalmat és a formát egyaránt portoltam a Hugo árnyalatnyit különböző oldalszerkezetébe végeztem egy rögtönzött mérést. Mérésem szerint a Pretzel alapú oldal 64 másodperc alatt renderelődött le nulláról, hugo segítségével 1,2 másodperc. Ebben a mérésben azonban egyéb toolok is be voltak kapcsolva, melyek a publikálás részét képezik. Azok nélkül a hugo 300ms alatt rendereli a blogot mai formájában, a Pretzel szintúgy egy perc felett. Ez két nagyságrendbéli gyorsulás, úgyhogy megtartom.

Konklúzió

A permalink kezelés eltérései miatt néhány írás permalinkje megváltozott. 😎

Meg kellett tanulnom együttélni a rigolyáival, de úgy döntöttem,hogy megtartom. Így lett tehát ez az oldal az én Hugom.

Anno megvásároltam Steamen a GTA IV-ig bezárólag a teljes GTA sorozatot. A GTA IV-et anno végig is játszottam, és azóta többször le akartam porolni a kiegészítők miatt, de a Games for Windows Live integráció miatt nem tudtam elmenteni az állást, így mindig feladtam. Tegnap kiderítettem, hogy mi a megoldás!

Ez a vacak települ a játékkal, és már nem lehet belépni bele, de szerencsére nem voltak teljesen gonoszok/aljasak a készítői, így van mód “offline” is játszani, mindössze az acsívmönt-öktől (magyarul elérések?) esünk el, illetve a játékállás nem szinkronizálja a gépek közt.

Egyerzű a megoldás: amikor belépést kér a játék a GFWL-be, akkor azt kell választani, hogy új felhasználót regisztrálunk, majd a miennyire jó ez nekem, blabla szöveget le kell görgetni, és a szövegben lesz egy link elrejtve, mellyel helyi felhasználót tudunk létrehozni, és így már lehetségessé válik a játékállás mentése.

Mivel oly trendy és fiatalosch is vagyok, csináltam is erről egy gifet (igaziból mp4) a giphyn!

A hétvégén régről félretett papírokat szortíroztam, hogy amit lehet kidobjak. Így került a kezembe egy három évvel ezelőtti bizonylat egy gyorsétteremből, ahol kártyával fizettem. Azt vettem észre, hogy a megszokottól eltérően nem csak az utolsó négy számjegy volt meghagyva a kártyaszámból, és a többi kicsillagozva, hanem mindössze 6 számjegy volt kicsillagozva a 16-ból. Mostanság a BKK jegy és bérletautomata is adott ilyen bizonylatot Ekkor kissé elgondolkoztam, hogy vajon hány kombináció lehetséges, és ez kellő biztonságot ad-e a vásárlónak, ha valaki illetéktelen jut a bizonylathoz?

Hogy őszinte legyek, mivel inkább vagyok programozó mint matematikus, ezért az információ gyűjtés után egyből kódolni kezdtem, anélkül, hogy jobban végig gondoltam volna a használt algoritmus tulajdonságait, amiből idejekorán levonhattam volna a végkövetkeztetést.

A bankkártyaszámokról

A bankkártya számok 8-19 számjegy hosszú számsorok, melyek eleje a kibocsájtó intézetet azonosítja, amit egy számlaszám követ, és egy előremutató hibajavító kódolást tartalmazó szám zár. Itthon általában 16 számjegyű VISA vagy MasterCard kártyaszámokkal találkozni.

A bankkártyaszámokban általában a Luhn algoritmust használják előremutató hibajavító kódként (Forward Error Correction), ami egy digit információvesztést (ismerve annak helyét) még helyre tud állítani. Itt kellett volna, hogy rájöjjek a válaszra, ám én itt azonnal kódolni kezdtem…

A megvalósításomhoz először teszt adatokra volt szükségem. Egy gyors kereséssel találtam minta kártyaszámokat, amikkel neki is állhattam a teszt kód megírásának:

using Xunit;

namespace CreditCardNumberGuesser.Tests {
    public class CreditCardNumberTest {
        [Theory]
        [InlineData("5555555555554444")]
        [InlineData("5105-1051-0510-5100")]
        public void CorrectNumersAreValid(string cardNumber) {
            CreditCardNumber cn = new CreditCardNumber(cardNumber);
            Assert.True(cn.IsValid());
        }

        [Theory]
        [InlineData("5555555555554443")]
        [InlineData("5105-1050-0510-5100")]
        public void IncorrectNumersAreInvalid(string cardNumber) {
            CreditCardNumber cn = new CreditCardNumber(cardNumber);
            Assert.False(cn.IsValid());
        }
    }
}

A kártyaszám osztály, benne a Luhn algoritmust szinte szavanként kódba ültető IsValid metódussal:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CreditCardNumberGuesser {
    static class CreditCardNumberHelper {
        public static int[] ToDigitArray(this string number) {
            var digits = from n in number
                         .Trim()
                         .Replace("-", "")
                         .Replace(" ", "")
                         select int.Parse(n.ToString());
            return digits.ToArray();
        }
    }

    class CreditCardNumber {
        private int[] digits;
        private Lazy<string> str;

        public CreditCardNumber(string number) : this(number.ToDigitArray()) { }

        public CreditCardNumber(int[] digits) {
            if (!digits.All(n => 0 <= n && n <= 9)) {
                throw new ArgumentOutOfRangeException("Each card digit must be between 0..9 inclusive!");
            }
            this.digits = digits;

            this.str = new Lazy<string>(() => {
                var sb = new StringBuilder(digits.Length + digits.Length / 4);
                for (int i = 0; i < digits.Length; i++) {
                    if (0 < i && i % 4 == 0) {
                        sb.Append("-");
                    }
                    sb.Append(digits[i]);
                }
                return sb.ToString();
            }
           );
        }

        public bool IsValid() {
            var sum = digits.Reverse().Skip(1)
                .Select((n, idx) => {
                    if (idx % 2 == 1) {
                        return n;
                    } else {
                        var dbl = n * 2;
                        if (dbl > 9) {
                            return dbl - 9;
                        } else {
                            return dbl;
                        }

                    }
                }).Sum();

            var checkDigit = (sum * 9).ToString().Last() - '0';

            return checkDigit == digits.Last();
        }

        public override string ToString() {
            return str.Value;
        }
    }
}

Amit itt érdemes megfigyelni, az a LINQ IEnumerable<T>.Select() azon overloadjának hívása, ahol nem csak az elemet, hanem az indexét is átadjuk a projekciós függvénynek. (Ha ez nem lenne, akkor vagy for ciklusra, vagy egy szekvenciával zip-pelt sorozat projekciójára lenne szükség, amik mind rosszabbul olvashatóak szerintem). Ez nekem a kód írásakor újdonság volt.

Ezután lássuk a program eredeti feladatát: valahogy meg kellene számolni, hogy hány érvényes kártyaszámot lehet találni a hiányzó helyeket kitöltve! Ehhez kell egy lista a lehetséges számokról. Ezt az alábbi osztállyal valósítottam meg:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CreditCardNumberGuesser {
    class CreditCardNumberSequenceGenerator : IEnumerable<CreditCardNumber> {
        private int[] pattern;
        string formatString;
        long maxguess;

        public CreditCardNumberSequenceGenerator(string pattern) {
            string normalized = pattern.Trim().Replace("-", "").Replace(" ", "");

            this.pattern = normalized.Select(n => {
                int num;
                if (int.TryParse(n.ToString(), out num)) {
                    return num;
                } else {
                    return -1;
                }
            }).ToArray();

            var missing = this.pattern.Count(n => n == -1);
            maxguess = 1;
            for (int i=0; i<missing; i++) {
                maxguess *= 10;
            }
            formatString = $"D{missing}";
        }

        private CreditCardNumber buildCard(long guess) {
            var guessNums = (from n in guess.ToString(formatString) select int.Parse(n.ToString())).ToArray();
            int j = 0;

            var nums = new int[pattern.Length];
            for(int i =0; i<pattern.Length; i++) {
                if(0 <= pattern[i]) {
                    nums[i] = (int)pattern[i];
                } else {
                    nums[i] = guessNums[j++];
                }
            }

            return new CreditCardNumber(nums);
        }

        public IEnumerator<CreditCardNumber> GetEnumerator() {
            long guess = 0;
            while(guess < maxguess) {
                long g = System.Threading.Interlocked.Increment(ref guess);
                yield return buildCard(g-1);
            }
        }

        IEnumerator IEnumerable.GetEnumerator() {
            return GetEnumerator();
        }
    }
}

Ebben a kódban véleményem szerint egyszerűsége ellenére is van néhány említésre méltó pont.

A hiányzó helyiértékeket a -1 értékkel kódoltam a számjegy tömbben, mivel ez az kitöltött helyiérték értékkészletén kívül esik.

A hiányzó helyiértékekre adott “tipp” aktuális értékét egy long értékkel tárolom. Azért tehetem ezt, mert a kártyaszámok tipikusan 16 jegyűek, de ez a típus Int64.MaxValue = 2^63 -1 -ig tud értékeket ábrázolni, ami 18 hiányzó számjegyig használható (mivel egy 19 jegyű szám a 10-es számrendszerben). Azért ezt a megoldást választottam, mert nem akartam egy szám-tömbben BCD aritmetikát megvalósítani, tesztelni, amikor ez vajmi keveset adna hozzá a kísérletem értékéhez (BigInteger típussal is csinálhattam volna, az pedig nem jutott eszembe). Ráadásképp egy long növelése atomi művelet (tud lenni), az alternatívákkal ellentétben, így csak minimláisan kell a lockolással foglalkoznom, és nulla erőfeszítéssel párhuzamosítható a kód a maximális teljesítmény mellett. Ez a minimális foglalkozás kimerül az Interlocked.Increment -el való inkrementálásban, ami garantáltam atomi.

Kitérő az enumerátorok párhuzamos felhasználására

Egyébként arra jutottam, hogy a lockolással amúgy sem kellett volna foglalkoznom, mivel az IEnumerator<T> contractja eleve nem thread safe. Ez belátható, hisz az IEnumerable<T> így néz ki:

public interface IEnumerable<out T> : IEnumerable {
    IEnumerator<T> GetEnumerator();
}

Az iteráció állapotát tartalmazó IEnumerator<T> pedig:

public interface IEnumerator<out T> : IDisposable, IEnumerator {
    T Current { get; }
}

public interface IEnumerator {
    object Current { get; }
    bool MoveNext();
    void Reset();
}

Az enumerálás nem atomi, kít hívás váltogatásával zajlik: Egy MoveNext() majd egy Current, és így tovább. Mivel nem atomi, így az egész objektumot kell lockolni, hogy egy elem se maradjon ki, vagy szerepeljen kétszer. Ezért a PLINQ és a Parallel.ForEach, kénytelen maga megoldani a lockolást, cachelést az enumerátorból érték vételezésre. Azt viszont feltételezik, mert a párhuzamos feldolgozáshoz szükséges, hogy az enumerátortól visszakapott értékek függetlenek, azaz lehet rajtuk párhuzamosan dolgozni.

Vissza a bankkártyaszámokhoz

A tippelt érték számjegyekké alakítását egy 0-val balról paddelt formázott nyomtatással oldottam meg. Lehet fintorogni, de ez egy letesztelt, helyes algoritmus, amit készen szállít a BCL. Nem akartam tesztet és kódot írni egy ilyen a célom szempontjából mellékes dologra. A nullával paddeléshez a megfelelő format stringet a C# 6.0 String Interpolation funkciójával oldottam meg. Ez egy kényelmes funkció, legalábbis némi groovyzás után jó, hogy itt is tudom használni.

Rakjuk össze

Ezekből a komponensekből összeállítható végre a feladat megoldása:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CreditCardNumberGuesser {
    class Program {
        static void Main(string[] args) {
            var gen = new CreditCardNumberSequenceGenerator("5334 12** **** 1234");

            var sw = new System.Diagnostics.Stopwatch();
            sw.Start();
            var valids = gen.AsParallel().Where(ccn => ccn.IsValid()).OrderBy(ccn => ccn.ToString()).ToArray();
            sw.Stop();

            foreach( var ccn in valids) {
                Console.WriteLine(ccn);
            }

            Console.WriteLine();
            Console.WriteLine($"That is a total {valids.Length} valid credit card numbers");
            Console.WriteLine($"Took {sw.Elapsed} to calculate");

            Console.Write("Press any key to exit...");
            Console.ReadKey();
        }
    }
}

Eredmény

A program futása tanulságos: Ha X a hiányzó számjegyek száma, akkor 10^(X-1) érvényes kombináció adódik. Ez logikus is, hisz ha 1 számjegy hiányzik, azt a hibajavító kódból helyre lehet állítani. Ha 1-nél több hiányzik, akkor tetszőleges számot választva visszavezetjük az egyel kevesebb hiányzó számjegyes esetre, míg az utolsó esetben már adott, hogy mi az egyetlen lehetséges hiányzó elem. Ez programozás nélkül is leeshetett volna. 😞

A teljes kód (angol kommentekkel) letölthető lehetne, ha nem lennék lusta összecsomagolni. Ollózd ezt össze, ha használni akarod 😎.

És ha már bank és kártya: a Payday 2-ben sikerült hírhedtté válnom: Megvan a Joker kártyám! A sok bankrablás meghozta az eredményét! Igaz, azóta rá is untam a játékra, a fejlesztők ntipatikus üzletpolitikája miatt: Újabb DLC-khez meg kellene újra vennem egy csomagban azokat, amiket már megvettem. Ez is tiszta bankrablás 😠!