Професионална програма
Loading...
SimeonKazandzhiev avatar SimeonKazandzhiev 3 Точки

Plant Discovery от Final Exam

Моля за малко помощ, докарвам логиката но в принтирането се спъвам .

Ако някой има решение на 100/100 моля да сподели .

Ето го и кода ми:

https://pastebin.com/iQqrBpRv

Благодаря предварително

Тагове:
0
Programming Fundamentals
MartinBG avatar MartinBG 3751 Точки

Имаше няколко пропуска по условието, като например да се извежда "error" при невалидна команда, както и да се презаписва rarity, ако растението бъде въведено повече от веднъж в началото.

 

Тази задача би се решила доста по-лесно и прегледно с помощен клас, но ето и оправеното решение с два Map-а :

 

import java.util.*;

public class PlantDiscovery {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        Map<String, Integer> plants = new HashMap<>();
        Map<String, List<Double>> rating = new HashMap<>();

        int n = Integer.parseInt(scanner.nextLine());

        for (int i = 0; i < n; i++) {
            String[] input = scanner.nextLine().split("<->");
            String plant = input[0];
            int rarity = Integer.parseInt(input[1]);
            plants.compute(plant, (k, v) -> rarity);
            rating.putIfAbsent(plant, new ArrayList<>());
        }

        String input;
        while (!"Exhibition".equals(input = scanner.nextLine())) {
            String[] tokens = input.split(": ");
            String command = tokens[0];
            String[] elements = tokens[1].split(" - ");
            String name = elements[0];

            if (!plants.containsKey(name)) {
                System.out.println("error");
                continue;
            }

            switch (command) {
            case "Rate":
                double rate = Double.parseDouble(elements[1]);
                rating.get(name).add(rate);
                break;
            case "Update":
                int rarity = Integer.parseInt(elements[1]);
                plants.compute(name, (k, v) -> rarity);
                break;
            case "Reset":
                rating.get(name).clear();
                break;
            default:
                System.out.println("error");
            }
        }

        System.out.println("Plants for the exhibition:");
        plants.entrySet()
                .stream()
                .sorted(Map.Entry.<String, Integer>comparingByValue()
                        .thenComparingDouble(x -> rating.get(x.getKey()).stream()
                                .mapToDouble(Double::doubleValue)
                                .average().orElse(0.0))
                        .reversed())
                .forEach(e -> System.out.printf("- %s; Rarity: %d; Rating: %.2f%n", e.getKey(), e.getValue(),
                        rating.get(e.getKey()).stream().mapToDouble(Double::doubleValue).average().orElse(0.0)));
    }
}

 

0
SimeonKazandzhiev avatar SimeonKazandzhiev 3 Точки

Благодаря ти много , да знаех че нямах случая за "error", но ме чупеше принтирането и сортирането едновременно на двата мапа,понеже никъде не го бяха показвали и не успях да намеря инфо в интернет.Интересно,досега не бях използвал "compute".Би ли ми обяснил накратко в кои случаи е удобно да се използва.:)

1
MartinBG avatar MartinBG 3751 Точки

@SimeonKazandzhiev

 

Извинявам се за закъснелия отговор.

 

Map#compute(K, BiFunction(K, V)) ни дава възможност едновременно да достъпим и да променим стойността на вече съществуващ ключ. Това е полезно, например, ако искаме да конкатенираме стрингове, както е показано и в документацията:

Attempts to compute a mapping for the specified key and its current mapped value (or null if there is no current mapping). For example, to either create or append a String msg to a value mapping:

 
 map.compute(key, (k, v) -> (v == null) ? msg : v.concat(msg))

 

Друго приложение е, ако искаме да сумираме старата стойност с нова. Например, ако в конкретната задача се искаше при повторно въвеждане на растение новото rarity да се добави към вече съществуващото:

plants.compute(plant, (k, v) -> Objects.requireNonNullElse(v, 0) + rarity);

Забележете, че задължително трябва да се прави проверка за null на старата стойност, преди тя да бъде използвана (ще е null ако растението се въвежда за първи път или ако по някаква причина е имало стойност null). Може да се използва тернарен оператор (примера от официалната документация) или Objects#requireNonNullElse​(T obj, T defaultObj) - и двете правят едно и също в случая.

 

След като изяснихме това, вече сигурно се досещате, че използването на compute в конкретната задача е абсолютно ненужно (и нежелателно) и може да се заменени с Map#put​(K key, V value), което ни дава желаната функционалност, без да събужда въпроси какво прави и защо е използвано (т.е. допринася за четимостта на кода) blush

plants.put(plant, rarity);

 

1
18/11/2020 10:30:02
Georgieva_Nadezhda avatar Georgieva_Nadezhda 28 Точки

Здравей MartinBG,

Можеш ли да напишеш на части разбито по-подробно какво се први в тази част на сортирането без да се ползват готовите методи Map.Entry.<String, Integer>comparingByValue() .thenComparingDouble. Благодаря ти. Разбирам какво е сортирането спрямо уловието, но синтаксиса ми е доста сложен към момента и ако знаеш нещо по-лесно, много ще ми помогнеш :) 

.sorted(Map.Entry.<String, Integer>comparingByValue() .thenComparingDouble(x -> rating.get(x.getKey()).stream() .mapToDouble(Double::doubleValue) .average().orElse(0.0)) .reversed())

1
MartinBG avatar MartinBG 3751 Точки

@Georgieva_Nadezhda

Както писах и по-горе, решението с използване на 2 Map-а не е оптимално, защото кодът е по-труден за четене, поддръжка и модифициране (например, ако трябва да добавим нов параметър към всяко растение или пък да пазим списък с имената на хората, които вече са оценили растението).

Сложното сортиране (като запис и синтаксис) е следствие от горното, защото трябва да се използват и двата мапа:

.sorted(Map.Entry.<String, Integer>
             comparingByValue() // първо сортираме по стойност, т.е rarity
            .thenComparingDouble( // после сортираме по double стойност, която получаваме
                x -> rating.get(x.getKey()).stream() // вземайки рейтингите от другия мап
                     .mapToDouble(Double::doubleValue) // to DoubleStream
                     .average() // Optional<Double> - средна стойност
                     .orElse(0.0)) // връща средната стойност или 0, при празен лист 
             .reversed() // обръща реда на предходната сортировка (по средна стойност)
             )

Решението, при което се използва помощен клас Plant би решило тези проблеми и ще улесни сортировката, защото ще имаме само един Map<String, Plant> plants:

    
    public static void main(String[] args) {

        //.....

        System.out.println("Plants for the exhibition:");
        plants.values()
                .stream()
                .sorted(Comparator
                        .comparing(Plant::getRarity)
                        .thenComparingDouble(Plant::getAverageRating)
                        .reversed())
                .map(Plant::info)
                .forEach(System.out::println);
    }

    private static final class Plant {
        private final String name;
        private int rarity;
        private final List<Double> ratings;

        public Plant(String name, int rarity) {
            this.name = name;
            this.rarity = rarity;
            ratings = new ArrayList<>();
        }

        public void addRating(double rating) {
            ratings.add(rating);
        }

        public void setRarity(int newRarity) {
            rarity = newRarity;
        }

        public void clearRatings() {
            ratings.clear();
        }

        public int getRarity() {
            return rarity;
        }

        public String getName() {
            return name;
        }

        public String info() {
            return String.format(Locale.US, "- %s; Rarity: %d; Rating: %.2f", name, rarity, getAverageRating());
        }

        public double getAverageRating() {
            return ratings.stream().mapToDouble(Double::doubleValue).average().orElse(0.0);
        }
    }

 

2
Georgieva_Nadezhda avatar Georgieva_Nadezhda 28 Точки

Добро утро, @MartinBG, много ти благодаря за насоките, чудех се как е най-добре да се подхожда към такива задачи, за които аз си мисля, че ми трябват 2 мапа, а то всъщност мап с клас е правилния подход. Почвам да пренаписвам няколко задачки сега. Благодаря за разясненията още веднъж. Хубав ден 🙃

1