Posts tagged ‘C/C++’

Funkcja join()

2011-04-29 21:24

Będąc w zgodzie z podzielanym przez siebie poglądem o kluczowej a często niedocenianej roli “małych” algorytmów, dzisiaj wezmę pod lupę funkcję do łączenia napisów, znaną większości jako join. Ta przydatna operacja występuje w wielu językach i bibliotekach, a jej brak w pozostałych jest zwykle wyraźnie odczuwalny (tak, Java, o tobie mówię). Dobrze użyty join – zwłaszcza w połączeniu z pewnymi specyficznymi mechanizmami językowi – potrafi zapobiec pisaniu niepotrzebnych pętli, znacząco redukując code bloat.

Ale po kolei. join to operacja polegająca na złączeniu kolekcji napisów w jeden, odpowiednio sklejony łańcuch. Łączenie polega na tym, że w wyniku pomiędzy elementami kolekcji wstawiony jest pewien określony ciąg (“klej”). Najlepiej widać to na przykładzie:

  1. array = ["Ala", "ma", "kota"]
  2. text = str.join(" ", array)
  3. assert text == "Ala ma kota"

Łatwo zauważyć, że join jest w gruncie rzeczy przeciwieństwem funkcji split, którą nieprzypadkowo kiedyś już opisywałem :)

W czym przejawia się przydatność tej operacji? Przede wszystkim rozwiązuje ona “problem ostatniego przecinka” przy wypisywaniu list. Tradycyjnie obchodzi się go mniej więcej tak:
for (int i = 0; i < (int)strings.length(); ++i) { std::cout << strings[i]; if (i + 1 < (int)strings.length()) std::cout << ", "; }[/cpp] Instrukcja if w tej pętli nie jest oczywiście szczytem elegancji. Gdybyśmy mieli tu funkcję join wszystko byłoby o wiele czytelniejsze:
std::cout << join(", ", strings);[/cpp] Drugą zaletą joina jest jego dobra współpraca z modnymi ostatnio, funkcyjnymi rozszerzeniami wielu języków, pozwalająca w zwięzły sposób przetwarzać kolekcje obiektów. Jeśli na przykład mamy słownik (tudzież mapę/hash), to zamiana go na tekstowy odpowiednik klucz=wartość jest prosta:

  1. import os
  2. def join_dict(d):
  3.      # os.linesep to separator wierszy właściwy dla systemu
  4.     return str.join(os.linesep, map(lambda item: "%s=%s" % item, d.items()))
  5.  
  6. data = { "fullscreen": 1, "width": 800, "height": 600 }
  7. print join_dict(data)
  8. # fullscreen=1
  9. # width=800
  10. # height=600

Oczywiście jest tak wówczas, gdy na widok słowa kluczowego lambda nie uciekamy z krzykiem ;-)

Na koniec tej krótkiej pogadanki wypadałoby jeszcze zaprezentować przykładową implementację omawianej funkcji. Ponieważ – jak napomknąłem wcześniej – doskwierał mi ostatnio jej brak w Javie, więc kod będzie w tym właśnie języku:

  1. public static String join(final Collection<?> s, final String delimiter) {
  2.  
  3.     final StringBuilder builder = new StringBuilder();
  4.     final Iterator<?> iter = s.iterator();
  5.     while (iter.hasNext()) {
  6.         builder.append(iter.next());
  7.         if (!iter.hasNext()) break;
  8.         builder.append(delimiter);
  9.     }
  10.     return builder.toString();
  11. }

Z dokładnością do szczegółów generycznych kolekcji i operacji na stringach, powyższą implementację powinno się dać łatwo przetłumaczyć także na C++.

Tags: , , , ,
Author: Xion, posted under Programming » 7 comments

Importy niezupełnie z zagranicy

2011-03-17 23:55

Przeglądając plik źródłowy programu w dowolnym niemal języku, gdzieś bardzo blisko początku znajdziemy zawsze region z importami. Niekoniecznie będą one oznaczone słowem kluczowym import – czasem to będzie using, być może do spółki z #include – ale zawsze będą robiły zasadniczo to samo. Chodzi o poinformowanie kompilatora lub interpretera, że w tym pliku z kodem używamy takich-a-takich funkcji/klas/itp. z takich-a-takich modułów/pakietów. Dzięki temu “obce” nazwy użyte w dalszej części będą mogły być połączone z symbolami zdefiniowanymi gdzie indziej.

Każdy import w jakiś sposób rozszerza więc przestrzeń nazw danego modułu i zazwyczaj wszystko jest w porządku, dopóki dokładnie wiemy, jak to robi. Dlatego też powszechnie niezalecane są “dzikie” importy (wild imports), które nie wyliczają jawnie wszystkich dodawanych nazw, zwykle ukrywając je za gwiazdką (*). Ale nawet jeśli ich nie używamy, to nie oznacza to, że żadne problemy z importowanymi nazwami nas nie spotkają. Oto kilka innych potencjalnych źródeł kłopotów:

  • Importowanie nazwy, która jest identyczna z jednym z symboli zdefiniowanych w tym samym pakiecie. Import będzie wtedy ją przesłaniał – ale oczywiście tylko wtedy, jeśli go rzeczywiście dodamy. A to nie jest takie pewne zwłaszcza w języku interpretowanym, gdzie nawet symbol o zupełnie innej semantyce może łatwo przejść niezauważony aż do momentu uruchomienia kodu z jego błędnym wystąpieniem. Możemy więc nieświadomie używać nazwy lokalnej tam, gdzie należałoby raczej zaimportować zewnętrzną – lub odwrotnie.
  • Zaimportowane nazwy mogą być kwalifikowane lub nie, zależnie od języka i/lub sposobu importowania. I tak chociażby fraza import foo.bar.baz; wprowadza do przestrzeni modułu nazwę baz (czyli niekwalifikowaną) w przypadku Javy. W przypadku Pythona ten sam efekt wymaga z kolei instrukcji from foo.bar import baz, a zwykła instrukcja import da nam jedynie kwalifikowaną nazwę foo.bar.baz – która z kolei w Javie i C# jest dostępna bez żadnych importów, a w C++ po dodaniu dyrektywy #include… Całkiem intuicyjne, czyż nie? ;-) Skoro tak, to dodajmy do tego jeszcze fakt, iż…
  • Nazwy importowane można w większości języków aliasować. Oznacza to, że wynikowa nazwa wprowadzona do przestrzeni może być zupełnie inna niż ta, która jest zdefiniowana w źródłowym module. Aliasowania używa się najczęściej do skracania prefiksów nazw kwalifikowanych i choć ułatwiają one ich wpisywanie, to w rezultacie ogólna czytelność kodu może się pogorszyć.
  • W wielu językach importy mają zasięg. Przekłada się on potem na zasięg zaimportowanych nazw, którego ograniczenie jest często dobrym pomysłem. Sam import występujący poza początkiem pliku może jednak mieć niepożądane efekty – jak choćby załadowanie do pamięci importowanego modułu dopiero w momencie jego pierwszego użycia, co może wiązać się z potencjalnie dużym, chwilowym spadkiem wydajności.
  • Jeśli nie jesteśmy uważni, możemy rozpropagować zaimportowane nazwy do kolejnych modułów, które bynajmniej wcale z nich nie korzystają. Chyba najbardziej znanym przykładem jest wstawianie deklaracji using w plikach nagłówkowych C++, ale to nie jedyny przypadek i nie jedyny język, w którym importy z jednego modułu mogą zaśmiecić przestrzeń nazw innego.

Podsumowując, importy – chociaż często zarządzane prawie całkowicie przez IDE – to w sumie dość poważna sprawa i warto zwrócić na nie uwagę przynajmniej od czasu do czasu.

Tags: , , , , , , ,
Author: Xion, posted under Programming » Comments Off on Importy niezupełnie z zagranicy

Najboleśniejszy strzał w stopę

2011-03-13 20:14

Znanych jest mnóstwo kruczków w języku C++, o których trzeba pamiętać, jeśli chcemy efektywnie w nim programować i nie tracić zbyt dużo czasu na odczytywanie niespecjalnie zrozumiałych komunikatów kompilatora czy – gorzej – drapanie się po głowie podczas debugowania naszego programu. O wielu z nich miałem okazję pisać, ale oczywiście ten temat-rzeka daleki jest od wyczerpania :) W jego nurcie wyróżnia się jednak jeden wybitnie złośliwy przypadek, który przez długi czas uważałem aczkolwiek za ciekawostkę, na którą w praktyce raczej nikt się nie natknie…
Rzecz jasna, myliłem się. Okazuje się, że okaz ten jak najbardziej występuje w rzeczywistym świecie. Nie objawia się wprawdzie zbyt często, ale dzięki temu jest jeszcze bardziej podstępny, posiadając tym większą siłę rażenia, gdy w końcu na kogoś trafi. Dobrze więc wiedzieć, co robić, aby się przed nim bronić :] I tym właśnie chciałbym się dzisiaj zająć.

Pewną pomocą jest tutaj fakt, iż opisywany błąd zdaje się zazwyczaj pojawiać w pewnym konkretnym scenariuszu. Jego wystąpienie w owym kontekście jest przy tym tak zaskakujące, że zetknięcie się z nim skutecznie “uodparnia” na wszelkie inne, podobne okoliczności w których problem może wystąpić. Rzeczony scenariusz jest przy tym bardzo typowy: chodzi o wczytanie zawartości pliku (otwartego jako strumień std::ifstream) do kolekcji albo zmiennej, na przykład łańcucha znaków typu std::string.
Są naturalnie tacy, co bawiliby się tutaj w bufor pomocniczy, pętlę i funkcję getline lub coś w tym guście. Programiści lubiący operować na nieco wyższym poziomie wiedzą jednak, że std::string możemy inicjalizować zakresem znaków określonym – jak każdy zakres w C++ – parą iteratorów. Trochę mniej znanym faktem jest z kolei to, że iterować można również po strumieniach. Mamy na przykład coś takiego jak std::istream_iterator, który potrafi automatycznie dekodować ze strumienia egzemplarze ustalonego typu danych:

  1. // odczytanie 10 liczb int z stdio
  2. vector<int> numbers(10);
  3. copy (istream_iterator< int >(cin),
  4.       istream_iterator< int >(), numbers);

Jeśli nam to nie odpowiada i wolimy dostęp do “surowych” bajtów, wtedy z pomocą przychodzi bardziej wewnętrzny std::istreambuf_iterator. To właśnie przy jego pomocy możemy szybko przejść po zawartości strumienia plikowego i umieścić ją w stringu:

  1. ifstream file("plik.txt");
  2. string fileContents(istreambuf_iterator< char >(file),
  3.                     istreambuf_iterator< char >());

Jak pewnie można się domyślić, zakres zdefiniowany przez te iteratory w obu przypadkach zaczyna się na początku strumienia. Kończy się zaś w miejscu, gdzie odczyt następnej porcji danych nie jest już możliwy. W naszym “rozwiązaniu” problemu wczytywania całego pliku będzie to więc koniec tego pliku, czyli to co nam chodzi.

Nieprzypadkowo jednak słowo ‘rozwiązanie’ ująłem w cudzysłów. Powyższe dwie instrukcje zawierają bowiem ów wyjątkowo perfidny błąd, o którym wspominałem na początku. Dość powiedzieć, że jeśli spowoduje on wygenerowanie przez kompilator bardzo tajemniczego komunikatu, będzie to lepszy z jego dwóch możliwych rezultatów. Drugim jest kompilacja zakończona powodzeniem i… kod, który robi dokładnie nic. Nie tylko nic nie wczytuje, ale nawet nie tworzy zmiennej typu string! To raczej zaskakujące, nieprawdaż? ;)

Tags: , , , ,
Author: Xion, posted under Programming » 5 comments

Jedna funkcja w wielu wersjach

2011-03-10 18:12

Wytykanie niedoskonałości i różnego rodzaju felerów języka C++ to jedno z ulubionych zajęć niektórych programistów, zwłaszcza tych którzy z jakichś powodów muszą z tego języka korzystać. Sam czasami to robię, ale dzisiaj chcę zwrócić uwagę na to, co C++ robi dobrze – albo przynajmniej lepiej niż w wielu innych, popularnych językach. A chodzi tu o swobodę w definiowaniu wielu wersji tej samej funkcji, różniących się listą parametrów. Nazywa się to zwykle przeciążaniem, chociaż nie jest to jedyny sposób na osiągnięcie takiego efektu.

Innym są bowiem parametry domyślne i istnieje przynajmniej jeden język, w którym jest to sposób jedyny. Tak jest bowiem w Pythonie i nawet czasami ma to sens, biorąc pod uwagę brak deklarowanych typów w tym języku. Jednak równie często wymaga to czegoś w stylu samodzielnego rozstrzygania przeciążania już wewnątrz funkcji:

  1. def some_func(string_or_list):
  2.     if isinstance(string_or_list, list): x = "".join(string_or_list)
  3.     elif isinstance(string_or_list, basestring): x = string_or_list
  4.  
  5.     # ...

Powyższe ify to nic innego jak boilerplate code: redundantne, powtarzalne fragmenty kodu, które w dodatku robią tutaj to, co w innych językach spoczywa na barkach kompilatora. Usprawiedliwieniem może być pewnie to, że przecież Python nie jest kompilowany ;P

Ale jak wspomniałem wcześniej, język spod znaku węża posiada możliwość definiowania argumentów domyślnych, za co należy mu oddać honor. Niestety nie da się tego powiedzieć o kilku innych, popularnych dziś językach, jak choćby C# i Javie. Jeśli chcemy mieć w nich dwie sygnatury tej samej metody, musimy ją przeciążyć. Otrzymujemy wtedy dwie wersje (lub więcej), z których jedna – ta posiadająca mniej argumentów – jest po prostu wywołaniem drugiej. Widuje się to często w konstruktorach, z których czasem układa się swego rodzaju sztafeta wywołań:

  1. public Foo() { this(0); }
  2. public Foo(int x) { this(x, null); }
  3. public Foo(int x, Object o) { this(x, o, ""); }
  4. public Foo(int x, Object, String s) {
  5.     // właściwy konstruktor
  6. }

Jak łatwo zauważyć, jest ona wybitnie rozwlekła, trudna w utrzymaniu i modyfikacji, a także mało czytelna, gdyż wartości domyślne argumentów są ukryte w wywołaniach zamiast w deklaracjach funkcji. Jej odpowiednik z argumentami domyślnymi byłby natomiast krótki, przejrzysty, zrozumiały i łatwy do ewentualnej zmiany.

“Problem” z tworzeniem wielu wersji funkcji polega właśnie na tym, iż mimo istnienia dwóch sposobów na osiągnięcie tego samego efektu, bardzo często jeden jest wyraźnie lepszy niż drugi. Stąd każdy język nieposiadający wsparcia dla któregoś z nich niejako z automatu kreuje sytuacje, dla których rozwiązania trzeba szukać na około.
I tu pozytywnie wyróżnia się C++, który pozwala na stosowanie obu technik (przeciążania lub argumentów domyślnych) w zależności od potrzeb. Również razem, jeśli rezultat nie powoduje niejednoznaczności w wywołaniach. Z pewnością jest to cenna cecha tego języka, równoważąca znaczącą część jego znanych niedostatków :)

Tags: , , , , ,
Author: Xion, posted under Programming » 4 comments

Formatowanie tekstu

2010-12-12 19:03

Tematem budowania łańcuchów znaków zajmowałem się już kiedyś, badając efektywność tej operacji w C++ dla trzech różnych sposobów. Tym razem przyjrzę się sprawie z perspektywy bardziej niezależnej od języka. Ostatnio poczyniłem bowiem pewną obserwację. Zauważyłem mianowicie, że “tradycyjne” podejście do składania stringów – przez konkatenację kolejnych podciągów – staje się… hmm… niemodne, jeśli można tu użyć takiego określenia.

Ci, którzy kodują nieco dłużej niż ja pewnie zwrócą mi zaraz uwagę, że takie naprawdę tradycyjne podejście do problemu to to, które implementują funkcje z języka C podobne do printf:

  1. fprintf (stderr, "[%s] Error (errno=%d)\n", tag, errno);

Polega ono na uprzednim zdefiniowaniu wzorca tekstu (tzw. formatu, tutaj jest to drugi argument fprintf), zawierającego symbole zastępcze takie jak %s czy %d. Są one następnie zastępowane podanymi wartościami (tutaj: tag oraz errno) w celu wyprodukowania finalnego ciągu. Nie ma tu jawnej operacji łączenia dwóch napisów. Zamiast tego mały parser przegląda podany wzorzec i w miejscu symboli zastępczych wstawia do wyniku podane wartości, przepisując bez zmian resztę znaków.
Wkrótce jednak nadszedł C++ z klasami string i ostream, oferującymi podobne możliwości bez użycia wzorców. Zamiast tego posługiwały się operatorami + i << w celu wyprodukowania tekstowego wyjścia:
cerr << "[" << tag << "] Error (errno=" << errno << ")" << endl;[/cpp] To działa, ale - jak można zobaczyć w porównaniu - wygląda często znacznie mniej przejrzyście. Nie dziwi więc, że w tym względzie historia zdaje się zatoczyć koło. Obecnie bowiem operacja formatowania napisów (w sensie powyższej funkcji fprintf, a nie określania czcionki czy stylu tekstu) jest wbudowana w wiele nowoczesnych, popularnych języków programowania, jak choćby C#, Pythona czy Javę. Najczęściej też, co ciekawe, trzyma się ona z grubsza oryginalnej składni wzorców pochodzącej jeszcze z C, dodając oczywiście jakieś własne, bardziej zaawansowane rozszerzenia:

  1. exc_type, exc, _ = sys.exc_info()
  2. logging.error("[%s] %s" , exc_type.__name__, exc)
  1. Console.WriteLine("[{0}] {1}", e.GetType().Name, e.Message);

Czasami jednak – jak widać wyżej – tak nie jest :) Na obecność mechanizmu formatowania łańcuchów znaków możemy aczkolwiek liczyć w każdym szanującym się języku. I to łącznie z C++, gdzie wszystko prawie wszystko, czego w nim pierwotnie nie ma, zawiera się w jakiejś bibliotece boost – w tym przypadku jest to boost::format.

Tags: , , , ,
Author: Xion, posted under Programming » 5 comments

Obiekt na chwilę

2010-12-02 21:58

Swego rodzaju miejską legendą pewnego forum nt. programowania jest “algorytm” znany pod intrygującą nazwą sortowania przez ListBox. Adept kodowania postawiony przed problemem posortowania listy obiektów postanowił mianowicie, że akceptowalnym rozwiązaniem dla niego jest następująca sekwencja czynności:

  1. Stworzenie obiektu kontrolki typu listbox, czyli typowej przewijalnej listy elementów, która byłaby niewidoczna, niepodłączona pod hierarchię kontrolek w interfejsie lub po prostu miała zerowe rozmiary.
  2. Wypełnienie jej zawartości na napisy odpowiadające elementom do posortowania.
  3. Ustawienie właściwości Sorted tej kontrolki na true, dzięki czemu jej elementy (wcześniej dodane) będą zawsze posortowane.
  4. Pobranie zawartości kontrolki, którą są posortowane elementy.

Genialne, nieprawdaż? :) Zauważmy, że to kreatywne rozwiązanie trudnego problemu wymagało zaledwie jednego dużego i skomplikowanego obiektu, który zasadniczo jest też z zupełnie innej bajki (bo z warstwy GUI) niż kod logiki programu, który to zapewne potrzebował rozwiązania. No ale w końcu programowanie premiuje myślenie wybiegające poza schematy, więc nie ma chyba żadnego powodu, żeby powyższy sposób uznać na niewłaściwy, czyż nie? ;-)

Żarty żartami, ale… No właśnie, w rzeczywistości zastosowane tu “podejście” nie jest wcale tak obce niektórym faktycznym rozwiązaniom rzeczywistych problemów programistycznych. Na wiele pytań odpowiedzią jest bowiem często utworzenie obiektu “tylko na chwilę” – zazwyczaj jedynie dla wywołania jednej jego metody.
Weźmy na przykład pobranie aktualnego zrzutu stosu (stacktrace) w Javie. Prawdopodobnie najmniej kłopotliwą metodą jest utworzenie obiektu wyjątkopodobnego (Throwable) i i wywołanie jego metody getStackTrace:

  1. StackTraceElement[] stacktrace = new Throwable().getStackTrace();

Jest to na tyle nietypowe, że pewnie sporo osób odruchowo otoczyłoby instrukcję konstruującą obiekt nawiasami, chociaż nie jest to składniowo wymagane.
Inny przykład? Dokonanie HTML-owego escape‘owania tekstu w JavaScripcie wspomaganym biblioteką jQuery. W tym celu tworzy się nigdzie nieprzyłączony węzeł DOM, ustawia jego zawartość tekstową i natychmiast ją pobiera:

  1. function htmlEscape(str) {
  2.     return $("<div/>").text(str).html();
  3. ]

I wreszcie rzecz ze znanego i lubianego podwórka C++: konwersja liczb do i z formatu binarnego przy pomocy chwilowych obiektów typu bitset:

  1. const size_t BITS = 8 * sizeof(unsigned long);
  2. unsigned long bin2long(const string& str) { return bitset<BITS>(str).to_ulong(); }
  3. string long2bin(unsigned long n)
  4.     { return bitset<BITS>(n).to_string<char, char_traits<char>, allocator<char> >(); }

Podobnych przykładów dałoby się znaleźć oczywiście znacznie więcej. Fakt ten nie jest zupełnie obojętny dla programistów, jako że przynajmniej w niektórych językach utworzenie obiektu może być operacją dość kosztowną. Nie dotyczy to akurat C++, gdzie możliwe jest alokowanie dowolnych obiektów na stosie, lecz raczej tych platform, gdzie obiekty tworzy się na stercie. Często mają też one stały, dodatkowy narzut na sam fakt swego istnienia, spowodowany odziedziczeniem funkcjonalności po klasie “najbardziej bazowej” (zwanej zwykle Object) – narzut zarówno czasowy, jak i pamięciowy. Dlatego też wielokrotne tworzenie małych i krótko żyjących obiektów może być poważnym uszczerbkiem wydajnościowym. Można jednak liczyć na to, że z biegiem czasu sytuacja pod tym względem będzie się poprawiać – tak jak chociażby w przypadku Javy i wprowadzonych optymalizacji w obsłudze obiektów typu String.

Tags: , , , ,
Author: Xion, posted under Programming » 1 comment

Małe kruczki

2010-11-22 8:31

Przeskakując między wieloma językami programowania, bibliotekami czy frameworkami po pewnym czasie można wyrobić sobie zdolności “uogólniające”, dzięki którym takie zmiany nie są specjalnie kłopotliwe. Jednak nawet jeśli potrafimy płynnie przechodzić od jednego języka do drugiego, to zawsze istnieje szansa, że zapomnimy przy okazji o jakimś szczególe, drobnej rzeczy specyficznej dla konkretnego narzędzia – tytułowym małym kruczku.
A przynajmniej mi się tak nierzadko zdarza. Dlatego tę notkę dedykuję pojedynczym, drobnym a “dziwnym” cechom poszczególnych języków programowania, o których często zdarza mi się zapominać. A są one następujące:

  • W C++ nie dodaje się stałych tekstowych. W dziesiątkach języków programowania (i nie tylko programowania) zapis w stylu "ala" + " ma " + "kota" jest najzupełniej oczywistą konkatenacją trzech napisów. Ale w C++ nie. Ponieważ stałe dosłowne, takie jak teksty w cudzysłowach, są – w przybliżeniu – typu const char*, ich łączenie operatorem + jest interpretowane jako dodawanie dwóch wskaźników. Serio.
    Wniosek: W C++ w takich sytuacjach plus jest zbędny i należy go pominąć "ala" " ma " "kota", o ile oczywiście nie łączymy obiektów klasy string.
  • W Javie łańcuchy znaków porównujemy… inaczej. Otóż Java jest bardzo obiektowa i w związku z tym nawet dla typu java.lang.String nie robi wyjątku i obsługuje go tak, jak każdą inną klasę. A to oznacza, że zwykły operator == działa dla napisów tak samo, jak dla innych obiektów (inaczej niż choćby w C#), tj. porównuje ich referencje w celu sprawdzenia, czy mamy do czynienia z tym samym obiektem. Tym samym, nie zaś takim samym, czyli równym co do wartości – a o to nam zwykle chodzi, gdy pracujemy z napisami.
    Wniosek: W Javie do porównywania napisów korzystamy z metody equals. To pewnie dlatego wciąż nie ma w niej switcha dla napisów ;-)
  • Słowniki w Pythonie domyślnie “iterują się” po kluczach. Żeby oszczędzić nadmiernie rozbudowanych wyjaśnień, rzucę poniższym kawałkiem kodu:
    1. for k, v in dictionary: print k, " => ", v

    który na pierwszy rzut oka wygląda na zupełnie poprawną iterację po zawartości słownika dictionary, w której zmienna k dostaje klucz, a v wartość. Jest on dowodem, że oczami należy rzucać zawsze przynajmniej dwa razy. W istocie bowiem k i v będą kolejno otrzymywały, uwaga, po dwa elementy klucza (potraktowanego jako para). Po prostu konwersja słownik → coś_po_czym_można_iterować daje w Pythonie kolekcję kluczy, a nie par klucz-wartość. Nie pytajcie, dlaczego.
    Wniosek: Albo iterujemy po kluczach i wartość uzyskujemy zwykłym indeksowaniem, albo korzystamy z metody items w celu uzyskania kolekcji par klucz-wartość.

  • W Javascripcie zmienne – nawet te użyte po raz pierwszy w funkcjach – są domyślnie globalne. Nawet nie śmiem dociekać, co jest uzasadnieniem dla tego faktu…
    Wniosek: Pierwsze przypisanie do nowej zmiennej wewnątrz funkcji należy opatrywać słowem kluczowym var.
Tags: , , , ,
Author: Xion, posted under Programming » 7 comments
 


© 2017 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.