Skąd wiadomo, ze dane rozwiązanie jest odpowiednie?

0

Bardzo często patrzę na coś zbyt idealistycznie, a później okazuje się, że nie uwzględniłem istotnych parametrów. Skąd wiadomo, ze np. rozwiązanie 1 jest lepsze od rozwiązania 2 ew. 3. Przypuśćmy mamy zadanie:

Napisz kod, który odczytuje sekwencję liczb całkowitych i wyświetla wartość "true", jeśli sekwencja jest uporządkowana (w kolejności rosnącej lub malejącej), w przeciwnym razie "false".
Pamiętaj, że jeśli liczba ma taką samą wartość jak kolejna liczba, nie powoduje to zerwania kolejności.
Sekwencja kończy się na 0. Nie traktuj tej liczby jako części sekwencji. Sekwencja ma zawsze co najmniej jedną liczbę (z wyłączeniem 0).

No i na szybko rozwiązałem to tak(1):

import java.util.Scanner;

class Main {
    public static void main(String[] args) {        
        Scanner scanner = new Scanner(System.in);        
        int num;
        int prev = 0;
        boolean asc = false; 
        boolean dsc = false;
        boolean result = true;        
        while (scanner.hasNextInt()) {
            num = scanner.nextInt();            
            if (num == 0) {
                break;
            } else if (prev > num) {
                if (!asc && !dsc) {
                    dsc = true;
                } else if (asc) {
                    result = false;
                }
            } else if (prev < num && prev != 0) {
                if (!asc && !dsc) {
                    asc = true;
                } else if (dsc) {
                    result = false;
                }
            }
            prev = num;
        }        
        System.out.println(result);
    }
}

Jednak kod wydawal mi sie przydlugi i napisalem to(2):

import java.util.Scanner;

class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int previousNumber = scanner.nextInt();
        boolean isAscending = true;
        boolean isDescending = true;
        while (true) {
            int number = scanner.nextInt();
            if (number == 0) {
                break;
            }
            if (previousNumber > number) {
                isAscending = false;
            } else if (previousNumber < number) {
                isDescending = false;
            }
            previousNumber = number;
        }
        System.out.println(isAscending || isDescending);
    }
}

W strzeciej wersji uzylem Array.List:

import java.util.*;

class Main {
    public static void main(String[] args) {
        // put your code here
        Scanner scanner = new Scanner(System.in);
        List<Integer> list = new ArrayList<>();

        while (true) {
            int num = scanner.nextInt();
            if (num == 0) {
                break;
            }
            list.add(num);
        }
        List<Integer> copyList = new ArrayList<>(list);
        Collections.sort(copyList);
        if (list.equals(copyList)) {
            System.out.println("true");
        } else {
            Collections.reverse(copyList);
            if (list.equals(copyList)) {
                System.out.println("true");
            } else {
                System.out.println("false");
            }
        }
    }
}

Tutaj pytanie, ktore rozwiazanie jest najlepsze/najbardziej odpowiednie? Gdzie jest ta granica udoskanalania kodu. U was w projekach wystarczy, ze dziala? Czy raczej macie z gory ustalone schematy.

UPDATE :D

class Main {
    public static void main(String[] args) {
       var s = new java.util.Scanner(System.in);
       boolean asc= true;
       boolean desc = true;
       for(int b, a=s.nextInt();(b= s.nextInt())!=0;a=b){
           asc &= a<=b;
           desc &= a>=b;
       }
        System.out.println(asc||desc);
    }
}
2

Z różnych punktów widzenia można patrzeć.

  • najkrótsze (ostatnie - ale main() psuje wrażenie)
  • najjaśniejsze (ostatnie i drugie - dla mnie)
  • o najmniejszym zużyciu obiektów GC - nie jest to zagadnieniem w ogóle w jednostrzałowych projektach edukacyjnych. Z takiej perspektywy rozważne użycie dodatkowych struktur danych, jak ArrayList. BTW wersja z listą MSZ nie daje "zwrotu z inwestycji", nie zwiększa jasności.
    Hipotetycznie jest to jedyna metoda, która może oddzielić wczytanie danych od ich przetwarzania, w pewnych warunkach to może być DUŻĄ zaletą. *)
    Czy lista i porównanie listy do posortowanej ... ja bym na to nie wpadł. Przypuszczam, mógłbym użyć listy aby oddzielić input/algorytm, ale przetworzył bym ją pętlą (wyjadacze: streamem)

Ktoś inny za najjaśniejsze by uznał na streamch / lambdach

Więc "to zależy".
Z drobiazgó: na pewno nie main() ale wydzielenie do metody o jasnej nazwie

*) sort ma jedną pułapkę. Liczby np dwa razy powtórzone siedemnaście są nieodróżnialne, ale gdyby to były obiekty, mogłyby się odwrócić w sortowaniu

4

A skąd wiadomo, że potrzebny ci jest ciągnik siodłowy czy może wyścigówka, a może wystarczy rower ? Kontekst!!
Co chcesz osiągnąć minimalną złożoność obliczeniową, pamięciową a może możliwość skalowania ? A może jednak wydajność nie jest istotna a ważniejsza jest czytelność.
W każdym przypadku inne rozwiązanie będzie lepsze musiałbyś określi kryteria na podstawie których byłbyś w stanie uszeregować rozwiązania.

#EDIT
PS> Piotr Przybył ma pełno filmików na temat kontekstów itd.

1

@p_agon:

Bardzo często patrzę na coś zbyt idealistycznie, a później okazuje się, że nie uwzględniłem istotnych parametrów. Skąd wiadomo, ze np. rozwiązanie 1 jest lepsze od rozwiązania 2 ew. 3.

Chyba tutaj na forum widziałem fajny cytat, który od strony filozoficznej oddaje to o co pytasz:

Życie jest jak gra w pokera. Podejmujemy najlepsze możliwe dla nas decyzje w danym miejscu i czasie.

2

Java, a tfu. Nie pchaj się w to, bo zostaniesz frustratem jak Julian :(

Co do tego, który kod jest lepszy:

  • dla ludzi używających jednoliterowych nazw zmiennych i skrótów od skrótów jest specjalne miejsce między Perlem, Javą 5 i Harnasiem
  • pierwszy jest totalnie nieczytelny - ifologia stosowana :(
  • nie ma co iść w skrajności z dzieleniem kodu na funkcje / metody, ALE... dla czytelności rozbiłbym to na takie podstawowe logiczne czynności i te czynności wydzielił. Łatwiej jest interpretować łopatologicznie napisany kod, w którym te czynności są nazwane, niż gdy kod jest masą pętli i ifów wymieszanych w jednym garze ;) nie robisz w HFT, więc możesz sobie pozwolić na takie luksusy. Coś w ten deseń: isAscending(...), readInput(...), isOrdered...
5

Nie wiem o co chodzi ale przepisałem twój czwarty przykład na rekurencyjnego jednolinijkowca z optymalizacją ogonkową. Niestety musiałem użyć Scali bo Java jeszcze tego nie ogarnia

@scala.annotation.tailrec
def rec_(
    asc: Boolean,
    desc: Boolean,
    prev: Int,
    current: Int,
    scanner: java.util.Scanner
): Boolean = if (current == 0) asc || desc
else
  rec_(
    asc && prev <= current,
    desc && prev >= current,
    current,
    scanner.nextInt,
    scanner
  )

def rec(scaner: java.util.Scanner) =
  rec_(true, true, scaner.nextInt, scaner.nextInt, scaner)

Array(
  "1 2 3 4 5 6 7 8 9 0",
  "9 8 7 6 5 4 3 2 1 0",
  "1 1 1 1 1 1 1 1 1 0",
  "1 2 1 2 1 2 1 2 1 0",
  "2 1 2 1 2 1 2 1 2 0"
) map { s =>
  println(s + " " + rec(new java.util.Scanner(new java.io.StringReader(s))))
}
4

@p_agon kwestia kontekstu czasem determinuje jak pisać kod. Jeśli chcesz żyłować cykle procesora bo to jakiś krytyczny element, to piszesz tak zeby było wydajnie. W innym wypadku dużo lepiej pisać kod żeby był czytelny, nawet jeśli jest to jakimś kosztem. W przypadku prawdziwego softu dochodzi też kwestia konfigurowalności oraz możliwych zmian.

  1. Wczytanie danych i sortowanie to zły plan bo raz ze jest bardzo kosztowne obliczeniowo a dwa że potrzebujesz całą sekwencje w pamięci.
  2. Twoje rozwiązanie 2 jest oczywiście błędne, bo nigdzie nie sprawdzasz czy ci się czasem nie zmienił kierunek
  3. Trzecie jest nieczytelne
  4. Żadne z rozwiazań nie bierze pod uwagę potencjalnych innych funkcji określających kolejność! Zakładasz tylko uzycie > oraz < ale co jeśli chcemy użyć innej relacji porządku? Wydaje się może dziwne, ale przecież moglibyśmy tego samego algorytmu użyc nie dla liczb całkowitych a dla obiektów MyBusinessObject albo dla Stringów albo dla jeszcze czegoś innego. Co wtedy? ;)
0

Może tak:

public class AscFourP {
    static final int END_VALUE = 0;
    static final int EQUAL = 0;
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int previous = END_VALUE;
        int current = 0;
        int difference = EQUAL;
        int counter = 0;
        do{
            current = scanner.nextInt();
            counter++;
            if (counter == 1){
                previous = current;
                continue;
            }
            if (current - previous == EQUAL){
                continue;
            }
            if (difference == EQUAL && current - previous != EQUAL){
                difference = current - previous;
                continue;
            }
            if (Math.signum(difference) != Math.signum(current - previous) && current != END_VALUE){
                System.out.println("FALSE");
                return;
            }
            difference = current - previous;
            previous = current;
        } while (current != END_VALUE);
        System.out.println("TRUE");
    }
}

W miejscu różnicy current - previous można wstawić compare i powinno działać dla dowolnych obiektów.

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.