Архив рубрики: Кодинг

Advent of Code 2022: Day 7

Мои взаимоотношения с этой загадкой можно описать примерно такой фразой (по мотивам анекдотов):

– Дерево – подумал Штирлиц
– Дерево – поняло дерево

Всё началось с того, что мне совершенно не хотелось строить это дерево (забегая вперёд – всё же пришлось).

После пары неудачных попыток (о которых ниже) – решил хранить эту псевдо-ФС в избыточном виде – получились этакие “мангровые заросли”.

Директории, помимо вложенности в свои родительские каталоги, дублируются на корневом уровне. Таким образом получается без лишних усилий посчитать результат по условиям загадки.

record File(String name, long size, List<File> files) {}
static Map<String, File> flattenFs = new HashMap<>();

static void day7(String puzzleInputUri) throws IOException, InterruptedException {
    List<String[]> commands = client.send(
            request.uri((URI.create(puzzleInputUri))).build(), HttpResponse.BodyHandlers.ofLines()
        ).body()
        .skip(1)
        .filter(line -> !line.equals("$ ls"))
        .map(line -> line.replace("$ ", ""))
        .map(line -> line.split(" "))
        .toList();

    String currentDirKey = "/";
    flattenFs.put(currentDirKey, new File("/", 0L, new ArrayList<>()));
    for (var commandLine : commands) {
        String command = commandLine[0];
        String arg = commandLine[1];
        if ("dir".equals(command)) {
            String newDirKey = currentDirKey + "/" + arg;
            flattenFs.putIfAbsent(newDirKey, new File(newDirKey, 0L, new ArrayList<>()));
            flattenFs.get(currentDirKey).files().add(flattenFs.get(newDirKey));
        } else if ("cd".equals(command)) {
            if ("..".equals(arg)) {
                String parentDir = currentDirKey.substring(0, currentDirKey.lastIndexOf("/"));
                currentDirKey = flattenFs.keySet().stream()
                    .filter(dir -> dir.equals(parentDir))
                    .findAny().orElseThrow();
            } else {
                currentDirKey += "/" + arg;
            }
        } else {
            flattenFs.get(currentDirKey).files().add(new File(arg, Long.parseLong(command), new ArrayList<>()));
        }
    }

    long resultPartOne = flattenFs.values().stream()
        .mapToLong(dir -> getTotalSize(dir, 0L))
        .filter(size -> size <= 100_000L)
        .sum();
    System.out.println(resultPartOne);

    long usedSpace = getTotalSize(flattenFs.get("/"), 0L);
    long freeSpace = 70_000_000L - usedSpace;
    long needForUpdateSpace = 30_000_000L - freeSpace;
    long resultPartTwo = flattenFs.values().stream()
        .mapToLong(dir -> getTotalSize(dir, 0L))
        .filter(size -> size >= needForUpdateSpace)
        .min()
        .orElseThrow();
    System.out.println(resultPartTwo);
}

static long getTotalSize(File dir, long totalSize) {
    List<File> files = dir.files();
    for (File file : files) {
        totalSize += file.size() > 0L
            ? file.size()
            : getTotalSize(flattenFs.get(file.name()), 0L);
    }
    return totalSize;
}

Этот пример запускал на Java 17, на традиционно используемой в прошлых задачах одиннадцатой версии Java он не заработает.

Исходные данные: https://adventofcode.com/2022/day/7/input
Читать далее Advent of Code 2022: Day 7

Advent of Code 2022: Day 6

Решение для шестого дня получилось коротким, простым, универсальным (применительно к загадке).

И, я уверен, – глубоко неоптимальным при этом :). Зато – алгоритмически это было, пожалуй, проще загадок всех предыдущих дней.

Но оно сработало, что полностью соответствует духу викторины. Для первой части загадки markerSize = 4, для части два – markerSize = 14.

static void day6(String puzzleInputUri) throws IOException, InterruptedException {
    int markerSize = 14;
    Set<Byte> buffer = new HashSet<>();
    byte[] message = client.send(request.uri((URI.create(puzzleInputUri))).build(), HttpResponse.BodyHandlers.ofByteArray()).body();
    for (int i = 0; i < message.length - markerSize; i++) {
        for (int j = i; j < i + markerSize; j++) {
            buffer.add(message[j]);
        }
        if (buffer.size() == markerSize) {
            System.out.println(i + markerSize);
            break;
        }
        buffer.clear();
    }
}
Исходные данные: https://adventofcode.com/2022/day/6/input

Advent of Code 2022: Day 5

Добрался до пятого дня AoC. Вообще, по ощущениям, сами алгоритмы пока становятся проще от задачи к задаче. А вот пред-обработка исходных данных начинает требовать времени.

Начальное состояние склада просто захардкодил, слегка покрутив в Calc (для бо́льшего удобства копипаста). В конце заметки приведу некоторые подробности.

Поскольку мутируются стеки ящиков “на месте” – для корректного решения второй части приходится использовать копию начального состояния склада.

static void day5(String puzzleInputUri) throws IOException, InterruptedException {
/*
                [V]     [C]     [M]
[V]     [J]     [N]     [H]     [V]
[R] [F] [N]     [W]     [Z]     [N]
[H] [R] [D]     [Q] [M] [L]     [B]
[B] [C] [H] [V] [R] [C] [G]     [R]
[G] [G] [F] [S] [D] [H] [B] [R] [S]
[D] [N] [S] [D] [H] [G] [J] [J] [G]
[W] [J] [L] [J] [S] [P] [F] [S] [L]
 1   2   3   4   5   6   7   8   9
*/
    Stack<String> stack1 = new Stack<>();
    stack1.addAll(List.of("W","D","G","B","H","R","V"));
    Stack<String> stack2 = new Stack<>();
    stack2.addAll(List.of("J","N","G","C","R","F"));
    Stack<String> stack3 = new Stack<>();
    stack3.addAll(List.of("L","S","F","H","D","N","J"));
    Stack<String> stack4 = new Stack<>();
    stack4.addAll(List.of("J","D","S","V"));
    Stack<String> stack5 = new Stack<>();
    stack5.addAll(List.of("V","S","H","D","R","Q","W","N","V"));
    Stack<String> stack6 = new Stack<>();
    stack6.addAll(List.of("P","G","H","C","M"));
    Stack<String> stack7 = new Stack<>();
    stack7.addAll(List.of("C","F","J","B","G","L","Z","H","C"));
    Stack<String> stack8 = new Stack<>();
    stack8.addAll(List.of("S","J","R"));
    Stack<String> stack9 = new Stack<>();
    stack9.addAll(List.of("M","L","G","S","R","B","N","V","M"));

    Map<Integer, Stack<String>> wareHousePartOne = new TreeMap<>(Map.of(
        1, stack1, 2, stack2, 3, stack3, 4, stack4,
        5, stack5, 6, stack6, 7, stack7, 8, stack8,
        9, stack9
    ));

    Map<Integer, Stack<String>> wareHousePartTwo = new TreeMap<>();
    wareHousePartOne.forEach((stackN, stack) -> {
        var wh2Stack = new Stack<String>();
        wh2Stack.addAll(stack);
        wareHousePartTwo.put(stackN, wh2Stack);
    });

    client.send(request.uri((URI.create(puzzleInputUri))).build(), HttpResponse.BodyHandlers.ofLines()).body()
        .dropWhile(s -> !s.contains("move"))
        .map(s -> s.split(" "))
        .forEach(action -> {
            int count = Integer.parseInt(action[1]);
            int fromStack = Integer.parseInt(action[3]);
            int toStack = Integer.parseInt(action[5]);
            for (int i = 0; i < count; i++) {
                String crate = wareHousePartOne.get(fromStack).pop();
                wareHousePartOne.get(toStack).push(crate);
            }
        });
    StringBuilder upperCrates = new StringBuilder();
    wareHousePartOne.values().forEach(stack -> upperCrates.append(stack.pop()));
    System.out.println(upperCrates);

    client.send(request.uri((URI.create(puzzleInputUri))).build(), HttpResponse.BodyHandlers.ofLines()).body()
        .dropWhile(s -> !s.contains("move"))
        .map(s -> s.split(" "))
        .forEach(action -> {
            int count = Integer.parseInt(action[1]);
            int fromStack = Integer.parseInt(action[3]);
            int toStack = Integer.parseInt(action[5]);
            List<String> cratesBuffer = new ArrayList<>();
            for (int i = 0; i < count; i++) {
                cratesBuffer.add(wareHousePartTwo.get(fromStack).pop());
            }
            Collections.reverse(cratesBuffer);
            cratesBuffer.forEach(crate -> wareHousePartTwo.get(toStack).push(crate));
        });
    StringBuilder upperCratesPartTwo = new StringBuilder();
    wareHousePartTwo.values().forEach(stack -> upperCratesPartTwo.append(stack.pop()));
    System.out.println(upperCratesPartTwo);
}

Либо – просто между вызовами для решения разных частей – поменять внутрянку перестановки ящиков:

List<String> cratesBuffer = new ArrayList<>();
for (int i = 0; i < count; i++) {
    cratesBuffer.add(wareHouse.get(fromStack).pop());
}
Collections.reverse(cratesBuffer);
cratesBuffer.forEach(crate -> wareHouse.get(toStack).push(crate));
Исходные данные: https://adventofcode.com/2022/day/5/input
Читать далее Advent of Code 2022: Day 5

Advent of Code 2022: Day 4

Задачка, предложенная на четвёртом дне, снова показалась проще предыдущей. Если так пойдёт и дальше – можно успеть нагнать календарь и начать двигаться размеренно, по штуке в день.

Получилось так, что решается она практически целиком – копипастом решения первой половины во вторую (за исключением последнего .map. На циклах можно и не копипастить, пожалуй, но уже сделано так.

Главное – есть верный ответ!

static void day4(String puzzleInputUri) throws IOException, InterruptedException {
    var resultPartOne = client.send(request.uri((URI.create(puzzleInputUri))).build(), HttpResponse.BodyHandlers.ofLines()).body()
        .map(pair -> pair.split(","))
        .map(tasks -> {
            String[] taskOneBounds = tasks[0].split("-");
            String[] taskTwoBounds = tasks[1].split("-");
            return Map.entry(
                Map.entry(Integer.parseInt(taskOneBounds[0]), Integer.parseInt(taskOneBounds[1])),
                Map.entry(Integer.parseInt(taskTwoBounds[0]), Integer.parseInt(taskTwoBounds[1]))
            );
        })
        .map(tasks -> Map.entry(
            IntStream.rangeClosed(tasks.getKey().getKey(), tasks.getKey().getValue()).boxed().collect(Collectors.toSet()),
            IntStream.rangeClosed(tasks.getValue().getKey(), tasks.getValue().getValue()).boxed().collect(Collectors.toSet())
        ))
        .map(tasks -> {
            var task1 = new HashSet<>(tasks.getKey());
            var task2 = new HashSet<>(tasks.getValue());
            task1.removeAll(task2);
            tasks.getValue().removeAll(tasks.getKey());
            return task1.isEmpty() || tasks.getValue().isEmpty();
        })
        .filter(included -> included)
        .count();
    System.out.println(resultPartOne);

    var resultPartTwo = client.send(request.uri((URI.create(puzzleInputUri))).build(), HttpResponse.BodyHandlers.ofLines()).body()
        .map(pair -> pair.split(","))
        .map(tasks -> {
            String[] taskOneBounds = tasks[0].split("-");
            String[] taskTwoBounds = tasks[1].split("-");
            return Map.entry(
                Map.entry(Integer.parseInt(taskOneBounds[0]), Integer.parseInt(taskOneBounds[1])),
                Map.entry(Integer.parseInt(taskTwoBounds[0]), Integer.parseInt(taskTwoBounds[1]))
            );
        })
        .map(tasks -> Map.entry(
            IntStream.rangeClosed(tasks.getKey().getKey(), tasks.getKey().getValue()).boxed().collect(Collectors.toSet()),
            IntStream.rangeClosed(tasks.getValue().getKey(), tasks.getValue().getValue()).boxed().collect(Collectors.toSet())
        ))
        .map(tasks -> tasks.getKey().stream().anyMatch(taskN -> tasks.getValue().contains(taskN))
            || tasks.getValue().stream().anyMatch(taskN -> tasks.getKey().contains(taskN))
        )
        .filter(cross -> cross)
        .count();
    System.out.println(resultPartTwo);
}
Исходные данные: https://adventofcode.com/2022/day/4/input

Advent of Code 2022: Day 3

Первая часть задачки из третьего дня оказалась неожиданно простой в решении. По ощущениям – проще заданий дня второго.

А вот во второй пришлось вернуться к корням 🙂 Через стрим получался этакий монстроузорный коллектор для группировки по три строки, что ну его на фиг. Через циклы тоже не конфетка, но тут этого и не нужно. Ответ – есть!

static void day3(String puzzleInputUri) throws IOException, InterruptedException {
    int lowerCaseOffset = 96;
    int upperCaseOffset = 38;

    var resultPartOne = client.send(request.uri((URI.create(puzzleInputUri))).build(), HttpResponse.BodyHandlers.ofLines())
        .body()
        .map(backpack -> Map.entry(
            Arrays.stream(backpack.substring(0, backpack.length() / 2)
                .split("")).collect(Collectors.toSet()),
            Arrays.stream(backpack.substring(backpack.length() / 2)
                .split("")).collect(Collectors.toSet())
        ))
        .map(pockets -> {
            pockets.getKey().retainAll(pockets.getValue());
            return pockets.getKey();
        })
        .flatMap(Collection::stream)
        .mapToInt(item -> {
            int charCode = item.codePointAt(0);
            return Character.isUpperCase(charCode)
                ? charCode - upperCaseOffset
                : charCode - lowerCaseOffset;
        })
        .sum();
    System.out.println(resultPartOne);

    List<String> src = client.send(request.uri((URI.create(puzzleInputUri))).build(), HttpResponse.BodyHandlers.ofLines())
        .body().collect(Collectors.toList());
    Integer resultPartTwo = 0;
    Map<String, Integer> groupOfThree = new HashMap<>();
    for (String backpack : src) {
        if (!groupOfThree.containsValue(3)) {
            for (String item : Arrays.stream(backpack.split("")).collect(Collectors.toSet())) {
                groupOfThree.merge(item, 1, Integer::sum);
            }
        }
        if (groupOfThree.containsValue(3)) {
            resultPartTwo += groupOfThree.entrySet().stream()
                .filter(e -> e.getValue() == 3)
                .map(Entry::getKey)
                .mapToInt(item -> {
                    int charCode = item.codePointAt(0);
                    return Character.isUpperCase(charCode)
                        ? charCode - upperCaseOffset
                        : charCode - lowerCaseOffset;
                })
                .sum();
            groupOfThree = new HashMap<>();
        }
    }
    System.out.println(resultPartTwo);
}
Исходные данные: https://adventofcode.com/2022/day/3/input

Advent of Code 2022: Day 2

Задачка второго дня оказалась повариативней, пришлось искать отдельные решения для первой и второй частей.

Тем не менее – они были найдены, а ответы – получены. Как обычно – решение в виде функции запускается в jshell.

static void day2(String puzzleInputUri) throws IOException, InterruptedException {
/*
rock: A, X
scissors: C, Z
paper:  B, Y
*/
    Map<String, Integer> choiceCosts = Map.of("X", 1, "Y", 2, "Z", 3);
    Map<Integer, Set<String>> strategyCosts = Map.of(
        6, Set.of("A Y", "C X", "B Z"), //win
        3, Set.of("A X", "C Z", "B Y"), //draw
        0, Set.of("A Z", "C Y", "B X") //lose
    );
    Map<String, Set<String>> strategyMappingPartTwo = Map.of(
        "X", strategyCosts.get(0), //lose
        "Y", strategyCosts.get(3), //draw
        "Z", strategyCosts.get(6) //win
    );

    int totalPartOne = client.send(request.uri((URI.create(puzzleInputUri))).build(), HttpResponse.BodyHandlers.ofLines()).body()
        .map(round -> Map.entry(round,
            strategyCosts.entrySet().stream().mapToInt(entry -> entry.getValue().contains(round) ? entry.getKey() : 0).sum())
        )
        .mapToInt(roundWithCost -> {
            String choice = roundWithCost.getKey().split(" ")[1];
            return roundWithCost.getValue() + choiceCosts.get(choice);
        })
        .sum();
    System.out.println(totalPartOne);

    int totalPartTwo = client.send(request.uri((URI.create(puzzleInputUri))).build(), HttpResponse.BodyHandlers.ofLines()).body()
        .map(round -> round.split(" "))
        .map(choiceAndStrategy -> strategyMappingPartTwo.get(choiceAndStrategy[1]).stream()
            .filter(strategies -> strategies.contains(choiceAndStrategy[0]))
            .findAny()
        )
        .flatMap(Optional::stream)
        .mapToInt(round -> {
            int cost = strategyCosts.entrySet().stream().mapToInt(entry -> entry.getValue().contains(round) ? entry.getKey() : 0).sum();
            return cost + choiceCosts.get(round.split(" ")[1]);
        })
        .sum();
    System.out.println(totalPartTwo);
}
Исходные данные: https://adventofcode.com/2022/day/2/input

P.S. Как же, оказывается, тяжело воспринимается “на слух” непривычный порядок – вместо “камень/ножницы/бумага” в условиях описывается Rock Paper Scissors​ – несколько раз путался при мапинге стратегий, пока комментарий в “правильном” порядке не написал 😀

Advent of Code 2022: Day 1

Итак, простое решение для первого дня Advent of Code 2022 года. Запускается из консоли jshell.

Т.к. лень было что-то специальное изобретать для работы с данными задачи (да и формат AoC этого не предполагает) – сразу решил сложить всё в сортированную коллекцию.

Это пригодилось для второй части задачи – оказалось достаточным просто просуммировать три наибольшие цифры на калькуляторе – и ответ готов!

static void day1(String problemUri) throws IOException, InterruptedException {
    List<List<String>> initial = new ArrayList<>();
    initial.add(new ArrayList<>());
    var result = client.send(request.uri((URI.create(problemUri))).build(), HttpResponse.BodyHandlers.ofLines()).body()
        .reduce(initial, (sublist, element) -> {
            if (element.isBlank()) {
                sublist.add(new ArrayList<>());
            } else {
                sublist.get(sublist.size() - 1).add(element);
            }
            return sublist;
        }, (list1, list2) -> emptyList());

    TreeSet<Integer> calories = new TreeSet<>();
    for (var stringList : result) {
        calories.add(stringList.stream()
            .mapToInt(Integer::parseInt)
            .sum()
        );
    }

    System.out.println(calories);
}
Исходные данные: https://adventofcode.com/2022/day/1/input

Advent of Code 2022

Собрался в этом году поиграть-таки в Advent of Code (сомневаюсь, конечно, что до католического Рождества уложусь, но до НГ – шансы есть).

Чем он интересен – так это подходом. Собственно, сам код там и не нужен, требуется только ответ. Никому не важно, насколько эффективным, красивым и т.д. было решение, позволившее этот ответ получить.

Хоть на бумажке делай, хоть в электронных таблицах, хоть прямо в консоли браузера. Есть только задачка и окошка для ввода ответа.

Я решил делать в консоли jshell преимущественно. До тех пор, пока это получается удобно и быстро.

Читать далее Advent of Code 2022

Huginn, Yahoo Pipes, Habr и фид ленты комментариев

Huginn Agent event flow

Понадобилось мне получать с Хабра ленту комментариев пользователя в каком-то удобном виде, чтобы не заходить периодически на сайт для проверки её обновления. Очевидное решение – RSS.

В базе Хабр позволяет подписаться на профиль пользователя, или, по RSS, на ленту его публикаций. А вот для комментариев подобной возможности не предусмотрено (сама лента комментариев имеет вид https://habr.com/ru/users/user-name/comments/).

Первая мысль – воспользоваться Yahoo Pipes, ведь когда-то уже использовал его для решения подобных задач. Увы – оказалось, что сервис давно закрыт. Помолчим минуту (он был хорош!) и рассмотрим доступные альтернативы.

Читать далее Huginn, Yahoo Pipes, Habr и фид ленты комментариев

Idea – подключение к Docker в Windows WSL2

Небольшая памятка для себя. Понадобилось воспользоваться докером в Windows 10. При этом – уже установлена Ubuntu внутри WSL2. То есть – ставить сверху ещё и графический Docker Desktop, поднимающий свои контейнеры в отдельных виртуалках WSL, особой необходимости нет. Управлять контейнерами можно прямо из Idea.
Небольшое неудобство возникло с тем, что Docker плагин в idea (под Win) поддерживает Docker for Windows, но не умеет напрямую работать с докером внутри WSL. Вариантов несколько, среди них – подключение через tcp socket или через ssh.
Против tcp socket возникли два аргумента – сложнее настроить (чтобы просто пара настроек – и заработало – такого не произошло, пришлось повозиться); кроме того – руководство Docker не рекомендует пользоваться им из-за наличия потенциальной уязвимости.
Остановился на ssh – это проще, и в целом удовлетворяет моим потребностям.
Читать далее Idea – подключение к Docker в Windows WSL2