mirror of
https://github.com/mfocko/blog.git
synced 2024-11-24 22:11:54 +01:00
1 line
No EOL
28 KiB
JavaScript
1 line
No EOL
28 KiB
JavaScript
"use strict";(self.webpackChunkfi=self.webpackChunkfi||[]).push([[146],{42492:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>r,contentTitle:()=>a,default:()=>h,frontMatter:()=>s,metadata:()=>l,toc:()=>c});var i=n(85893),o=n(11151);const s={title:"1st week of Advent of Code '22 in Rust",description:"Surviving first week in Rust.",date:"2022-12-15T01:15",slug:"aoc-2022/1st-week",authors:"mf",tags:["advent-of-code","advent-of-code-2022","rust"],hide_table_of_contents:!1},a=void 0,l={permalink:"/blog/aoc-2022/1st-week",editUrl:"https://github.com/mfocko/blog/tree/main/blog/aoc-2022/01-week-1.md",source:"@site/blog/aoc-2022/01-week-1.md",title:"1st week of Advent of Code '22 in Rust",description:"Surviving first week in Rust.",date:"2022-12-15T01:15:00.000Z",formattedDate:"December 15, 2022",tags:[{label:"advent-of-code",permalink:"/blog/tags/advent-of-code"},{label:"advent-of-code-2022",permalink:"/blog/tags/advent-of-code-2022"},{label:"rust",permalink:"/blog/tags/rust"}],readingTime:12.4,hasTruncateMarker:!0,authors:[{name:"Matej Focko",email:"me+blog@mfocko.xyz",title:"a.k.a. @mf",url:"https://gitlab.com/mfocko",imageURL:"https://github.com/mfocko.png",key:"mf"}],frontMatter:{title:"1st week of Advent of Code '22 in Rust",description:"Surviving first week in Rust.",date:"2022-12-15T01:15",slug:"aoc-2022/1st-week",authors:"mf",tags:["advent-of-code","advent-of-code-2022","rust"],hide_table_of_contents:!1},unlisted:!1,prevItem:{title:"2nd week of Advent of Code '22 in Rust",permalink:"/blog/aoc-2022/2nd-week"},nextItem:{title:"Advent of Code '22 in Rust",permalink:"/blog/aoc-2022/intro"}},r={authorsImageUrls:[void 0]},c=[{value:"Day 1: Calorie Counting",id:"day-1-calorie-counting",level:2},{value:"Solution",id:"solution",level:3},{value:"Day 2: Rock Paper Scissors",id:"day-2-rock-paper-scissors",level:2},{value:"Solution",id:"solution-1",level:3},{value:"Day 3: Rucksack Reorganization",id:"day-3-rucksack-reorganization",level:2},{value:"Solution",id:"solution-2",level:3},{value:"Day 4: Camp Cleanup",id:"day-4-camp-cleanup",level:2},{value:"Solution",id:"solution-3",level:3},{value:"Day 5: Supply Stacks",id:"day-5-supply-stacks",level:2},{value:"Solution",id:"solution-4",level:3},{value:"Day 6: Tuning Trouble",id:"day-6-tuning-trouble",level:2},{value:"Solution",id:"solution-5",level:3},{value:"Day 7: No Space Left On Device",id:"day-7-no-space-left-on-device",level:2},{value:"Solution",id:"solution-6",level:3},{value:"Post Mortem",id:"post-mortem",level:2},{value:"<code>Rc<T></code> vs <code>Rc<RefCell<T>></code>",id:"rct-vs-rcrefcellt",level:3}];function d(e){const t={a:"a",admonition:"admonition",annotation:"annotation",blockquote:"blockquote",code:"code",del:"del",em:"em",h2:"h2",h3:"h3",li:"li",math:"math",mi:"mi",mrow:"mrow",p:"p",pre:"pre",semantics:"semantics",span:"span",strong:"strong",ul:"ul",...(0,o.a)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsxs)(t.p,{children:["Let's go through the first week of ",(0,i.jsx)(t.a,{href:"https://adventofcode.com",children:(0,i.jsx)(t.em,{children:"Advent of Code"})})," in Rust."]}),"\n",(0,i.jsx)(t.admonition,{type:"note",children:(0,i.jsxs)(t.p,{children:["If you wish to have a look at the solutions, you can follow them on my ",(0,i.jsx)(t.a,{href:"https://gitlab.com/mfocko/advent-of-code-2022",children:"GitLab"}),".\nMore specifically in the ",(0,i.jsx)(t.a,{href:"https://gitlab.com/mfocko/advent-of-code-2022/-/tree/main/src/bin",children:(0,i.jsx)(t.code,{children:"/src/bin/"})}),"."]})}),"\n",(0,i.jsxs)(t.p,{children:["I will try to summarize my experience with using Rust for the AoC. Trying it out\nages ago, I believe it will be ",(0,i.jsx)(t.em,{children:"pain and suffering"}),", but we will see. For each\nday I will also try to give a tl;dr of the problem, so that you can better imagine\nthe relation to my woes or ","\ud83d\udc4d"," moments."]}),"\n",(0,i.jsx)(t.h2,{id:"day-1-calorie-counting",children:(0,i.jsx)(t.a,{href:"https://adventofcode.com/2022/day/1",children:"Day 1: Calorie Counting"})}),"\n",(0,i.jsx)(t.admonition,{title:"tl;dr",type:"info",children:(0,i.jsx)(t.p,{children:"As the name suggests, we get the calories of the food contained in the elves\nbackpacks and we want to choose the elf that has the most food ;)"})}),"\n",(0,i.jsxs)(t.blockquote,{children:["\n",(0,i.jsx)(t.p,{children:"Wakey wakey!"}),"\n"]}),"\n",(0,i.jsx)(t.p,{children:"Programming in Rust at 6am definitely hits. I've also forgotten to mention how I\nhandle samples. With each puzzle you usually get a sample input and expected\noutput. You can use them to verify that your solution works, or usually doesn't."}),"\n",(0,i.jsxs)(t.p,{children:["At first I've decided to put asserts into my ",(0,i.jsx)(t.code,{children:"main"}),", something like"]}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-rust",children:'assert_eq!(part_1(&sample), 24000);\ninfo!("Part 1: {}", part_1(&input));\n\nassert_eq!(part_2(&sample), 45000);\ninfo!("Part 2: {}", part_2(&input));\n'})}),"\n",(0,i.jsx)(t.p,{children:"However, once you get further, the sample input may take some time to run itself.\nSo in the end, I have decided to turn them into unit tests:"}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-rust",children:'#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n fn test_part_1() {\n let sample = parse_input("samples/day01.txt");\n assert_eq!(part_1(&sample), 24000);\n }\n\n #[test]\n fn test_part_2() {\n let sample = parse_input("samples/day01.txt");\n assert_eq!(part_2(&sample), 45000);\n }\n}\n'})}),"\n",(0,i.jsxs)(t.p,{children:["And later on I have noticed, it's hard to tell the difference between the days,\nso I further renamed the ",(0,i.jsx)(t.code,{children:"mod"})," from generic ",(0,i.jsx)(t.code,{children:"tests"})," to reflect the days."]}),"\n",(0,i.jsxs)(t.p,{children:["Also after finishing the first day puzzle, I have installed an ",(0,i.jsx)(t.a,{href:"https://github.com/mozilla/sccache",children:(0,i.jsx)(t.code,{children:"sccache"})})," to\ncache the builds, so that the build time is lower, cause it was kinda unbearable."]}),"\n",(0,i.jsx)(t.h3,{id:"solution",children:"Solution"}),"\n",(0,i.jsx)(t.p,{children:"Well, it's a pretty simple problem. You just take the input, sum the calories and\nfind the biggest one. However, if we try to generalize to more than the biggest\none, the fun appears. We have few options:"}),"\n",(0,i.jsxs)(t.ul,{children:["\n",(0,i.jsx)(t.li,{children:"keep all the calories, sort them, take what we need"}),"\n",(0,i.jsx)(t.li,{children:"keep all the calories and use max heap"}),"\n",(0,i.jsx)(t.li,{children:"use min heap and maintain at most N calories that we need"}),"\n"]}),"\n",(0,i.jsx)(t.h2,{id:"day-2-rock-paper-scissors",children:(0,i.jsx)(t.a,{href:"https://adventofcode.com/2022/day/2",children:"Day 2: Rock Paper Scissors"})}),"\n",(0,i.jsx)(t.admonition,{title:"tl;dr",type:"info",children:(0,i.jsxs)(t.p,{children:["You want to know what score did you achieve while playing ",(0,i.jsx)(t.em,{children:"Rock Paper Scissors"}),".\nAnd then you want to be strategic about it."]})}),"\n",(0,i.jsx)(t.p,{children:"Apart from the technical details of the puzzle, it went relatively smooth."}),"\n",(0,i.jsx)(t.h3,{id:"solution-1",children:"Solution"}),"\n",(0,i.jsx)(t.p,{children:"I took relatively na\xefve approach and then tried to simplify it."}),"\n",(0,i.jsx)(t.h2,{id:"day-3-rucksack-reorganization",children:(0,i.jsx)(t.a,{href:"https://adventofcode.com/2022/day/3",children:"Day 3: Rucksack Reorganization"})}),"\n",(0,i.jsx)(t.admonition,{title:"tl;dr",type:"info",children:(0,i.jsx)(t.p,{children:"Let's go reorganize elves' backpacks! Each backpacks has 2 compartments and you\nwant to find the common item among those compartments. Each of them has priority,\nyou care only about the sum."})}),"\n",(0,i.jsx)(t.p,{children:"This is the day where I started to fight the compiler and neither of us decided\nto give up. Let's dive into it \\o/"}),"\n",(0,i.jsx)(t.admonition,{title:"Fun fact",type:"tip",children:(0,i.jsx)(t.p,{children:"Fighting the compiler took me 30 minutes."})}),"\n",(0,i.jsx)(t.p,{children:"We need to find a common item among 2 collections, that's an easy task, right?\nWe can construct 2 sets and find an intersection:"}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-rust",children:"let top: HashSet<i32> = [1, 2, 3].iter().collect();\nlet bottom: HashSet<i32> = [3, 4, 5].iter().collect();\n"})}),"\n",(0,i.jsxs)(t.p,{children:["Now, the first issue that we encounter is caused by the fact that we are using\na slice (the ",(0,i.jsx)(t.code,{children:"[\u2026]"}),"), iterator of that returns ",(0,i.jsx)(t.strong,{children:"references"})," to the numbers.\nAnd we get immediately yelled at by the compiler, because the numbers are discarded\nafter running the ",(0,i.jsx)(t.code,{children:".collect"}),". To fix this, we can use ",(0,i.jsx)(t.code,{children:".into_iter"}),":"]}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-rust",children:"let top: HashSet<i32> = [1, 2, 3].into_iter().collect();\nlet bottom: HashSet<i32> = [3, 4, 5].into_iter().collect();\n"})}),"\n",(0,i.jsx)(t.p,{children:"This way the numbers will get copied instead of referenced. OK, let's find the\nintersection of those 2 collections:"}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-rust",children:'println!("Common elements: {:?}", top.intersection(&bottom));\n'})}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{children:"Common elements: [3]\n"})}),"\n",(0,i.jsx)(t.admonition,{title:"caution",type:"warning",children:(0,i.jsxs)(t.p,{children:["Notice that we need to do ",(0,i.jsx)(t.code,{children:"&bottom"}),". It explicitly specifies that ",(0,i.jsx)(t.code,{children:".intersection"}),"\n",(0,i.jsx)(t.strong,{children:"borrows"})," the ",(0,i.jsx)(t.code,{children:"bottom"}),", i.e. takes an immutable reference to it."]})}),"\n",(0,i.jsx)(t.p,{children:"That's what we want, right? Looks like it! \\o/"}),"\n",(0,i.jsx)(t.p,{children:"Next part wants us to find the common element among all of the backpacks. OK, so\nthat should be fairly easy, we have an intersection and we want to find intersection\nover all of them."}),"\n",(0,i.jsxs)(t.p,{children:["Let's have a look at the type of the ",(0,i.jsx)(t.code,{children:".intersection"})]}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-rust",children:"pub fn intersection<'a>(\n\xa0\xa0\xa0\xa0&'a self,\n\xa0\xa0\xa0\xa0other: &'a HashSet<T, S>\n) -> Intersection<'a, T, S>\n"})}),"\n",(0,i.jsx)(t.p,{children:"OK\u2026 Huh\u2026 But we have an example there!"}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-rust",children:"let intersection: HashSet<_> = a.intersection(&b).collect();\n"})}),"\n",(0,i.jsx)(t.p,{children:"Cool, that's all we need."}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-rust",children:'let top: HashSet<i32> = [1, 2, 3, 4].into_iter().collect();\nlet bottom: HashSet<i32> = [3, 4, 5, 6].into_iter().collect();\nlet top_2: HashSet<i32> = [2, 3, 4, 5, 6].into_iter().collect();\nlet bottom_2: HashSet<i32> = [4, 5, 6].into_iter().collect();\n\nlet intersection: HashSet<_> = top.intersection(&bottom).collect();\nprintln!("Intersection: {:?}", intersection);\n'})}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{children:"Intersection: {3, 4}\n"})}),"\n",(0,i.jsxs)(t.p,{children:["Cool, so let's do the intersection with the ",(0,i.jsx)(t.code,{children:"top_2"}),":"]}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-rust",children:'let top: HashSet<i32> = [1, 2, 3, 4].into_iter().collect();\nlet bottom: HashSet<i32> = [3, 4, 5, 6].into_iter().collect();\nlet top_2: HashSet<i32> = [2, 3, 4, 5, 6].into_iter().collect();\nlet bottom_2: HashSet<i32> = [4, 5, 6].into_iter().collect();\n\nlet intersection: HashSet<_> = top.intersection(&bottom).collect();\nlet intersection: HashSet<_> = intersection.intersection(&top_2).collect();\nprintln!("Intersection: {:?}", intersection);\n'})}),"\n",(0,i.jsx)(t.p,{children:"And we get yelled at by the compiler:"}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{children:"error[E0308]: mismatched types\n --\x3e src/main.rs:10:58\n |\n10 | let intersection: HashSet<_> = intersection.intersection(&top_2).collect();\n | ------------ ^^^^^^ expected `&i32`, found `i32`\n | |\n | arguments to this function are incorrect\n |\n = note: expected reference `&HashSet<&i32>`\n found reference `&HashSet<i32>`\n"})}),"\n",(0,i.jsx)(t.p,{children:"/o\\ What the hell is going on here? Well, the funny thing is, that this operation\ndoesn't return the elements themselves, but the references to them and when we pass\nthe third set, it has just the values themselves, without any references."}),"\n",(0,i.jsx)(t.admonition,{type:"tip",children:(0,i.jsxs)(t.p,{children:["It may seem as a very weird decision, but in fact it makes some sense\u2026 It allows\nyou to do intersection of items that may not be possible to copy. Overall this is\na \u201ctax\u201d for having a borrow checker ",(0,i.jsx)(t.del,{children:"drilling your ass"})," having your back and\nmaking sure you're not doing something naughty that may cause an ",(0,i.jsx)(t.strong,{children:"undefined"}),"\n",(0,i.jsx)(t.strong,{children:"behavior"}),"."]})}),"\n",(0,i.jsxs)(t.p,{children:["To resolve this we need to get an iterator that ",(0,i.jsx)(t.strong,{children:"clones"})," the elements:"]}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-rust",children:'let top: HashSet<i32> = [1, 2, 3, 4].into_iter().collect();\nlet bottom: HashSet<i32> = [3, 4, 5, 6].into_iter().collect();\nlet top_2: HashSet<i32> = [2, 3, 4, 5, 6].into_iter().collect();\nlet bottom_2: HashSet<i32> = [4, 5, 6].into_iter().collect();\n\nlet intersection: HashSet<_> = top.intersection(&bottom).cloned().collect();\nlet intersection: HashSet<_> = intersection.intersection(&top_2).cloned().collect();\nlet intersection: HashSet<_> = intersection.intersection(&bottom_2).cloned().collect();\nprintln!("Intersection: {:?}", intersection);\n'})}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{children:"Intersection: {4}\n"})}),"\n",(0,i.jsx)(t.h3,{id:"solution-2",children:"Solution"}),"\n",(0,i.jsxs)(t.p,{children:["The approach is pretty simple, if you omit the ",(0,i.jsx)(t.em,{children:"1on1 with the compiler"}),". You just\nhave some fun with the set operations :)"]}),"\n",(0,i.jsx)(t.h2,{id:"day-4-camp-cleanup",children:(0,i.jsx)(t.a,{href:"https://adventofcode.com/2022/day/4",children:"Day 4: Camp Cleanup"})}),"\n",(0,i.jsx)(t.admonition,{title:"tl;dr",type:"info",children:(0,i.jsx)(t.p,{children:"Elves are cleaning up the camp and they got overlapping sections to clean up.\nFind how many overlap and can take the day off."})}),"\n",(0,i.jsxs)(t.p,{children:[(0,i.jsx)(t.a,{href:"https://doc.rust-lang.org/std/ops/struct.RangeInclusive.html",children:(0,i.jsx)(t.code,{children:"RangeInclusive"})})," is your friend not an enemy :)"]}),"\n",(0,i.jsx)(t.h3,{id:"solution-3",children:"Solution"}),"\n",(0,i.jsxs)(t.p,{children:["Relatively easy, you just need to parse the input and know what you want. Rust's\n",(0,i.jsx)(t.code,{children:"RangeInclusive"})," type helped a lot, cause it took care of all abstractions."]}),"\n",(0,i.jsx)(t.h2,{id:"day-5-supply-stacks",children:(0,i.jsx)(t.a,{href:"https://adventofcode.com/2022/day/5",children:"Day 5: Supply Stacks"})}),"\n",(0,i.jsx)(t.admonition,{title:"tl;dr",type:"info",children:(0,i.jsx)(t.p,{children:"Let's play with stacks of crates."})}),"\n",(0,i.jsx)(t.p,{children:"Very easy problem with very annoying input. You can judge yourself:"}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{children:" [D]\n[N] [C]\n[Z] [M] [P]\n 1 2 3\n\nmove 1 from 2 to 1\nmove 3 from 1 to 3\nmove 2 from 2 to 1\nmove 1 from 1 to 2\n"})}),"\n",(0,i.jsx)(t.p,{children:"Good luck transforming that into something reasonable :)"}),"\n",(0,i.jsx)(t.admonition,{title:"Fun fact",type:"tip",children:(0,i.jsx)(t.p,{children:"Took me 40 minutes to parse this reasonably, including fighting the compiler."})}),"\n",(0,i.jsx)(t.h3,{id:"solution-4",children:"Solution"}),"\n",(0,i.jsxs)(t.p,{children:["For the initial solution I went with a manual solution (as in ",(0,i.jsx)(t.em,{children:"I have done all"}),"\n",(0,i.jsx)(t.em,{children:"the work"}),". Later on I have decided to explore the ",(0,i.jsx)(t.code,{children:"std"})," and interface of the\n",(0,i.jsx)(t.code,{children:"std::vec::Vec"})," and found ",(0,i.jsx)(t.a,{href:"https://doc.rust-lang.org/std/vec/struct.Vec.html#method.split_off",children:(0,i.jsx)(t.code,{children:"split_off"})})," which takes an index and splits (duh)\nthe vector:"]}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-rust",children:"let mut vec = vec![1, 2, 3];\nlet vec2 = vec.split_off(1);\nassert_eq!(vec, [1]);\nassert_eq!(vec2, [2, 3]);\n"})}),"\n",(0,i.jsxs)(t.p,{children:["This helped me simplify my solution a lot and also get rid of some ",(0,i.jsx)(t.em,{children:"edge cases"}),"."]}),"\n",(0,i.jsx)(t.h2,{id:"day-6-tuning-trouble",children:(0,i.jsx)(t.a,{href:"https://adventofcode.com/2022/day/6",children:"Day 6: Tuning Trouble"})}),"\n",(0,i.jsx)(t.admonition,{title:"tl;dr",type:"info",children:(0,i.jsxs)(t.p,{children:["Finding start of the message in a very weird protocol. Start of the message is\ndenoted by ",(0,i.jsxs)(t.span,{className:"katex",children:[(0,i.jsx)(t.span,{className:"katex-mathml",children:(0,i.jsx)(t.math,{xmlns:"http://www.w3.org/1998/Math/MathML",children:(0,i.jsxs)(t.semantics,{children:[(0,i.jsx)(t.mrow,{children:(0,i.jsx)(t.mi,{children:"N"})}),(0,i.jsx)(t.annotation,{encoding:"application/x-tex",children:"N"})]})})}),(0,i.jsx)(t.span,{className:"katex-html","aria-hidden":"true",children:(0,i.jsxs)(t.span,{className:"base",children:[(0,i.jsx)(t.span,{className:"strut",style:{height:"0.6833em"}}),(0,i.jsx)(t.span,{className:"mord mathnormal",style:{marginRight:"0.10903em"},children:"N"})]})})]})," unique consecutive characters."]})}),"\n",(0,i.jsx)(t.h3,{id:"solution-5",children:"Solution"}),"\n",(0,i.jsx)(t.p,{children:"A lot of different approaches, knowing that we are dealing with input consisting\nsolely of ASCII letters, I bit the bullet and went with sliding window and\nconstructing sets from that window, checking if the set is as big as the window."}),"\n",(0,i.jsxs)(t.p,{children:["One possible optimization could consist of keeping a bit-vector (i.e. ",(0,i.jsx)(t.code,{children:"usize"}),"\nvariable) of encountered characters and updating it as we go. However this has\na different issue and that is removal of the characters from the left side of the\nwindow. We don't know if the same character is not included later on."]}),"\n",(0,i.jsx)(t.p,{children:"Other option is to do similar thing, but keeping the frequencies of the letters,\nand again knowing we have only ASCII letters we can optimize by having a vector\nof 26 elements that keeps count for each lowercase letter."}),"\n",(0,i.jsx)(t.h2,{id:"day-7-no-space-left-on-device",children:(0,i.jsx)(t.a,{href:"https://adventofcode.com/2022/day/7",children:"Day 7: No Space Left On Device"})}),"\n",(0,i.jsx)(t.admonition,{title:"tl;dr",type:"info",children:(0,i.jsxs)(t.p,{children:["Let's simulate ",(0,i.jsx)(t.a,{href:"https://www.man7.org/linux/man-pages/man1/du.1.html",children:(0,i.jsx)(t.code,{children:"du"})})," to get some stats about our file system and then pinpoint\ndirectories that take a lot of space and should be deleted."]})}),"\n",(0,i.jsxs)(t.blockquote,{children:["\n",(0,i.jsxs)(t.p,{children:["I was waiting for this moment, and yet it got me!\n",(0,i.jsx)(t.em,{children:"imagine me swearing for hours"})]}),"\n"]}),"\n",(0,i.jsx)(t.h3,{id:"solution-6",children:"Solution"}),"\n",(0,i.jsxs)(t.p,{children:["We need to \u201c",(0,i.jsx)(t.em,{children:"build"}),"\u201d a file system from the input that is given in a following form:"]}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{children:"$ cd /\n$ ls\ndir a\n14848514 b.txt\n8504156 c.dat\ndir d\n$ cd a\n$ ls\ndir e\n29116 f\n2557 g\n62596 h.lst\n$ cd e\n$ ls\n584 i\n$ cd ..\n$ cd ..\n$ cd d\n$ ls\n4060174 j\n8033020 d.log\n5626152 d.ext\n7214296 k\n"})}),"\n",(0,i.jsx)(t.p,{children:"There are few ways in which you can achieve this and also you can assume some\npreconditions, but why would we do that, right? :)"}),"\n",(0,i.jsxs)(t.p,{children:["You can \u201cslap\u201d this in either ",(0,i.jsx)(t.a,{href:"https://doc.rust-lang.org/std/collections/struct.HashMap.html",children:(0,i.jsx)(t.code,{children:"HashMap"})})," or ",(0,i.jsx)(t.a,{href:"https://doc.rust-lang.org/std/collections/struct.BTreeMap.html",children:(0,i.jsx)(t.code,{children:"BTreeMap"})})," and call it a day.\nAnd that would be boring\u2026"]}),"\n",(0,i.jsx)(t.admonition,{type:"tip",children:(0,i.jsxs)(t.p,{children:[(0,i.jsx)(t.code,{children:"BTreeMap"})," is quite fitting for this, don't you think?"]})}),"\n",(0,i.jsxs)(t.p,{children:["I always wanted to try allocation on heap in Rust, so I chose to implement a tree.\nI fought with the ",(0,i.jsx)(t.code,{children:"Box<T>"})," for some time and was losing\u2026"]}),"\n",(0,i.jsxs)(t.p,{children:["Then I looked up some implementations of trees or linked lists and decided to try\n",(0,i.jsx)(t.code,{children:"Rc<Cell<T>>"}),". And I got my ",(0,i.jsx)(t.em,{children:"ass whopped"})," by the compiler once again. /o\\"]}),"\n",(0,i.jsxs)(t.admonition,{type:"tip",children:[(0,i.jsxs)(t.p,{children:[(0,i.jsx)(t.code,{children:"Box<T>"})," represents a dynamically allocated memory on heap. It is a single pointer,\nyou can imagine this as ",(0,i.jsx)(t.code,{children:"std::unique_ptr<T>"})," in C++."]}),(0,i.jsxs)(t.p,{children:[(0,i.jsx)(t.code,{children:"Rc<T>"})," represents a dynamically allocated memory on heap. On top of that it is\n",(0,i.jsx)(t.em,{children:"reference counted"})," (that's what the ",(0,i.jsx)(t.code,{children:"Rc"})," stands for). You can imagine this as\n",(0,i.jsx)(t.code,{children:"std::shared_ptr<T>"})," in C++."]}),(0,i.jsxs)(t.p,{children:["Now the fun stuff. Neither of them lets you ",(0,i.jsx)(t.strong,{children:"mutate"})," the contents of the memory."]}),(0,i.jsxs)(t.p,{children:[(0,i.jsx)(t.code,{children:"Cell<T>"})," allows you to mutate the memory. Can be used reasonably with types that\ncan be copied, because the memory safety is guaranteed by copying the contents\nwhen there is more than one ",(0,i.jsx)(t.strong,{children:"mutable"})," reference to the memory."]}),(0,i.jsxs)(t.p,{children:[(0,i.jsx)(t.code,{children:"RefCell<T>"})," is similar to the ",(0,i.jsx)(t.code,{children:"Cell<T>"}),", but the borrowing rules (how many mutable\nreferences are present) are checked dynamically."]}),(0,i.jsxs)(t.p,{children:["So in the end, if you want something like ",(0,i.jsx)(t.code,{children:"std::shared_ptr<T>"})," in Rust, you want\nto have ",(0,i.jsx)(t.code,{children:"Rc<RefCell<T>>"}),"."]})]}),"\n",(0,i.jsxs)(t.p,{children:["So, how are we going to represent the file system then? We will use an enumeration,\nhehe, which is an algebraic data type that can store some stuff in itself ","\ud83d\ude29"]}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-rust",children:"type FileHandle = Rc<RefCell<AocFile>>;\n\n#[derive(Debug)]\nenum AocFile {\n File(usize),\n Directory(BTreeMap<String, FileHandle>),\n}\n"})}),"\n",(0,i.jsxs)(t.p,{children:["Let's go over it! ",(0,i.jsx)(t.code,{children:"FileHandle"})," represents dynamically allocated ",(0,i.jsx)(t.code,{children:"AocFile"}),", not\nmuch to discuss. What does the ",(0,i.jsx)(t.code,{children:"#[derive(Debug)]"})," do though? It lets us to print\nout the value of that enumeration, it's derived, so it's not as good as if we had\nimplemented it ourselves, but it's good enough for debugging, hence the name."]}),"\n",(0,i.jsxs)(t.p,{children:["Now to the fun part! ",(0,i.jsx)(t.code,{children:"AocFile"})," value can be represented in two ways:"]}),"\n",(0,i.jsxs)(t.ul,{children:["\n",(0,i.jsxs)(t.li,{children:[(0,i.jsx)(t.code,{children:"File(usize)"}),", e.g. ",(0,i.jsx)(t.code,{children:"AocFile::File(123)"})," and we can pattern match it, if we\nneed to"]}),"\n",(0,i.jsxs)(t.li,{children:[(0,i.jsx)(t.code,{children:"Directory(BTreeMap<String, FileHandle>)"})," will represent the directory and will\ncontain map matching the name of the files (or directories) within to their\nrespective file handles"]}),"\n"]}),"\n",(0,i.jsx)(t.p,{children:"I will omit the details about constructing this file system, cause there are a lot\nof technicalities introduced by the nature of the input. However if you are\ninterested, you can have a look at my solution."}),"\n",(0,i.jsx)(t.p,{children:"We need to find small enough directories and also find the smallest directory that\nwill free enough space. Now the question is, how could we do that. And there are\nmultiple ways I will describe."}),"\n",(0,i.jsxs)(t.p,{children:["I have chosen to implement ",(0,i.jsx)(t.a,{href:"https://en.wikipedia.org/wiki/Catamorphism#Tree_fold",children:(0,i.jsx)(t.em,{children:"tree catamorphism"})})," ","\ud83d\ude29",". It is basically a fold\nover a tree data structure. We descent down into the leaves and propagate computed\nresults all the way to the root. You can also notice that this approach is very\nsimilar to ",(0,i.jsx)(t.em,{children:"dynamic programming"}),", we find overlapping sections of the computation\nand try to minimize the additional work (in this case: we need to know sizes of\nour descendants, but we have already been there)."]}),"\n",(0,i.jsx)(t.p,{children:"Another approach that has been suggested to me few days later is running DFS on\nthe graph. And, funnily enough, we would still need to combine what we found in\nthe branches where we descent. So in the end, it would work very similarly to my\nsolution."}),"\n",(0,i.jsx)(t.p,{children:"One of the more exotic options would be precomputing the required information at\nthe same time as parsing. That could be done by adding additional fields to the\nnodes which would allow storing such information and updating it as we construct\nthe file system."}),"\n",(0,i.jsx)(t.h2,{id:"post-mortem",children:"Post Mortem"}),"\n",(0,i.jsx)(t.p,{children:"Things that have been brought up in the discussion later on."}),"\n",(0,i.jsxs)(t.h3,{id:"rct-vs-rcrefcellt",children:[(0,i.jsx)(t.code,{children:"Rc<T>"})," vs ",(0,i.jsx)(t.code,{children:"Rc<RefCell<T>>"})]}),"\n",(0,i.jsx)(t.p,{children:"It has been brought up that I have a contradicting statement regarding the\ndynamically allocated memory. Specifically:"}),"\n",(0,i.jsxs)(t.ul,{children:["\n",(0,i.jsxs)(t.li,{children:["You can imagine ",(0,i.jsx)(t.code,{children:"Rc<T>"})," as an ",(0,i.jsx)(t.code,{children:"std::shared_ptr<T>"})," (in C++)"]}),"\n",(0,i.jsxs)(t.li,{children:["When you want an equivalent of ",(0,i.jsx)(t.code,{children:"std::shared_ptr<T>"}),", you want to use\n",(0,i.jsx)(t.code,{children:"Rc<RefCell<T>>"})]}),"\n"]}),"\n",(0,i.jsxs)(t.p,{children:["Now, in Rust it is a bit more complicated, because the type that represents the\n\u201cshared pointer\u201d is ",(0,i.jsx)(t.code,{children:"Rc<T>"}),". What ",(0,i.jsx)(t.code,{children:"RefCell<T>"})," does is making sure that there is\nonly one \u201cowner\u201d of a mutable reference at a time (and dynamically, as opposed\nto the ",(0,i.jsx)(t.code,{children:"Cell<T>"}),")."]}),"\n",(0,i.jsxs)(t.p,{children:["Therefore to be precise and correct about the equivalents of ",(0,i.jsx)(t.code,{children:"std::shared_ptr<T>"}),"\nin Rust, we can say that"]}),"\n",(0,i.jsxs)(t.ul,{children:["\n",(0,i.jsxs)(t.li,{children:[(0,i.jsx)(t.code,{children:"Rc<T>"})," is an equivalent of a ",(0,i.jsx)(t.code,{children:"const std::shared_ptr<T>"}),","]}),"\n",(0,i.jsxs)(t.li,{children:["and ",(0,i.jsx)(t.code,{children:"Rc<RefCell<T>>"})," is an equivalent of a ",(0,i.jsx)(t.code,{children:"std::shared_ptr<T>"}),"."]}),"\n"]}),"\n",(0,i.jsxs)(t.p,{children:["You can easily see that they only differ in the mutability. (And even that is not\nas simple as it seems, because there is also ",(0,i.jsx)(t.code,{children:"Cell<T>"}),")"]})]})}function h(e={}){const{wrapper:t}={...(0,o.a)(),...e.components};return t?(0,i.jsx)(t,{...e,children:(0,i.jsx)(d,{...e})}):d(e)}},11151:(e,t,n)=>{n.d(t,{Z:()=>l,a:()=>a});var i=n(67294);const o={},s=i.createContext(o);function a(e){const t=i.useContext(s);return i.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function l(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:a(e.components),i.createElement(s.Provider,{value:t},e.children)}}}]); |