03 lipca 2013

Gwiazdkowa choinka w Scali (z użyciem Range, map i foreach)...również na Scalania.PL

Jeszcze daleko mi do zakończenia lektury książki Scala for the Impatient Cay'a Horstmann'a (jestem dopiero na 13 rozdziale, z 22), a już jestem pewien, że to najlepsza książka o Scali, którą dotąd przeczytałem (nie czytałem ich wiele, więc wszystko przede mną i proszę się nie sugerować rekomendacją).

Książka w sposób doskonały wprowadza w tajniki Scali prowadząc czytelnika za rękę - najpierw słów kilka o problemie, aby przedstawić rozwiązanie przez przykłady. Cała książka nafaszerowana jest przykładami i co rusz można trafić na coraz bardziej ciekawe jednolinijkowce. Książka marzenie!

Dzisiaj zobaczyłem kombinację Range, map i foreach do wyrysowania choinki z gwiazdek.
*
**
***
****
*****
Zanim zadam zadanie, proponuję odświeżenie wiadomości dotyczących programowania funkcyjnego - dzisiaj to Scala, ale w Clojure i F# byłoby baaaardzo podobnie.

Scala udostępnia typ Range, który reprezentuje uporządkowaną serię liczb całkowitych. Korzystamy z metody to do stworzenia obiektu typu Range.
scala> 1 to 5
res0: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3, 4, 5)
Range posiada cechę (trait) IndexedSeq, który oferuje metodę map, która z kolei tworzy strukturę danych typu parametru wejściowego i aplikuje funkcję do każdego jej elementu.
scala> scala> (1 to 5) map (_ * 2)
res1: scala.collection.immutable.IndexedSeq[Int] = Vector(2, 4, 6, 8, 10)
W Scali mamy również operator (metodę) mnożenia dla typu String, który tworzy ciąg powtarzających się znaków o liczności drugiego parametru.
scala> "1" * 5
res2: String = 11111
Ostatnią ważną metodą jest foreach na sekwencji, która wykonuje funkcję z efektem ubocznym, np. wypisanie na ekran (println), dla każdego elementu bez dbania o zwracany wynik.
scala> (1 to 5) foreach { e => println(e.toString * 3) }
111
222
333
444
555
Z tą wiedzą możesz spróbować rozwiązać malutkie zadanko. Zamień "XXX" na właściwe wykonanie funkcji w poniższym jednolinijkowcu, aby wyświetlić choinkę z gwiazdek.
scala> XXX map XXX foreach XXX
*
**
***
****
*****
Jeśli tego typu zadania i sposób nauki języka Scala przystają do Twoich oczekiwań, 9 lipca (wtorek) na MIMUWie organizuję scalania - naukę Scali przez grupowe rozwiązywanie krótkich zadań programistycznych. Gorąco zachęcam do udziału!

7 komentarzy:

  1. Nieco alternatywne podejście (spoiler) przy użyciu strumieni. I podobne rozwiązania w Javie... 8.

    Wreszcie rysowanie "diamentu", wyższy poziom trudności.

    OdpowiedzUsuń
    Odpowiedzi
    1. świetny pomysł ze strumieniami. na to bym nie wpadł :)

      Usuń
  2. Czy to jest dobre rozwiązanie tzn akceptowalne? Jestem początkujący w scali (spoiler)

    OdpowiedzUsuń
  3. Myślę, że akceptowalne :))
    Dla porównania załączam swoje

    OdpowiedzUsuń
  4. Dodam jeszcze swoją propozycję rozwiązania, wydaje mi się, że będzie to najbardziej funkcyjne podejście, bo funkcja z efektem ubocznym jest wywoływana tylko raz:

    def christmasTree(size:Int) = (1 to size).map("*" * _).mkString("\n")
    println(christmasTree(5))

    Wydaje mi się jednak, że gdybym miał pracować nad taką choinką w projekcie komercyjnym, wolałbym, żeby była napisana tak:

    for (i <- 1 to 5) {
    println("*" * i)
    }

    To chyba będzie najłatwiejsze to naprawiania, zmieniania, refaktoryzowania ;)

    OdpowiedzUsuń
    Odpowiedzi
    1. Cześć Marcin,

      Rozwiązanie podoba mi się i właśnie o coś takiego chodziło. Brawo!

      Odnośnie imperatywnej wersji, to jej łatwość "naprawiania, zmieniania, refaktoryzowania" jest jedynie kwestią gustu (piękno jest w oku posiadacza) i trzymania się podejścia (funkcyjne vs imperatywne). Wybieram funkcyjne rozwiązanie, bo zwraca wartość, z którą można *coś* zrobić później w innym miejscu aplikacji - jest po prostu bardziej uniwersalne do ponownego użycia.

      Usuń