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 он не заработает.

Неудачные попытки седьмого дня

Мы сделали ФС в твоей ФС

Первое решение — хорошо, у нас же тут «как бы файловая система» — так почему бы не сэмулировать её на файловой системе? Возможно, это сработало бы, если как следует поколдовать с find. А может — и нет.

Так или иначе, ФС создавалась (всегда успешно — на тестовом пример и не всегда — на реальном)

curl -s --request GET \ --url https://adventofcode.com/2022/day/7/input \ -b session=secret \ | sed -n '2,$p' | sed 's/$ //' | grep -v 'ls#39; | \ perl -lne ' @comm; %log = $_ =~ /^(\w+)\s(.+)$/; while (($k, $v) = each %log) { if ($k eq "dir") {push (@comm, "mkdir $v")} elsif ($k =~ /\d+/) {push (@comm, "fallocate -l $k $v")} elsif ($k eq "cd") {push (@comm, "$k $v")} } print join (" && ", @comm); ' \ | tail -n 1 | sh

И на тестовом же примере получался прекрасный и верный ответ с помощью

find. -type f -exec du -ab -t -100000 {} + |cut -f 1 |awk'{s+=$1} END {print s}'

Увы, заставить это сработать на реальных данных мне не удалось.

За деревьями леса не видно

Вторая попытка — ладно, придётся всё же создать дерево. Максимально простой узел в виде:

class TreeNode { public String name; public TreeNode parent; public Map<String, TreeNode> childs = new HashMap<>(); public long size; public TreeNode(String name, TreeNode parent, long size) { this.name = name; this.parent = parent; this.size = size; } }

И не менее простое заполнение дерева этим узлами:

TreeNode root = new TreeNode("/", null, 0L); TreeNode current = root; for (var commandLine : commands) { String command = commandLine[0]; String arg = commandLine[1]; if ("dir".equals(command)) { current.childs.putIfAbsent(arg, new TreeNode(arg, current, 0L)); } else if ("cd".equals(command)) { if ("..".equals(arg)) current = current.parent; else current = current.childs.get(arg); } else {current.size += Long.parseLong(command);} }

Отличное получилось дерево! Оно красиво распечатывалось в правильном виде

/[23352670] ├── a[94269] │ └── e[584] └── d[24933642]

и замечательно считало результат на тестовом примере! Жаль только, что на рабочих данных это дерево безбожно врало :) Либо я окончательно разучился в рекурсию…

В общем, безуспешно повозившись с деревьями некоторое время, я понял, что лес — это нечто большее, чем одни лишь деревья, и в итоге пришел к решению, описанному в начале заметки.

1
2 комментария
| \\\nperl -lne '\n@comm;\n%log = $_ =~ /^(\\w+)\\s(.+)$/;\nwhile (($k, $v) = each %log) {\n if ($k eq \"dir\") {push (@comm, \"mkdir $v\")}\n elsif ($k =~ /\\d+/) {push (@comm, \"fallocate -l $k $v\")}\n elsif ($k eq \"cd\") {push (@comm, \"$k $v\")}\n}\nprint join (\" && \", @comm);\n' \\\n| tail -n 1 | sh","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

И на тестовом же примере получался прекрасный и верный ответ с помощью

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"find. -type f -exec du -ab -t -100000 {} + |cut -f 1 |awk'{s+=$1} END {print s}'","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Увы, заставить это сработать на реальных данных мне не удалось.

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"За деревьями леса не видно"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Вторая попытка — ладно, придётся всё же создать дерево. Максимально простой узел в виде:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"class TreeNode {\n public String name;\n public TreeNode parent;\n public Map childs = new HashMap<>();\n public long size;\n\n public TreeNode(String name, TreeNode parent, long size) {\n this.name = name;\n this.parent = parent;\n this.size = size;\n }\n}","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

И не менее простое заполнение дерева этим узлами:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"TreeNode root = new TreeNode(\"/\", null, 0L);\nTreeNode current = root;\nfor (var commandLine : commands) {\n String command = commandLine[0];\n String arg = commandLine[1];\n if (\"dir\".equals(command)) {\n current.childs.putIfAbsent(arg, new TreeNode(arg, current, 0L));\n } else if (\"cd\".equals(command)) {\n if (\"..\".equals(arg)) current = current.parent;\n else current = current.childs.get(arg);\n } else {current.size += Long.parseLong(command);}\n}","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Отличное получилось дерево! Оно красиво распечатывалось в правильном виде

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"/[23352670]\n├── a[94269]\n│ └── e[584]\n└── d[24933642]","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

и замечательно считало результат на тестовом примере! Жаль только, что на рабочих данных это дерево безбожно врало :) Либо я окончательно разучился в рекурсию…

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

В общем, безуспешно повозившись с деревьями некоторое время, я понял, что лес — это нечто большее, чем одни лишь деревья, и в итоге пришел к решению, описанному в начале заметки.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

#adventofcode #adventofcode2022

"}}],"summaryContent":null,"isExistSummaryContent":false,"warningFromEditor":null,"warningFromEditorTitle":null,"counters":{"comments":2,"favorites":0,"reposts":0,"views":1,"hits":90,"reads":null,"online":0},"dateFavorite":0,"hitsCount":90,"isCommentsEnabled":true,"isLikesEnabled":true,"isRemovedByUserRequest":false,"isFavorited":false,"isPinned":false,"repostId":null,"repostData":null,"subscribedToTreads":false,"isEditorial":false,"isAudioAvailable":false,"audioUrl":null,"isAudioAvailableToGenerate":false,"commentEditor":{"enabled":true,"who":null,"text":"","until":null,"reason":null,"type":"everybody"},"isBlur":false,"isPublished":true,"isDisabledAd":false,"withheld":[],"ogTitle":null,"ogDescription":null,"url":"https://vc.ru/id1350758/559908-advent-of-code-2022-day-7","author":{"id":1350758,"name":"dimio","nickname":null,"description":"dimio.org TG: https://t.me/+V6AYCvyarnM4OTgy","uri":"","avatar":{"type":"image","data":{"uuid":"a9115688-0452-5bbb-8387-53d071a46d27","width":512,"height":512,"size":13288,"type":"png","color":"343434","hash":"","external_service":[]}},"cover":null,"achievements":[{"title":"3 года на vc.ru","code":"registration_3_years","description":"Провёл 3 года вместе с vc.ru. Получена 17 ноября 2025.","previewUuid":"d9d72ac5-bcb5-55e0-8c72-b99251e5cdd9","formats":{"glb":"https://static.vc.ru/achievements/shark.glb","usdz":"https://static.vc.ru/achievements/shark.usdz"},"viewData":{"contentColor":"#8E6F09","textMaxWidth":0.66796875,"textX":0.5205078125,"textY":0.341796875,"logoX":0.5205078125,"logoY":0.4609375,"logoXNoText":0.5,"logoYNoText":0.3662109375},"id":6093270,"userId":1350758,"count":0,"shareImage":"https://api.vc.ru/achievements/share/6093270"},{"title":"Год на vc.ru","code":"registration_1_year","description":"Первый год с vc.ru. Получена 24 июля 2025.","previewUuid":"0d11c244-49de-50e7-894e-b9b27945d42b","formats":{"glb":"https://static.vc.ru/achievements/fish.glb","usdz":"https://static.vc.ru/achievements/fish.usdz"},"viewData":{"contentColor":"#C67AA3","textMaxWidth":0.634765625,"textX":0.5888671875,"textY":0.54296875,"logoX":0.5859375,"logoY":0.6669921875,"logoXNoText":0.6044921875,"logoYNoText":0.5439453125},"id":4101174,"userId":1350758,"count":0,"shareImage":"https://api.vc.ru/achievements/share/4101174"}],"lastModificationDate":1764949489,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":true,"badgeId":null,"isDonationsEnabled":false,"isPlusGiftEnabled":true,"isUnverifiedBlogForCompanyWithoutPro":false,"isRemovedByUserRequest":false,"isFrozen":false,"isDisabledAd":false,"isPlus":false,"isVerified":false,"isPro":false,"yandexMetricaId":null,"badge":null,"isOnline":false,"tgChannelShortname":null,"isUnsubscribable":true,"type":1,"subtype":"personal_blog"},"subsite":{"id":1350758,"name":"dimio","nickname":null,"description":"dimio.org TG: https://t.me/+V6AYCvyarnM4OTgy","uri":"","avatar":{"type":"image","data":{"uuid":"a9115688-0452-5bbb-8387-53d071a46d27","width":512,"height":512,"size":13288,"type":"png","color":"343434","hash":"","external_service":[]}},"cover":null,"achievements":[{"title":"3 года на vc.ru","code":"registration_3_years","description":"Провёл 3 года вместе с vc.ru. Получена 17 ноября 2025.","previewUuid":"d9d72ac5-bcb5-55e0-8c72-b99251e5cdd9","formats":{"glb":"https://static.vc.ru/achievements/shark.glb","usdz":"https://static.vc.ru/achievements/shark.usdz"},"viewData":{"contentColor":"#8E6F09","textMaxWidth":0.66796875,"textX":0.5205078125,"textY":0.341796875,"logoX":0.5205078125,"logoY":0.4609375,"logoXNoText":0.5,"logoYNoText":0.3662109375},"id":6093270,"userId":1350758,"count":0,"shareImage":"https://api.vc.ru/achievements/share/6093270"},{"title":"Год на vc.ru","code":"registration_1_year","description":"Первый год с vc.ru. Получена 24 июля 2025.","previewUuid":"0d11c244-49de-50e7-894e-b9b27945d42b","formats":{"glb":"https://static.vc.ru/achievements/fish.glb","usdz":"https://static.vc.ru/achievements/fish.usdz"},"viewData":{"contentColor":"#C67AA3","textMaxWidth":0.634765625,"textX":0.5888671875,"textY":0.54296875,"logoX":0.5859375,"logoY":0.6669921875,"logoXNoText":0.6044921875,"logoYNoText":0.5439453125},"id":4101174,"userId":1350758,"count":0,"shareImage":"https://api.vc.ru/achievements/share/4101174"}],"lastModificationDate":1764949489,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":true,"badgeId":null,"isDonationsEnabled":false,"isPlusGiftEnabled":true,"isUnverifiedBlogForCompanyWithoutPro":false,"isRemovedByUserRequest":false,"isFrozen":false,"isDisabledAd":false,"isPlus":false,"isVerified":false,"isPro":false,"yandexMetricaId":null,"badge":null,"isOnline":false,"tgChannelShortname":null,"isUnsubscribable":true,"type":1,"subtype":"personal_blog"},"reactions":{"counters":[{"id":1,"count":1}],"reactionId":0},"isNews":false,"source":null,"clusters":[],"donations":{"amount":0,"isDonated":false},"commentsSeenCount":null,"keywords":["adventofcode","adventofcode2022"],"media":null,"customCover":null,"robotsTag":null,"categories":[],"isAnonymized":true}};