Как стать автором
Обновить

Комментарии 20

Вы там упомянули SimpleDateFormat, Date и Calendar — не надо этому учить новичков! Пусть сразу используют java.time.* классы, и спасут и себя и окружающих от головной боли. У старого date/time API очень много подводных камней и неочевидностей, и при наличии JSR-310 нет совершенно никаких причин их использовать, особенно в новом коде.

какой же красивый и аккуратный код :) одно удовольствие читать его. Спасибо за такую работу!
Я, например, вижу поехавшие отступы. Такое бывает, когда у автора табуляции в IDE занимают 4 пробела, а там, куда код вставили, — 8. Хорошая иллюстрация того, почему табуляции в коде использовать нельзя.

После ключевых слов перед круглой скобкой принято ставить пробел, чтобы отличать управляющую конструкцию языка от вызова функции.

После закрывающей круглой скобки перед открывающей фигурной скобкой нужен пробел.

Имя final-поля большими буквами нарушает правила именования. Большими буквами обозначаются константы, значение которых никогда не меняется. А final-поле у каждого экземпляра может иметь различные значения.

Runnable — функциональный интерфейс. Начиная, с 8-й явы его не надо анонимным классом реализовывать. Вместо этого надо лямбду создавать.

В иммутабельном классе Vector поля надо было сделать final.

Вместо compute() для модификации объектов в Map в методе inverse() лучше подойдет computeIfAbsent().

Стримы вообще не представлены никак. А это уже 5 лет как ключевое API.

Вот как выворачивание map выглядит на 10-й яве.
Однопоточный вариант
<K, V> Map<V, Collection<K>> inverseSequential(Map<K, V> map) {
    return map.entrySet().stream().collect(                        
        HashMap::new,
        (result, entry) -> {
            var value = result.computeIfAbsent(entry.getValue(), k -> new ArrayList<>());
            value.add(entry.getKey());
        },
        (a, b) -> { throw new UnsupportedOperationException(); }
    );
}



Многопоточный вариант
<K, V> Map<V, Collection<K>> inverseParallel(Map<K, V> map) {
    return map.entrySet().parallelStream().collect(                        
        HashMap::new,
        (result, entry) -> {
            var value = result.computeIfAbsent(entry.getValue(), k -> new ArrayList<>());
            value.add(entry.getKey());
        },
        (map1, map2) -> {
            map2.entrySet().forEach(
                entry -> {
                    var value = map1.computeIfAbsent(entry.getKey(), k -> new ArrayList<>());
                    value.addAll(entry.getValue());
                }
            );
        }
    );
}



Про устаревшие API уже выше сказали. На мой взгляд, код, использующий устаревшее API, нельзя назвать красивым.

Со стримами и лямбдами многое иначе выглядит.


buildDictionaryWithMap
void buildDictionaryWithMap(CharSequence text) {
    Function<Character, int[]> mkelem = x -> new int[1];
    text.chars()
    // .parallel()
    .map(Character::toLowerCase)
    .map(c -> c == 'ё' ? 'е' : c)
    .filter(c -> c >= 'а' && c <= 'я')
    .collect(
        HashMap<Character, int[]>::new,
        (r, c) -> r.computeIfAbsent((char) c, mkelem)[0]++,
        (a, b) -> b.forEach((k, v) -> a.computeIfAbsent(k, mkelem)[0] += v[0])
    )
    .entrySet().stream().sorted(Map.Entry.comparingByKey())
    .forEach(e -> System.out.printf("%c %d%n", e.getKey(), e.getValue()[0]));
}

buildDictionaryWithoutMap
void buildDictionaryWithoutMap(CharSequence text) {
    var freqs = text.chars()
        // .parallel()
        .map(Character::toLowerCase)
        .map(c -> c == 'ё' ? 'е' : c)
        .filter(c -> c >= 'а' && c <= 'я')
        .collect(
            () -> new int['я' - 'а' + 1],
            (r, c) -> r[c - 'а']++,
            (a, b) -> { for (int i = 0; i < a.length; i++) a[i] += b[i]; }
        );
    for (int i = 0; i < freqs.length; i++) {
        if (freqs[i] > 0) {
            System.out.printf("%c %d%n", (char) (i + 'а'), freqs[i]);
        }
    }
}

inverse
<K, V> Map<V, Collection<K>> inverse(Map<? extends K, ? extends V> map) {
    Function<V, Collection<K>> mkelem = x -> new ArrayList<>();
    return map.entrySet().stream()
        // .parallel()
        .collect(
            HashMap::new,
            (r, e) -> r.computeIfAbsent(e.getValue(), mkelem).add(e.getKey()),
            (a,b) -> b.forEach((k,v) -> a.computeIfAbsent(k,mkelem).addAll(v))
        );
}
Я, например, вижу поехавшие отступы.

Значит, это проблема редактура. Наверно, стоит о таких вещах сообщать через ctrl+enter. Так я хотя бы узнаю о чём именно Вы говорите.

Runnable — функциональный интерфейс. Начиная, с 8-й явы его не надо анонимным классом реализовывать. Вместо этого надо лямбду создавать.

В иммутабельном классе Vector поля надо было сделать final.

Я знаю. Но не стоит забывать, что в курсе есть все-таки некоторая последовательность. Если Вы даёте эту задачу студентам, когда они про лямбды и final же в курсе (первая задача на ООП?), то Вы можете это требовать и в решении тоже их показать.

Стримы вообще не представлены никак. А это уже 5 лет как ключевое API.

Да тут много чего не представлено ещё. Всё будет!

За остальные замечания спасибо!
В решении задачи 2.0 (вектор) ошибка: векторное произведение реализовано как скалярное. Ну и арифметические методы я бы назвал add() и subtract(), поскольку это методы объекта, а не статика.
Реализация некоторых итераторов нарушает контракт метода next() — при отсутствии следующего элемента тот должен выбрасывать NoSuchElementException (а не возвращать null / бросать исключение другого типа).
Точно, плохо скопипастил.
Про NoSuchElementException верно, поправил.
Спасибо.
Спасибо за проделанную работу!) Маленькая просьба: можете спрятать решения под спойлеры?
Блок с рекурсией, первые 2 задачи без рекурсии решаются короче. Кстати в 3.0 стоит добавить в условие что массив отсортирован.

Проверка в строках от а до я не цепляет букву ё.

В 6.1 моё мнение — лучше было использовать паттерн комманда в чистом виде, чтобы не плодить action. Все равно список команд маленький и енама хватило бы какого. Ну и да, имеются вопросы производительности, есть лучше варианты. (Они все деревья, neerc.ifmo.ru/wiki/index.php?title=Персистентный_массив пример).

Но все равно, подборка задач хорошая, думаю пригодится)

Если про добавить задач, то мне кажется, стоит уделить внимание работе с файлами и сетью чуть раньше чем на итоговом занятии. Это бы дало возможность тексты для частотных анализов вводить из файла. И, возможно, базы данных, особенно dao было бы полезно.
Сразу встал в тупик про сортировку пузырьком, может я уже чего то забыл, разве надо каждый раз шагать до конца во внутреннем цикле?
Не нужно. После первой итерации получится, что последний элемент уже сравнивали со всеми остальными, и он стоит на своем месте. Поэтому каждый следующий внутренний цикл должен быть на 1 итерацию короче предыдущего.
20 лет не смотрел на алгоритм, и вот посмотрел, хехе. Там и про простые числа в глаза бросилось, достаточно проверять до квадратного корня делители. Дальше уже не стал смотреть.
достаточно проверять до квадратного корня делители

Про корень это не единственное, что можно здесь оптимизировать. Ещё есть Решето Эратосфена. Но обычно я даю на первом занятии самое простое решение. Вы можете делать иначе, конечно же.

7.8) В методах, принимающих в качестве параметра дженерики, желательно указывать не конкретный тип, а границы (wildcard bounds), чтобы можно было сделать вот так:


<K, V> Map<V, Collection<K>> inverse(Map<? extends K, ? extends V> map) { ... }

Map<String, Integer> map1 = ...

Map<Number, Collection<Object>> map2 = inverse(map1);

P. S. Сам так очень редко делаю, но для методических материалов все должно быть четко.

Точно, спасибо.
Как пример, хорошо еще реализовать алгоритмы поиска, возможно даже с UI на канвасе и для этого хорошо подойдут BFS, Dejkstra, A*.
Нужно еще несколько алгоритмов на стеки и очереди, обход, поиск зацикливанияб сортировки например Merge, Insertion, Selection. Еще полезно дать например обход графа.
А для особо крутых ребят сделать сереализацию POJO модельки в json через рефлекцию.
Возможно сделать так же парсинг страниц html через Jsoup например.
У меня на одном из занятий есть задание «со звездочкой», на реализацию как раз A*. За несколько лет всего один человек сделал. Но если курс достаточно глубокий и продолжительный, то почему бы и нет.

В качестве простой задачи на графы можно предложить реализовать головоломку "волк-коза-капуста". Ее также можно усложнить, если спрашивать у пользователя правила несовместимости условных волков и коз.

Box extends Shape - имхо, такое ООП попахивает плохо

Для вывода на экран массива можно использовать

System.out.println(Arrays.toString(array));

вместо цикла.

Спасибо за такой набор задач, как раз недавно начал изучать джаву и хотел себе список задач для практики.

Зарегистрируйтесь на Хабре , чтобы оставить комментарий

Публикации