Najlepszy Java Developer – Zadanie 3: Stars

Zadanie

Trzecim zadaniem, a zarazem ostatnim z zadań rozruchowych jest rysowanie ustalonych figur przy użyciu gwiazdek. Niby nic trudnego, ale i tak możliwe, że będą małe problemy. Po tym zadaniu ruszymy z czymś ciekawszym. 😉

Zaimplementuj poniższy interfejs (klasa najlepiej gdyby nazywała się Draw ;))

public interface Stars {
    /*
        Rysuje kwadrat o podanym wymiarze n
        np. n = 2, ma zwrócić Stringa:
        **
        **
     */
    String drawSquare(int n);

    /*
        Rysuje prostokąt o wymiarach n na m
        np. n = 2, m = 3 ma zwrócić Stringa:
        **
        **
        **
     */
    String drawRectangle(int n, int m);

    /*
        Rysuje trójkąt równoramienny o wysokości równej n
        np. n = 3 ma zwrócić Stringa
        --*
        -***
        *****
     */
    String drawIsoscelesTriangle(int n);

    /*
        Rysuje diament, gdzie n oznacza wysokość diamentu
        n jest zawsze nieparzysta - w przeciwnym wypadku rzuca wyjątek IllegalArgumentException
        np. n = 5 ma zwrócić Stringa
         --*
         -***
         *****
         -***
         --*
     */
    String drawDiamond(int n);

    /*
        Rysuje trójkąt prostokątny równoramienny, gdzie n to długość ramienia
        np. dla n = 3 ma zwrócić Stringa:
        *
        **
        ***
     */
    String drawRectangleTriangle(int n);
}

W komentarzach znajdziesz informację o tym jak dana figura ma wyglądać. Pamiętaj, aby rzucić wyjątek IllegalArgumentException w metodzie drawDiamond, gdy podana liczba jest parzysta. 😉

Przykład

Poniższy przykład narysuje nam wszystkie figury

public class Main {

    public static void main(String[] args) {
        Draw draw = new Draw();

        System.out.println("Draw Square: n = 3");
        System.out.println(draw.drawSquare(3));

        System.out.println("Draw Rectangle: n = 3, m = 4");
        System.out.println(draw.drawRectangle(3, 4));

        System.out.println("Draw Isosceles Triangle: n = 4");
        System.out.println(draw.drawIsoscelesTriangle(4));

        System.out.println("Draw Diamond: n = 5");
        System.out.println(draw.drawDiamond(5));

        System.out.println("Draw Rectangle Triangle: n = 4");
        System.out.println(draw.drawRectangleTriangle(4))   ;
    }
}

Poniżej output programu:

Draw Square: n = 3
***
***
***

Draw Rectangle: n = 3, m = 4
***
***
***
***

Draw Isosceles Triangle: n = 4
---*
--***
-*****
*******

Draw Diamond: n = 5
--*
-***
*****
-***
--*

Draw Rectangle Triangle: n = 4
*
**
***
****

Tak jak widać, aby było jasne: po lewej stronie figury tam gdzie powinny być puste miejsca umieść myślniki, po prawej stronie figury spacje nie mogą występować. Jest to wersja trochę prostsza, ale musimy ustalić taki schemat, aby było prościej mi testować Wasze rozwiązania.

Punktacja

Przygotowanych jest 6 testów, za każdy zdany test otrzymasz 40expa, dodatkowo otrzymasz 60expa za używanie StringBuilder do łączenia Stringów, zamiast operator + lub +=.

Za to zadanie można otrzymać maksymalnie 240expa + 60expa dodatkowo czyli 300expa.

Porady

  • Staraj się stosować angielskie nazewnictwo nazw zmiennych, metod, klas 
  • Pamiętaj, że Twoja klasa musi implementować interfejs Stars – wymusi to na Tobie zwrócenie Stringa zamiast używanie println, które nie jest zbyt elastycznym rozwiązaniem i trudniejszym do testowania (jak przetestować automatycznie wydrukowany output w konsoli? ;))
  • Sprawdź swoje metody dla najprostszych przypadków, ale również tych skrajnych np. parzysta długość boku lub wysokości.

Zadanie zostało opublikowane 19 listopada 2018 roku, rozwiązania można przesyłać do 22 listopada 2018 roku do godziny 23:59. Zadania wysłane później będą automatycznie usuwane.

Format

Zadanie należy wysłać na email: njd@1024kb.pl z tematem: TWÓJ-NICK_STARS.

W załączniku należy przesłać plik o nazwie TWÓJ-NICK_STARS.java. Nie wysyłaj interfejsu, klasy Main, ani całego projektu. Tylko klasę, która implementuje interfejs Stars. Pliki w załączniku – bez gita i różnych pastebinów. 😉

Rozwiązanie

Zadania okazało się dosyć proste, na 14 przysłanych rozwiązań prawie wszystkie otrzymały 100%. Bardzo się z tego cieszę! 😉

Poniżej zamieszczam moje przykładowe rozwiązanie zadania:

public class Draw implements Stars {
    private final static String STAR = "*";
    private final static String DASH = "-";
    private final static String NEW_LINE = "\n";
    
    @Override
    public String drawSquare(int n) {
        return drawRectangle(n, n);
    }

    @Override
    public String drawRectangle(int n, int m) {
        final StringBuilder stringBuilder = new StringBuilder();

        for (int i=0;i<m;i++) {
            for (int j=0;j<n;j++) {
                stringBuilder.append(STAR);
            }
            stringBuilder.append(NEW_LINE);
        }

        return stringBuilder.toString();
    }

    private boolean isEvenNumber(int n) {
        return n % 2 == 0;
    }

    @Override
    public String drawIsoscelesTriangle(int n) {
        final StringBuilder stringBuilder = new StringBuilder();


        int amountStarsToDrawInRow = 1;
        for (int i=n-1;i>=0;i--) {

            for(int j=0;j<i;j++) {
                stringBuilder.append(DASH);
            }

            for(int j=0;j<amountStarsToDrawInRow;j++) {
                stringBuilder.append(STAR);
            }

            amountStarsToDrawInRow += 2;
            stringBuilder.append(NEW_LINE);
        }
        return stringBuilder.toString();
    }

    @Override
    public String drawDiamond(int n) {
        if (isEvenNumber(n)) {
            throw new IllegalArgumentException();
        }

        final String isoscelesTriangle = drawIsoscelesTriangle(n/2+1);
        final StringBuilder stringBuilder = new StringBuilder(isoscelesTriangle);

        int amountStarsToDrawInRow = n - 2;
        final int heightOfDownTriangle = n / 2;

        for (int i=0;i<heightOfDownTriangle;i++) {

            for(int j=0;j<=i;j++) {
                stringBuilder.append(DASH);
            }

            for(int j=0;j<amountStarsToDrawInRow;j++) {
                stringBuilder.append(STAR);
            }

            amountStarsToDrawInRow -= 2;
            stringBuilder.append(NEW_LINE);
        }


        return stringBuilder.toString();
    }

    @Override
    public String drawRectangleTriangle(int n) {
        final StringBuilder stringBuilder = new StringBuilder();

        for(int i=0;i<n;i++) {
            for(int j=0; j<=i;j++) {
                stringBuilder.append(STAR);
            }
            stringBuilder.append(NEW_LINE);
        }

        return stringBuilder.toString();
    }
}

Gdybyś miał jakiekolwiek pytania to możesz je kierować tutaj w komentarzu, do mnie na maila njd@1024kb.pl lub na grupie Facebookowej.

Zadanie było testowane przeze mnie testami jakie możecie znaleźć poniżej. W metodzie setup byłem czasami zmuszony dodawać newline na koniec figury. Niestety na początku zapomniałem o tym powiedzieć i dlatego niektórzy nie mieli na końcu figury newline, inni zaś mieli. Uznawałem obie odpowiedzi jako prawidłowe. 😉

import org.junit.*;
import org.junit.rules.ExpectedException;
import pl.maniaq.Draw;
import pl.maniaq.Stars;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;

public class DrawTest {

    private final Stars stars = new Draw();
    @Rule
    public ExpectedException thrown = ExpectedException.none();

    private static List<String> values = new LinkedList<>();
    private static final String SQUARE = "****\n****\n****\n****";
    private static final String RECTANGLE = "***\n***\n***\n***";
    private static final String ISOSCELES_TRIANGLE =
            "---*\n" +
            "--***\n" +
            "-*****\n" +
            "*******";
    private static final String DIAMOND =
            "---*\n" +
            "--***\n" +
            "-*****\n" +
            "*******\n" +
            "-*****\n" +
            "--***\n" +
            "---*";
    private static final String RECTANGLE_TRIANGLE =
            "*\n" +
            "**\n" +
            "***\n" +
            "****\n" +
            "*****\n" +
            "******";
    private final static boolean WITH_NEW_LINE = true;

    @BeforeClass
    public static void setup() {
        values.addAll(Arrays.asList(SQUARE, RECTANGLE, ISOSCELES_TRIANGLE, DIAMOND, RECTANGLE_TRIANGLE));

        if (WITH_NEW_LINE) {
            values = values.stream().map(value ->value + "\n").collect(Collectors.toList());
        }
    }


    @Test
    public void testDrawSquare() {
        //given
        final int n = 4;

        //is
        final String result = stars.drawSquare(n);

        //expected
        Assert.assertEquals(result, values.get(0));
    }

    @Test
    public void testDrawRectangle() {
        //given
        final int n = 3;
        final int m = 4;

        //is
        final String result = stars.drawRectangle(n, m);

        //expected
        Assert.assertEquals(result, values.get(1));
    }

    @Test
    public void testDrawIsoscelesTriangle() {
        //given
        final int n = 4;

        //is
        final String result = stars.drawIsoscelesTriangle(n);

        //expected
        Assert.assertEquals(result, values.get(2));
    }

    @Test
    public void testDrawDiamond() {
        //given
        final int n = 7;

        //is
        final String result = stars.drawDiamond(n);

        //expected
        Assert.assertEquals(result, values.get(3));
    }

    @Test
    public void testDrawDiamondWithEvenNumber() {
        //given
        final int n = 6;

        //expected
        thrown.expect(IllegalArgumentException.class);

        //when
        stars.drawDiamond(n);

    }

    @Test
    public void testDrawRectangleTriangle() {
        //given
        final int n = 6;

        //is
        final String result = stars.drawRectangleTriangle(n);

        //expected
        Assert.assertEquals(result, values.get(4));
    }



}

Gdybym się gdzieś pomylił to dajcie znać!

Porady

  • W zadaniu często dodawaliśmy *, – i \n – warto wyciągnąć do stałych takie wartości. Dzięki temu w jednym miejscu możemy zmienić wyświetlany znak, zamiast w X miejscach.
  • Nie bez powodu metody w interfejsie zwracają Stringa – dzięki temu nasza klasa może być reużywalna – owego Stringa możemy wyświetlić np. na stronie internetowej lub w okienku aplikacji, a nie tylko ograniczać się do konsoli przy użyciu System.out.print.
  • Wszelakie równania logiczne warto wyciągać do zmiennych lub metod – taka nazwa od razu mówi na czym polega warunek.

Czyli zamiast tak:

if (n % 2 == 0) {

}

Lepiej zapisać to tak:

private boolean isEvenNumber(int n) {
    return n % 2 == 0;
}

private void test(int n) {
   if (isEvenNumber(n)) {
   }
}

Kto to wie, czy taki warunek w obrębie klasy pojawi się dokładnie tylko jeden raz – jeśli nie to możemy korzystać już z gotowej metody. 😉