mirror of
https://github.com/mfocko/blog.git
synced 2024-11-24 22:11:54 +01:00
1 line
No EOL
47 KiB
JavaScript
1 line
No EOL
47 KiB
JavaScript
"use strict";(self.webpackChunkfi=self.webpackChunkfi||[]).push([[1011],{7582:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>d,contentTitle:()=>o,default:()=>h,frontMatter:()=>r,metadata:()=>a,toc:()=>l});var i=t(85893),s=t(11151);const r={title:"2nd week of Advent of Code '22 in Rust",description:"Surviving second week in Rust.",date:"2022-12-25T23:15",slug:"aoc-2022/2nd-week",authors:"mf",tags:["advent-of-code","advent-of-code-2022","rust"],hide_table_of_contents:!1},o=void 0,a={permalink:"/blog/aoc-2022/2nd-week",editUrl:"https://github.com/mfocko/blog/tree/main/blog/aoc-2022/02-week-2.md",source:"@site/blog/aoc-2022/02-week-2.md",title:"2nd week of Advent of Code '22 in Rust",description:"Surviving second week in Rust.",date:"2022-12-25T23:15:00.000Z",formattedDate:"December 25, 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:20.875,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:"2nd week of Advent of Code '22 in Rust",description:"Surviving second week in Rust.",date:"2022-12-25T23:15",slug:"aoc-2022/2nd-week",authors:"mf",tags:["advent-of-code","advent-of-code-2022","rust"],hide_table_of_contents:!1},unlisted:!1,prevItem:{title:"Sort the matrix diagonally",permalink:"/blog/leetcode/sort-diagonally"},nextItem:{title:"1st week of Advent of Code '22 in Rust",permalink:"/blog/aoc-2022/1st-week"}},d={authorsImageUrls:[void 0]},l=[{value:"Day 8: Treetop Tree House",id:"day-8-treetop-tree-house",level:2},{value:"Swapping indices",id:"swapping-indices",level:4},{value:"Indexing <code>Vec</code>",id:"indexing-vec",level:4},{value:"Issues",id:"issues",level:5},{value:"Checking bounds",id:"checking-bounds",level:4},{value:"Solution",id:"solution",level:3},{value:"Day 9: Rope Bridge",id:"day-9-rope-bridge",level:2},{value:"Solution",id:"solution-1",level:3},{value:"Day 10: Cathode-Ray Tube",id:"day-10-cathode-ray-tube",level:2},{value:"What does that mean though?",id:"what-does-that-mean-though",level:6},{value:"Solution",id:"solution-2",level:3},{value:"Day 11: Monkey in the Middle",id:"day-11-monkey-in-the-middle",level:2},{value:"Solution",id:"solution-3",level:3},{value:"Day 12: Hill Climbing Algorithm",id:"day-12-hill-climbing-algorithm",level:2},{value:"Solution",id:"solution-4",level:3},{value:"Day 13: Distress Signal",id:"day-13-distress-signal",level:2},{value:"Solution",id:"solution-5",level:3},{value:"Day 14: Regolith Reservoir",id:"day-14-regolith-reservoir",level:2},{value:"Solution",id:"solution-6",level:3},{value:"Post Mortem",id:"post-mortem",level:2},{value:"Indexing",id:"indexing",level:3},{value:"Cause of the problem",id:"cause-of-the-problem",level:4}];function c(e){const n={a:"a",admonition:"admonition",annotation:"annotation",blockquote:"blockquote",code:"code",em:"em",h2:"h2",h3:"h3",h4:"h4",h5:"h5",h6:"h6",li:"li",math:"math",mdxAdmonitionTitle:"mdxAdmonitionTitle",mi:"mi",mn:"mn",mo:"mo",mrow:"mrow",p:"p",pre:"pre",semantics:"semantics",span:"span",strong:"strong",ul:"ul",...(0,s.a)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsxs)(n.p,{children:["Let's go through the second week of ",(0,i.jsx)(n.a,{href:"https://adventofcode.com",children:(0,i.jsx)(n.em,{children:"Advent of Code"})})," in Rust."]}),"\n",(0,i.jsx)(n.h2,{id:"day-8-treetop-tree-house",children:(0,i.jsx)(n.a,{href:"https://adventofcode.com/2022/day/8",children:"Day 8: Treetop Tree House"})}),"\n",(0,i.jsx)(n.admonition,{title:"tl;dr",type:"info",children:(0,i.jsx)(n.p,{children:"We get a forest and we want to know how many trees are visible from the outside.\nApart from that we want to find the best view."})}),"\n",(0,i.jsxs)(n.p,{children:["Nothing interesting. We are moving around 2D map though. And indexing can get a\nbit painful when doing so, let's refactor it a bit ;) During the preparation for\nthe AoC, I have written ",(0,i.jsx)(n.code,{children:"Vector2D"})," and now it's time to extend it with indexing\nof ",(0,i.jsx)(n.code,{children:"Vec"})," of ",(0,i.jsx)(n.code,{children:"Vec"}),"s. In my solution I was manipulating with indices in the following\nway:"]}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"swapping them"}),"\n",(0,i.jsxs)(n.li,{children:["checking whether they are correct indices for the ",(0,i.jsx)(n.code,{children:"Vec<Vec<T>>"})]}),"\n",(0,i.jsxs)(n.li,{children:["indexing ",(0,i.jsx)(n.code,{children:"Vec<Vec<T>>"})," with them"]}),"\n"]}),"\n",(0,i.jsx)(n.admonition,{title:"caution",type:"warning",children:(0,i.jsxs)(n.p,{children:["I'm getting familiar with Rust and starting to \u201cabuse\u201d it\u2026 While doing so, I'm\nalso uncovering some \u201cfeatures\u201d that I don't really like. Therefore I will mark\nall of my rants with ",(0,i.jsx)(n.em,{children:"thicc"})," ",(0,i.jsx)(n.strong,{children:"\xab\u21af\xbb"})," mark and will try to \u201clock\u201d them into their\nown \u201cbox of hell\u201d."]})}),"\n",(0,i.jsx)(n.h4,{id:"swapping-indices",children:"Swapping indices"}),"\n",(0,i.jsx)(n.p,{children:"Relatively simple implementation, just take the values, swap them and return new\nvector."}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:"impl<T: Copy> Vector2D<T> {\n pub fn swap(&self) -> Self {\n Self {\n x: self.y,\n y: self.x,\n }\n }\n}\n"})}),"\n",(0,i.jsxs)(n.p,{children:["Pretty straight-forward implementation, but let's talk about the ",(0,i.jsx)(n.code,{children:"T: Copy"}),". We\nneed to use it, since we are returning a ",(0,i.jsx)(n.strong,{children:"new"})," vector, with swapped ",(0,i.jsx)(n.strong,{children:"values"}),".\nIf we had values that cannot be copied, the only thing we could do, would be a\nvector of references (and it would also introduce a lifetime, to which we'll get\nlater on). This is pretty similar with the operations on sets from the first week."]}),"\n",(0,i.jsxs)(n.h4,{id:"indexing-vec",children:["Indexing ",(0,i.jsx)(n.code,{children:"Vec"})]}),"\n",(0,i.jsx)(n.p,{children:"I will start with the indexing, cause bound-checking is a bit more\u2026 complicated\nthan I would like to."}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:"pub fn index<'a, T, U>(v: &'a [Vec<U>], idx: &Vector2D<T>) -> &'a U\nwhere\n usize: TryFrom<T>,\n <usize as TryFrom<T>>::Error: Debug,\n T: Copy,\n{\n let (x, y): (usize, usize) = (idx.x.try_into().unwrap(), idx.y.try_into().unwrap());\n &v[y][x]\n}\n"})}),"\n",(0,i.jsxs)(n.p,{children:["Let's talk about this mess\u2026 Body of the function is probably the most easy part\nand should not be hard to understand, we just take the ",(0,i.jsx)(n.code,{children:"x"})," and ",(0,i.jsx)(n.code,{children:"y"})," and convert\nthem both to ",(0,i.jsx)(n.code,{children:"usize"})," type that can be used later on for indexing."]}),"\n",(0,i.jsxs)(n.p,{children:["The type signature of the function is where the fun is at ","\ud83d\ude09"," We are trying\nto convert unknown type to ",(0,i.jsx)(n.code,{children:"usize"}),", so we must bound the ",(0,i.jsx)(n.code,{children:"T"})," as a type that can\nbe converted to ",(0,i.jsx)(n.code,{children:"usize"}),", that's how we got ",(0,i.jsx)(n.code,{children:"usize: TryFrom<T>"})," which basically\nsays that ",(0,i.jsx)(n.code,{children:"usize"})," must implement ",(0,i.jsx)(n.code,{children:"TryFrom<T>"})," trait and therefore allows us to\nconvert the indices to actual ",(0,i.jsx)(n.code,{children:"usize"})," indices. Using ",(0,i.jsx)(n.code,{children:".unwrap()"})," also forces us\nto bound the error that can occur when converting ",(0,i.jsx)(n.code,{children:"T"})," into ",(0,i.jsx)(n.code,{children:"usize"}),", that's how\nwe get ",(0,i.jsx)(n.code,{children:"<usize as TryFrom<T>>::Error: Debug"})," which loosely means"]}),"\n",(0,i.jsxs)(n.blockquote,{children:["\n",(0,i.jsxs)(n.p,{children:["error during conversion of ",(0,i.jsx)(n.code,{children:"T"})," into ",(0,i.jsx)(n.code,{children:"usize"})," must implement ",(0,i.jsx)(n.code,{children:"Debug"}),",\ni.e. can be printed in some way or other"]}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"T: Copy"})," is required by ",(0,i.jsx)(n.code,{children:".try_into()"})," which takes ",(0,i.jsx)(n.code,{children:"T"})," by-value."]}),"\n",(0,i.jsx)(n.p,{children:"And now we are left only with the first line of the definition."}),"\n",(0,i.jsx)(n.admonition,{type:"note",children:(0,i.jsx)(n.p,{children:"Skilled Rustaceans might notice that this implementation is rather flaky and can\nbreak in multiple places at once. I'll get back to it\u2026"})}),"\n",(0,i.jsx)(n.p,{children:"Let's split it in multiple parts:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"v: &'a [Vec<U>]"})," represents the 2D ",(0,i.jsx)(n.code,{children:"Vec"}),", we are indexing, ",(0,i.jsx)(n.code,{children:"Vec"})," implements\n",(0,i.jsx)(n.code,{children:"Slice"})," trait and ",(0,i.jsx)(n.em,{children:"clippy"})," recommends using ",(0,i.jsx)(n.code,{children:"&[T]"})," to ",(0,i.jsx)(n.code,{children:"&Vec<T>"}),", exact details\nare unknown to me"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"idx: &Vector2D<T>"})," represents the ",(0,i.jsx)(n.em,{children:"indices"})," which we use, we take them by\nreference to avoid an unnecessary copy"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"-> &'a U"})," means that we are returning a ",(0,i.jsx)(n.em,{children:"reference"})," to some value of type ",(0,i.jsx)(n.code,{children:"U"}),".\nNow the question is what does the ",(0,i.jsx)(n.code,{children:"'a"})," mean, we can also see it as a generic\ntype declared along ",(0,i.jsx)(n.code,{children:"T"})," and ",(0,i.jsx)(n.code,{children:"U"}),". And the answer is ",(0,i.jsx)(n.em,{children:"relatively"})," simple, ",(0,i.jsx)(n.code,{children:"'a"}),"\nrepresents a ",(0,i.jsx)(n.em,{children:"lifetime"}),". We take the ",(0,i.jsx)(n.code,{children:"v"})," by a reference and return a reference,\nborrow checker validates all of the ",(0,i.jsx)(n.em,{children:"borrows"})," (or references), so we need to\nspecify that our returned value has ",(0,i.jsx)(n.em,{children:"the same lifetime"})," as the vector we have\ntaken by a reference, i.e. returned reference must live at least as long as the\n",(0,i.jsx)(n.code,{children:"v"}),". This way we can \u201cbe sure\u201d that the returned reference is valid."]}),"\n"]}),"\n",(0,i.jsx)(n.h5,{id:"issues",children:"Issues"}),"\n",(0,i.jsxs)(n.p,{children:["First issue that our implementation has is the fact that we cannot get a mutable\nreference out of that function. This could be easily resolved by introducing new\nfunction, e.g. ",(0,i.jsx)(n.code,{children:"index_mut"}),". Which I have actually done while writing this part:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:"pub fn index_mut<'a, T, U>(v: &'a mut [Vec<U>], idx: &Vector2D<T>) -> &'a mut U\nwhere\n usize: TryFrom<T>,\n <usize as TryFrom<T>>::Error: Debug,\n T: Copy,\n{\n let (x, y): (usize, usize) = (idx.x.try_into().unwrap(), idx.y.try_into().unwrap());\n &mut v[y][x]\n}\n"})}),"\n",(0,i.jsxs)(n.admonition,{type:"warning",children:[(0,i.jsxs)(n.mdxAdmonitionTitle,{children:[(0,i.jsx)(n.strong,{children:"\xab\u21af\xbb"})," Why can't we use one function?"]}),(0,i.jsxs)(n.p,{children:["When we consider a ",(0,i.jsx)(n.code,{children:"Vec<T>"}),", we don't need to consider containers as ",(0,i.jsx)(n.code,{children:"T"}),", Rust\nimplements indexing as traits ",(0,i.jsx)(n.code,{children:"Index<T>"})," and ",(0,i.jsx)(n.code,{children:"IndexMut<T>"})," that do the dirty work\nbehind syntactic sugar of ",(0,i.jsx)(n.code,{children:"container[idx]"}),"."]}),(0,i.jsxs)(n.p,{children:["However, implementing of traits is not allowed for ",(0,i.jsx)(n.em,{children:"external"})," types, i.e. types\nthat you haven't defined yourself. This means that you can implement indexing\nover containers that you have implemented yourself, but you cannot use your own\ntypes for indexing \u201cbuilt-in\u201d types."]}),(0,i.jsxs)(n.p,{children:["Another part of this rabbit hole is trait ",(0,i.jsx)(n.code,{children:"SliceIndex<T>"})," that is of a relevance\nbecause of"]}),(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:"impl<T, I> Index<I> for [T]\nwhere\n I: SliceIndex<[T]>\n\nimpl<T, I, A> Index<I> for Vec<T, A>\nwhere\n I: SliceIndex<[T]>,\n A: Allocator\n\nimpl<T, I, const N: usize> Index<I> for [T; N]\nwhere\n [T]: Index<I>\n"})}),(0,i.jsxs)(n.p,{children:["In other words, if your type implements ",(0,i.jsx)(n.code,{children:"SliceIndex<T>"})," trait, it can be used\nfor indexing. As of now, this trait has all of its required methods experimental\nand is marked as ",(0,i.jsx)(n.code,{children:"unsafe"}),"."]})]}),"\n",(0,i.jsxs)(n.p,{children:["Another problem is a requirement for indexing either ",(0,i.jsx)(n.code,{children:"[Vec<T>]"})," or ",(0,i.jsx)(n.code,{children:"Vec<Vec<T>>"}),".\nThis requirement could be countered by removing inner type ",(0,i.jsx)(n.code,{children:"Vec<T>"})," and constraining\nit by a trait ",(0,i.jsx)(n.code,{children:"Index"})," (or ",(0,i.jsx)(n.code,{children:"IndexMut"})," respectively) in a following way"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:"pub fn index<'a, C, T>(v: &'a [C], idx: &Vector2D<T>) -> &'a C::Output\nwhere\n usize: TryFrom<T>,\n <usize as TryFrom<T>>::Error: Debug,\n T: Copy,\n C: Index<usize>\n{\n let (x, y): (usize, usize) = (idx.x.try_into().unwrap(), idx.y.try_into().unwrap());\n &v[y][x]\n}\n"})}),"\n",(0,i.jsxs)(n.p,{children:["Given this, we can also give a more meaningful typename for indexing type, such\nas ",(0,i.jsx)(n.code,{children:"I"}),"."]}),"\n",(0,i.jsx)(n.h4,{id:"checking-bounds",children:"Checking bounds"}),"\n",(0,i.jsxs)(n.p,{children:["Now we can get to the boundary checks, it is very similar, but a more\u2026 dirty.\nFirst approach that came up was to convert the indices in ",(0,i.jsx)(n.code,{children:"Vector2D"})," to ",(0,i.jsx)(n.code,{children:"usize"}),",\nbut when you add the indices up, e.g. when checking the neighbors, you can end\nup with negative values which, unlike in C++, causes an error (instead of underflow\nthat you can use to your advantage; you can easily guess how)."]}),"\n",(0,i.jsx)(n.p,{children:"So how can we approach this then? Well\u2026 we will convert the bounds instead of\nthe indices and that lead us to:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:"pub fn in_range<T, U>(v: &[Vec<U>], idx: &Vector2D<T>) -> bool\nwhere\n usize: TryInto<T>,\n <usize as TryInto<T>>::Error: Debug,\n T: PartialOrd + Copy,\n{\n idx.y >= 0.try_into().unwrap()\n && idx.y < v.len().try_into().unwrap()\n && idx.x >= 0.try_into().unwrap()\n && idx.x\n < v[TryInto::<usize>::try_into(idx.y).unwrap()]\n .len()\n .try_into()\n .unwrap()\n}\n"})}),"\n",(0,i.jsxs)(n.p,{children:["You can tell that it's definitely a shitty code. Let's improve it now! We will\nget back to the original idea, but do it better. We know that we cannot convert\nnegative values into ",(0,i.jsx)(n.code,{children:"usize"}),", ",(0,i.jsx)(n.strong,{children:"but"})," we also know that conversion like that\nreturns a ",(0,i.jsx)(n.code,{children:"Result<T, E>"})," which we can use to our advantage."]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:"pub fn in_range<T, U>(v: &[Vec<U>], idx: &Vector2D<T>) -> bool\nwhere\n T: Copy,\n usize: TryFrom<T>,\n{\n usize::try_from(idx.y)\n .and_then(|y| usize::try_from(idx.x).map(|x| y < v.len() && x < v[y].len()))\n .unwrap_or(false)\n}\n"})}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"Result<T, E>"})," is a type similar to ",(0,i.jsx)(n.code,{children:"Either"})," in Haskell and it allows us to chain\nmultiple operations on correct results or propagate the original error without\ndoing anything. Let's dissect it one-by-one."]}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"try_from"})," is a method implemented in ",(0,i.jsx)(n.code,{children:"TryFrom"})," trait, that allows you to convert\ntypes and either successfully convert them or fail (with a reasonable error). This\nmethod returns ",(0,i.jsx)(n.code,{children:"Result<T, E>"}),"."]}),"\n",(0,i.jsxs)(n.p,{children:["We call ",(0,i.jsx)(n.code,{children:"and_then"})," on that ",(0,i.jsx)(n.em,{children:"result"}),", let's have a look at the type signature of\n",(0,i.jsx)(n.code,{children:"and_then"}),", IMO it explains more than enough:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:"pub fn and_then<U, F>(self, op: F) -> Result<U, E>\nwhere\n F: FnOnce(T) -> Result<U, E>\n"})}),"\n",(0,i.jsx)(n.p,{children:"OK\u2026 So it takes the result and a function and returns another result with\ndifferent value and different error. However we can see that the function, which\nrepresents an operation on a result, takes just the value, i.e. it doesn't care\nabout any previous error. To make it short:"}),"\n",(0,i.jsxs)(n.blockquote,{children:["\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"and_then"})," allows us to run an operation, which can fail, on the correct result"]}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:["We parsed a ",(0,i.jsx)(n.code,{children:"y"})," index and now we try to convert the ",(0,i.jsx)(n.code,{children:"x"})," index with ",(0,i.jsx)(n.code,{children:"try_from"}),"\nagain, but on that result we use ",(0,i.jsx)(n.code,{children:"map"})," rather than ",(0,i.jsx)(n.code,{children:"and_then"}),", why would that be?"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:"pub fn map<U, F>(self, op: F) -> Result<U, E>\nwhere\n F: FnOnce(T) -> U\n"})}),"\n",(0,i.jsxs)(n.p,{children:["Huh\u2026 ",(0,i.jsx)(n.code,{children:"map"})," performs an operation that ",(0,i.jsx)(n.strong,{children:"cannot"})," fail. And finally we use\n",(0,i.jsx)(n.code,{children:"unwrap_or"})," which takes the value from result, or in case of an error returns the\ndefault that we define."]}),"\n",(0,i.jsxs)(n.p,{children:["How does this work then? If ",(0,i.jsx)(n.code,{children:"y"})," is negative, the conversion fails and the error\npropagates all the way to ",(0,i.jsx)(n.code,{children:"unwrap_or"}),", if ",(0,i.jsx)(n.code,{children:"y"})," can be a correct ",(0,i.jsx)(n.code,{children:"usize"})," value, then\nwe do the same with ",(0,i.jsx)(n.code,{children:"x"}),". If ",(0,i.jsx)(n.code,{children:"x"})," is negative, we propagate the error as with ",(0,i.jsx)(n.code,{children:"y"}),",\nand if it's not, then we check whether it exceeds the higher bounds or not."]}),"\n",(0,i.jsx)(n.h3,{id:"solution",children:"Solution"}),"\n",(0,i.jsx)(n.p,{children:"Relatively simple, you just need follow the rules and not get too smart, otherwise\nit will get back at you."}),"\n",(0,i.jsx)(n.h2,{id:"day-9-rope-bridge",children:(0,i.jsx)(n.a,{href:"https://adventofcode.com/2022/day/9",children:"Day 9: Rope Bridge"})}),"\n",(0,i.jsx)(n.admonition,{title:"tl;dr",type:"info",children:(0,i.jsx)(n.p,{children:"We get a rope with knots and we want to track how many different positions are\nvisited with the rope's tail."})}),"\n",(0,i.jsx)(n.p,{children:"By this day, I have come to a conclusion that current skeleton for each day\ngenerates a lot of boilerplate. And even though it can be easily copied, it's\njust a waste of space and unnecessary code. Let's \u201csimplify\u201d this (on one end\nwhile creating monster on the other end). I've gone through what we need in the\npreparations for the AoC. Let's sum up our requirements:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"parsing"}),"\n",(0,i.jsx)(n.li,{children:"part 1 & 2"}),"\n",(0,i.jsx)(n.li,{children:"running on sample / input"}),"\n",(0,i.jsx)(n.li,{children:"tests"}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:"Parsing and implementation of both parts is code that changes each day and we\ncannot do anything about it. However running and testing can be simplified!"}),"\n",(0,i.jsxs)(n.p,{children:["Let's introduce and export a new module ",(0,i.jsx)(n.code,{children:"solution"})," that will take care of all of\nthis. We will start by introducing a trait for each day."]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:"pub trait Solution<Input, Output: Display> {\n fn parse_input<P: AsRef<Path>>(pathname: P) -> Input;\n\n fn part_1(input: &Input) -> Output;\n fn part_2(input: &Input) -> Output;\n}\n"})}),"\n",(0,i.jsxs)(n.p,{children:["This does a lot of work for us already, we have defined a trait and for each day\nwe will create a structure representing a specific day. That structure will also\nimplement the ",(0,i.jsx)(n.code,{children:"Solution"})," trait."]}),"\n",(0,i.jsxs)(n.p,{children:["Now we need to get rid of the boilerplate, we can't get rid of the ",(0,i.jsx)(n.code,{children:"main"})," function,\nbut we can at least move out the functionality."]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:'fn run(type_of_input: &str) -> Result<()>\nwhere\n Self: Sized,\n{\n tracing_subscriber::fmt()\n .with_env_filter(EnvFilter::from_default_env())\n .with_target(false)\n .with_file(true)\n .with_line_number(true)\n .without_time()\n .compact()\n .init();\n color_eyre::install()?;\n\n let input = Self::parse_input(format!("{}s/{}.txt", type_of_input, Self::day()));\n\n info!("Part 1: {}", Self::part_1(&input));\n info!("Part 2: {}", Self::part_2(&input));\n\n Ok(())\n}\n\nfn main() -> Result<()>\nwhere\n Self: Sized,\n{\n Self::run("input")\n}\n'})}),"\n",(0,i.jsxs)(n.p,{children:["This is all part of the ",(0,i.jsx)(n.code,{children:"Solution"})," trait, which can implement methods while being\ndependent on what is provided by the implementing types. In this case, we just\nneed to bound the ",(0,i.jsx)(n.code,{children:"Output"})," type to implement ",(0,i.jsx)(n.code,{children:"Display"})," that is necessary for the\n",(0,i.jsx)(n.code,{children:"info!"})," and format string there."]}),"\n",(0,i.jsxs)(n.p,{children:["Now we can get to first of the nasty things we are going to do\u2026 And it is the\n",(0,i.jsx)(n.code,{children:"day()"})," method that you can see being used when constructing path to the input\nfile. That method will generate a name of the file, e.g. ",(0,i.jsx)(n.code,{children:"day01"})," and we know that\nwe can ",(0,i.jsx)(n.em,{children:"somehow"})," deduce it from the structure name, given we name it reasonably."]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:'fn day() -> String {\n let mut day = String::from(type_name::<Self>().split("::").next().unwrap());\n day.make_ascii_lowercase();\n\n day.to_string()\n}\n'})}),"\n",(0,i.jsxs)(n.admonition,{type:"warning",children:[(0,i.jsx)(n.mdxAdmonitionTitle,{children:(0,i.jsx)(n.code,{children:"type_name"})}),(0,i.jsx)(n.p,{children:"This feature is still experimental and considered to be internal, it is not\nadvised to use it any production code."})]}),"\n",(0,i.jsxs)(n.p,{children:["And now we can get to the nastiest stuff ","\ud83d\ude29"," We will ",(0,i.jsx)(n.strong,{children:"generate"})," the tests!"]}),"\n",(0,i.jsx)(n.p,{children:"We want to be able to generate tests for sample input in a following way:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:"test_sample!(day_01, Day01, 42, 69);\n"})}),"\n",(0,i.jsx)(n.p,{children:"There's not much we can do, so we will write a macro to generate the tests for us."}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:'#[macro_export]\nmacro_rules! test_sample {\n ($mod_name:ident, $day_struct:tt, $part_1:expr, $part_2:expr) => {\n #[cfg(test)]\n mod $mod_name {\n use super::*;\n\n #[test]\n fn test_part_1() {\n let sample =\n $day_struct::parse_input(&format!("samples/{}.txt", $day_struct::day()));\n assert_eq!($day_struct::part_1(&sample), $part_1);\n }\n\n #[test]\n fn test_part_2() {\n let sample =\n $day_struct::parse_input(&format!("samples/{}.txt", $day_struct::day()));\n assert_eq!($day_struct::part_2(&sample), $part_2);\n }\n }\n };\n}\n'})}),"\n",(0,i.jsxs)(n.p,{children:["We have used it in a similar way as macros in C/C++, one of the things that we\ncan use to our advantage is defining \u201ctype\u201d of the parameters for the macro. All\nparameters have their name prefixed with ",(0,i.jsx)(n.code,{children:"$"})," sign and you can define various \u201cforms\u201d\nof your macro. Let's go through it!"]}),"\n",(0,i.jsx)(n.p,{children:"We have following parameters:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"$mod_name"})," which represents the name for the module with tests, it is typed\nwith ",(0,i.jsx)(n.code,{children:"ident"})," which means that we want a valid identifier to be passed in."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"$day_struct"})," represents the structure that will be used for tests, it is typed\nwith ",(0,i.jsx)(n.code,{children:"tt"})," which represents a ",(0,i.jsx)(n.em,{children:"token tree"}),", in our case it is a type."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"$part_X"})," represents the expected output for the ",(0,i.jsx)(n.code,{children:"X"}),"th part and is of type ",(0,i.jsx)(n.code,{children:"expr"}),"\nwhich literally means an ",(0,i.jsx)(n.em,{children:"expression"}),"."]}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:["Apart from that we need to use ",(0,i.jsx)(n.code,{children:"#[macro_export]"})," to mark the macro as exported\nfor usage outside of the module. Now our skeleton looks like:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:'use aoc_2022::*;\n\ntype Input = String;\ntype Output = String;\n\nstruct DayXX;\nimpl Solution<Input, Output> for DayXX {\n fn parse_input<P: AsRef<Path>>(pathname: P) -> Input {\n file_to_string(pathname)\n }\n\n fn part_1(input: &Input) -> Output {\n todo!()\n }\n\n fn part_2(input: &Input) -> Output {\n todo!()\n }\n}\n\nfn main() -> Result<()> {\n // DayXX::run("sample")\n DayXX::main()\n}\n\n// test_sample!(day_XX, DayXX, , );\n'})}),"\n",(0,i.jsx)(n.h3,{id:"solution-1",children:"Solution"}),"\n",(0,i.jsx)(n.p,{children:"Not much to talk about, it is relatively easy to simulate."}),"\n",(0,i.jsx)(n.h2,{id:"day-10-cathode-ray-tube",children:(0,i.jsx)(n.a,{href:"https://adventofcode.com/2022/day/10",children:"Day 10: Cathode-Ray Tube"})}),"\n",(0,i.jsx)(n.admonition,{title:"tl;dr",type:"info",children:(0,i.jsx)(n.p,{children:"Emulating basic arithmetic operations on a CPU and drawing on CRT based on the\nCPU's accumulator."})}),"\n",(0,i.jsxs)(n.p,{children:["In this day I have discovered an issue with my design of the ",(0,i.jsx)(n.code,{children:"Solution"})," trait.\nAnd the issue is caused by different types of ",(0,i.jsx)(n.code,{children:"Output"})," for the part 1 and part 2."]}),"\n",(0,i.jsx)(n.p,{children:"Problem is relatively simple and consists of simulating a CPU, I have approached\nit in a following way:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:"fn evaluate_instructions(instructions: &[Instruction], mut out: Output) -> Output {\n instructions\n .iter()\n .fold(State::new(), |state, instruction| {\n state.execute(instruction, &mut out)\n });\n\n out\n}\n"})}),"\n",(0,i.jsxs)(n.p,{children:["We just take the instructions, we have some state of the CPU and we execute the\ninstructions one-by-one. Perfect usage of the ",(0,i.jsx)(n.code,{children:"fold"})," (or ",(0,i.jsx)(n.code,{children:"reduce"})," as you may know\nit from other languages)."]}),"\n",(0,i.jsxs)(n.p,{children:["You can also see that we have an ",(0,i.jsx)(n.code,{children:"Output"})," type, so the question is how can we fix\nthat problem. And the answer is very simple and ",(0,i.jsx)(n.em,{children:"functional"}),". Rust allows you to\nhave an ",(0,i.jsx)(n.code,{children:"enumeration"})," that can ",(0,i.jsx)(n.em,{children:"bear"})," some other values apart from the type itself."]}),"\n",(0,i.jsxs)(n.admonition,{type:"tip",children:[(0,i.jsxs)(n.p,{children:["We could've seen something like this with the ",(0,i.jsx)(n.code,{children:"Result<T, E>"})," type that can be\ndefined as"]}),(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:"enum Result<T, E> {\n Ok(T),\n Err(E)\n}\n"})}),(0,i.jsx)(n.h6,{id:"what-does-that-mean-though",children:"What does that mean though?"}),(0,i.jsxs)(n.p,{children:["When we have an ",(0,i.jsx)(n.code,{children:"Ok"})," value, it has the result itself, and when we get an ",(0,i.jsx)(n.code,{children:"Err"}),"\nvalue, it has the error. This also allows us to handle ",(0,i.jsx)(n.em,{children:"results"})," in a rather\npretty way:"]}),(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:'match do_something(x) {\n Ok(y) => {\n println!("SUCCESS: {}", y);\n },\n Err(y) => {\n eprintln!("ERROR: {}", y);\n }\n}\n'})})]}),"\n",(0,i.jsx)(n.p,{children:"My solution has a following outline:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:"fn execute(&self, i: &Instruction, output: &mut Output) -> State {\n // execute the instruction\n\n // collect results if necessary\n match output {\n Output::Part1(x) => self.execute_part_1(y, x),\n Output::Part2(x) => self.execute_part_2(y, x),\n }\n\n // return the obtained state\n new_state\n}\n"})}),"\n",(0,i.jsxs)(n.p,{children:["You might think that it's a perfectly reasonable thing to do. Yes, ",(0,i.jsx)(n.strong,{children:"but"})," notice\nthat the ",(0,i.jsx)(n.code,{children:"match"})," statement doesn't ",(0,i.jsx)(n.em,{children:"collect"})," the changes in any way and also we\npass ",(0,i.jsx)(n.code,{children:"output"})," by ",(0,i.jsx)(n.code,{children:"&mut"}),", so it is shared across each ",(0,i.jsx)(n.em,{children:"iteration"})," of the ",(0,i.jsx)(n.code,{children:"fold"}),"."]}),"\n",(0,i.jsxs)(n.p,{children:["The dirty and ingenious thing is that ",(0,i.jsx)(n.code,{children:"x"}),"s are passed by ",(0,i.jsx)(n.code,{children:"&mut"})," too and therefore\nthey are directly modified by the helper functions. To sum it up and let it sit"]}),"\n",(0,i.jsxs)(n.blockquote,{children:["\n",(0,i.jsxs)(n.p,{children:["We are ",(0,i.jsx)(n.strong,{children:"collecting"})," the result ",(0,i.jsx)(n.strong,{children:"into"})," an ",(0,i.jsx)(n.strong,{children:"enumeration"})," that is ",(0,i.jsx)(n.strong,{children:"shared"}),"\nacross ",(0,i.jsx)(n.strong,{children:"all"})," iterations of ",(0,i.jsx)(n.code,{children:"fold"}),"."]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"solution-2",children:"Solution"}),"\n",(0,i.jsxs)(n.p,{children:["Similar to ",(0,i.jsx)(n.em,{children:"Day 9"}),", but there are some technical details that can get you."]}),"\n",(0,i.jsx)(n.h2,{id:"day-11-monkey-in-the-middle",children:(0,i.jsx)(n.a,{href:"https://adventofcode.com/2022/day/11",children:"Day 11: Monkey in the Middle"})}),"\n",(0,i.jsx)(n.admonition,{title:"tl;dr",type:"info",children:(0,i.jsx)(n.p,{children:"Simulation of monkeys throwing stuff around and measuring your stress levels\nwhile your stuff is being passed around."})}),"\n",(0,i.jsx)(n.p,{children:"I think I decided to use regular expressions here for the first time, cause\nparsing the input was a pain."}),"\n",(0,i.jsx)(n.p,{children:"Also I didn't expect to implement Euclidean algorithm in Rust\u2026"}),"\n",(0,i.jsx)(n.h3,{id:"solution-3",children:"Solution"}),"\n",(0,i.jsx)(n.p,{children:"Again, we're just running a simulation. Though I must admit it was very easy to\nmake a small technical mistakes that could affect the final results very late."}),"\n",(0,i.jsx)(n.h2,{id:"day-12-hill-climbing-algorithm",children:(0,i.jsx)(n.a,{href:"https://adventofcode.com/2022/day/12",children:"Day 12: Hill Climbing Algorithm"})}),"\n",(0,i.jsx)(n.admonition,{title:"tl;dr",type:"info",children:(0,i.jsx)(n.p,{children:"Finding shortest path up the hill and also shortest path down to the ground while\nalso rolling down the hill\u2026"})}),"\n",(0,i.jsxs)(n.p,{children:["As I have said in the ",(0,i.jsx)(n.em,{children:"tl;dr"}),", we are looking for the shortest path, but the start\nand goal differ for the part 1 and 2. So I have decided to refactor my solution\nto a BFS algorithm that takes necessary parameters via functions:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:"fn bfs<F, G>(\n graph: &[Vec<char>], start: &Position, has_edge: F, is_target: G\n) -> Option<usize>\nwhere\n F: Fn(&[Vec<char>], &Position, &Position) -> bool,\n G: Fn(&[Vec<char>], &Position) -> bool\n"})}),"\n",(0,i.jsxs)(n.p,{children:["We pass the initial vertex from the caller and everything else is left to the BFS\nalgorithm, based on the ",(0,i.jsx)(n.code,{children:"has_edge"})," and ",(0,i.jsx)(n.code,{children:"is_target"})," functions."]}),"\n",(0,i.jsxs)(n.p,{children:["This was easy! And that is not very usual in Rust once you want to pass around\nfunctions. ","\ud83d\udc40"]}),"\n",(0,i.jsx)(n.h3,{id:"solution-4",children:"Solution"}),"\n",(0,i.jsxs)(n.p,{children:["Looking for the shortest path\u2026 Must be Dijkstra, right? ",(0,i.jsx)(n.strong,{children:"Nope!"})," Half of the\nReddit got jebaited though. In all fairness, nothing stops you from implementing\nthe Dijkstra's algorithm for finding the shortest path, ",(0,i.jsx)(n.strong,{children:"but"})," if you know that\nall connected vertices are in a unit (actually ",(0,i.jsxs)(n.span,{className:"katex",children:[(0,i.jsx)(n.span,{className:"katex-mathml",children:(0,i.jsx)(n.math,{xmlns:"http://www.w3.org/1998/Math/MathML",children:(0,i.jsxs)(n.semantics,{children:[(0,i.jsxs)(n.mrow,{children:[(0,i.jsx)(n.mi,{children:"d"}),(0,i.jsx)(n.mo,{children:"="}),(0,i.jsx)(n.mn,{children:"1"})]}),(0,i.jsx)(n.annotation,{encoding:"application/x-tex",children:"d = 1"})]})})}),(0,i.jsxs)(n.span,{className:"katex-html","aria-hidden":"true",children:[(0,i.jsxs)(n.span,{className:"base",children:[(0,i.jsx)(n.span,{className:"strut",style:{height:"0.6944em"}}),(0,i.jsx)(n.span,{className:"mord mathnormal",children:"d"}),(0,i.jsx)(n.span,{className:"mspace",style:{marginRight:"0.2778em"}}),(0,i.jsx)(n.span,{className:"mrel",children:"="}),(0,i.jsx)(n.span,{className:"mspace",style:{marginRight:"0.2778em"}})]}),(0,i.jsxs)(n.span,{className:"base",children:[(0,i.jsx)(n.span,{className:"strut",style:{height:"0.6444em"}}),(0,i.jsx)(n.span,{className:"mord",children:"1"})]})]})]}),") distance from each other,\nthen you know that running Dijkstra is equivalent to running BFS, only with worse\ntime complexity, because of the priority heap instead of the queue."]}),"\n",(0,i.jsx)(n.h2,{id:"day-13-distress-signal",children:(0,i.jsx)(n.a,{href:"https://adventofcode.com/2022/day/13",children:"Day 13: Distress Signal"})}),"\n",(0,i.jsx)(n.admonition,{title:"tl;dr",type:"info",children:(0,i.jsx)(n.p,{children:"Processing packets with structured data from the distress signal."})}),"\n",(0,i.jsxs)(n.p,{children:["You can implement a lot of traits if you want to. It is ",(0,i.jsx)(n.em,{children:"imperative"})," to implement\nordering on the packets. I had a typo, so I also proceeded to implement a ",(0,i.jsx)(n.code,{children:"Display"}),"\ntrait for debugging purposes:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:'impl Display for Packet {\n fn fmt(&self, f: &mut std::fmt::Formatter<\'_>) -> std::fmt::Result {\n match self {\n Packet::Integer(x) => write!(f, "{x}"),\n Packet::List(lst) => write!(f, "[{}]", lst.iter().map(|p| format!("{p}")).join(",")),\n }\n }\n}\n'})}),"\n",(0,i.jsx)(n.h3,{id:"solution-5",children:"Solution"}),"\n",(0,i.jsx)(n.p,{children:"A lot of technical details\u2026 Parsing is nasty too\u2026"}),"\n",(0,i.jsx)(n.h2,{id:"day-14-regolith-reservoir",children:(0,i.jsx)(n.a,{href:"https://adventofcode.com/2022/day/14",children:"Day 14: Regolith Reservoir"})}),"\n",(0,i.jsx)(n.admonition,{title:"tl;dr",type:"info",children:(0,i.jsx)(n.p,{children:"Let's simulate falling sand grain-by-grain."})}),"\n",(0,i.jsxs)(n.p,{children:["Again, both parts are relatively similar with minimal changes, so it is a good\nidea to refactor it a bit. Similar approach to the ",(0,i.jsx)(n.a,{href:"#day-12-hill-climbing-algorithm",children:"BFS above"}),". Also this is the\nfirst day where I ran into efficiency issues and had to redo my solution to speed\nit up just a bit."]}),"\n",(0,i.jsx)(n.h3,{id:"solution-6",children:"Solution"}),"\n",(0,i.jsx)(n.p,{children:"Tedious."}),"\n",(0,i.jsx)(n.h2,{id:"post-mortem",children:"Post Mortem"}),"\n",(0,i.jsx)(n.h3,{id:"indexing",children:"Indexing"}),"\n",(0,i.jsxs)(n.p,{children:["I was asked about the indexing after publishing the blog. And truly it is rather\ncomplicated topic, especially after releasing ",(0,i.jsx)(n.code,{children:"SliceIndex<I>"})," trait. I couldn't\nleave it be, so I tried to implement the ",(0,i.jsx)(n.code,{children:"Index"})," and ",(0,i.jsx)(n.code,{children:"IndexMut"})," trait."]}),"\n",(0,i.jsx)(n.admonition,{type:"note",children:(0,i.jsxs)(n.p,{children:["I have also mentioned that the ",(0,i.jsx)(n.code,{children:"SliceIndex"})," trait is ",(0,i.jsx)(n.code,{children:"unsafe"}),", but truth be told,\nonly ",(0,i.jsx)(n.em,{children:"unsafe"})," part are the 2 methods that are named ",(0,i.jsx)(n.code,{children:"*unchecked*"}),". Anyways, I will\nbe implementing the ",(0,i.jsx)(n.code,{children:"Index*"})," traits for now, rather than the ",(0,i.jsx)(n.code,{children:"SliceIndex"}),"."]})}),"\n",(0,i.jsx)(n.p,{children:"It's relatively straightforward\u2026"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:"impl<I, C> Index<Vector2D<I>> for [C]\nwhere\n I: Copy + TryInto<usize>,\n <I as TryInto<usize>>::Error: Debug,\n C: Index<usize>,\n{\n type Output = C::Output;\n\n fn index(&self, index: Vector2D<I>) -> &Self::Output {\n let (x, y): (usize, usize) =\n (index.x.try_into().unwrap(), index.y.try_into().unwrap());\n &self[y][x]\n }\n}\n\nimpl<I, C> IndexMut<Vector2D<I>> for [C]\nwhere\n I: Copy + TryInto<usize>,\n <I as TryInto<usize>>::Error: Debug,\n C: IndexMut<usize>,\n{\n fn index_mut(&mut self, index: Vector2D<I>) -> &mut Self::Output {\n let (x, y): (usize, usize) =\n (index.x.try_into().unwrap(), index.y.try_into().unwrap());\n &mut self[y][x]\n }\n}\n"})}),"\n",(0,i.jsxs)(n.p,{children:["We can see a lot of similarities to the implementation of ",(0,i.jsx)(n.code,{children:"index"})," and ",(0,i.jsx)(n.code,{children:"index_mut"}),"\nfunctions. In the end, they are 1:1, just wrapped in the trait that provides a\nsyntax sugar for ",(0,i.jsx)(n.code,{children:"container[idx]"}),"."]}),"\n",(0,i.jsxs)(n.admonition,{type:"note",children:[(0,i.jsxs)(n.p,{children:["I have also switched from using the ",(0,i.jsx)(n.code,{children:"TryFrom"})," to ",(0,i.jsx)(n.code,{children:"TryInto"})," trait, since it better\nmatches what we are using, the ",(0,i.jsx)(n.code,{children:".try_into"})," rather than ",(0,i.jsx)(n.code,{children:"usize::try_from"}),"."]}),(0,i.jsxs)(n.p,{children:["Also implementing ",(0,i.jsx)(n.code,{children:"TryFrom"})," automatically provides you with a ",(0,i.jsx)(n.code,{children:"TryInto"})," trait,\nsince it is relatively easy to implement. Just compare the following:"]}),(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:"pub trait TryFrom<T>: Sized {\n type Error;\n\n fn try_from(value: T) -> Result<Self, Self::Error>;\n}\n\npub trait TryInto<T>: Sized {\n type Error;\n\n fn try_into(self) -> Result<T, Self::Error>;\n}\n"})})]}),"\n",(0,i.jsxs)(n.p,{children:["OK, so we have our trait implemented, we should be able to use ",(0,i.jsx)(n.code,{children:"container[index]"}),",\nright? Yes\u2026 but actually no ","\ud83d\ude26"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{children:"error[E0277]: the type `[std::vec::Vec<i8>]` cannot be indexed by `aoc_2022::Vector2D<usize>`\n --\x3e src/bin/day08.rs:26:18\n |\n26 | if trees[pos] > tallest {\n | ^^^ slice indices are of type `usize` or ranges of `usize`\n |\n = help: the trait `std::slice::SliceIndex<[std::vec::Vec<i8>]>` is not implemented for `aoc_2022::Vector2D<usize>`\n = note: required for `std::vec::Vec<std::vec::Vec<i8>>` to implement `std::ops::Index<aoc_2022::Vector2D<usize>>`\n\nerror[E0277]: the type `[std::vec::Vec<i8>]` cannot be indexed by `aoc_2022::Vector2D<usize>`\n --\x3e src/bin/day08.rs:30:28\n |\n30 | max(tallest, trees[pos])\n | ^^^ slice indices are of type `usize` or ranges of `usize`\n |\n = help: the trait `std::slice::SliceIndex<[std::vec::Vec<i8>]>` is not implemented for `aoc_2022::Vector2D<usize>`\n = note: required for `std::vec::Vec<std::vec::Vec<i8>>` to implement `std::ops::Index<aoc_2022::Vector2D<usize>>`\n\nerror[E0277]: the type `[std::vec::Vec<i8>]` cannot be indexed by `aoc_2022::Vector2D<isize>`\n --\x3e src/bin/day08.rs:52:28\n |\n52 | let max_height = trees[position];\n | ^^^^^^^^ slice indices are of type `usize` or ranges of `usize`\n |\n = help: the trait `std::slice::SliceIndex<[std::vec::Vec<i8>]>` is not implemented for `aoc_2022::Vector2D<isize>`\n = note: required for `std::vec::Vec<std::vec::Vec<i8>>` to implement `std::ops::Index<aoc_2022::Vector2D<isize>>`\n"})}),"\n",(0,i.jsxs)(n.p,{children:["Why? We have it implemented for the slices (",(0,i.jsx)(n.code,{children:"[C]"}),"), why doesn't it work? Well,\nthe fun part consists of the fact that in other place, where we were using it,\nwe were passing the ",(0,i.jsx)(n.code,{children:"&[Vec<T>]"}),", but this is coming from a helper functions that\ntake ",(0,i.jsx)(n.code,{children:"&Vec<Vec<T>>"})," instead. And\u2026 we don't implement ",(0,i.jsx)(n.code,{children:"Index"})," and ",(0,i.jsx)(n.code,{children:"IndexMut"})," for\nthose. Just for the slices. \ud83e\udd2f ",(0,i.jsx)(n.em,{children:"What are we going to do about it?"})]}),"\n",(0,i.jsxs)(n.p,{children:["We can either start copy-pasting or be smarter about it\u2026 I choose to be smarter,\nso let's implement a macro! The only difference across the implementations are\nthe types of the outer containers. Implementation doesn't differ ",(0,i.jsx)(n.strong,{children:"at all"}),"!"]}),"\n",(0,i.jsx)(n.p,{children:"Implementing the macro can be done in a following way:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:"macro_rules! generate_indices {\n ($container:ty) => {\n impl<I, C> Index<Vector2D<I>> for $container\n where\n I: Copy + TryInto<usize>,\n <I as TryInto<usize>>::Error: Debug,\n C: Index<usize>,\n {\n type Output = C::Output;\n\n fn index(&self, index: Vector2D<I>) -> &Self::Output {\n let (x, y): (usize, usize) =\n (index.x.try_into().unwrap(), index.y.try_into().unwrap());\n &self[y][x]\n }\n }\n\n impl<I, C> IndexMut<Vector2D<I>> for $container\n where\n I: Copy + TryInto<usize>,\n <I as TryInto<usize>>::Error: Debug,\n C: IndexMut<usize>,\n {\n fn index_mut(&mut self, index: Vector2D<I>) -> &mut Self::Output {\n let (x, y): (usize, usize) =\n (index.x.try_into().unwrap(), index.y.try_into().unwrap());\n &mut self[y][x]\n }\n }\n };\n}\n"})}),"\n",(0,i.jsx)(n.p,{children:"And now we can simply do"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:"generate_indices!(VecDeque<C>);\ngenerate_indices!([C]);\ngenerate_indices!(Vec<C>);\n// generate_indices!([C; N], const N: usize);\n"})}),"\n",(0,i.jsxs)(n.p,{children:["The last type (I took the inspiration from the implementations of the ",(0,i.jsx)(n.code,{children:"Index"})," and\n",(0,i.jsx)(n.code,{children:"IndexMut"})," traits) is a bit problematic, because of the ",(0,i.jsx)(n.code,{children:"const N: usize"})," part,\nwhich I haven't managed to be able to parse. And that's how I got rid of the error."]}),"\n",(0,i.jsx)(n.admonition,{type:"note",children:(0,i.jsxs)(n.p,{children:["If I were to use 2D-indexing over ",(0,i.jsx)(n.code,{children:"[C; N]"})," slices, I'd probably just go with the\ncopy-paste, cause the cost of this \u201cmonstrosity\u201d outweighs the benefits of no DRY."]})}),"\n",(0,i.jsx)(n.h4,{id:"cause-of-the-problem",children:"Cause of the problem"}),"\n",(0,i.jsxs)(n.p,{children:["This issue is relatively funny. If you don't use any type aliases, just the raw\ntypes, you'll get suggested certain changes by the ",(0,i.jsx)(n.em,{children:"clippy"}),". For example if you\nconsider the following piece of code"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:'fn get_sum(nums: &Vec<i32>) -> i32 {\n nums.iter().sum()\n}\n\nfn main() {\n let nums = vec![1, 2, 3];\n println!("Sum: {}", get_sum(&nums));\n}\n'})}),"\n",(0,i.jsxs)(n.p,{children:["and you run ",(0,i.jsx)(n.em,{children:"clippy"})," on it, you will get"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{children:'Checking playground v0.0.1 (/playground)\nwarning: writing `&Vec` instead of `&[_]` involves a new object where a slice will do\n --\x3e src/main.rs:1:18\n |\n1 | fn get_sum(nums: &Vec<i32>) -> i32 {\n | ^^^^^^^^^ help: change this to: `&[i32]`\n |\n = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#ptr_arg\n = note: `#[warn(clippy::ptr_arg)]` on by default\n\nwarning: `playground` (bin "playground") generated 1 warning\n Finished dev [unoptimized + debuginfo] target(s) in 0.61s\n'})}),"\n",(0,i.jsx)(n.p,{children:"However, if you introduce a type alias, such as"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-rust",children:"type Numbers = Vec<i32>;\n"})}),"\n",(0,i.jsxs)(n.p,{children:["Then ",(0,i.jsx)(n.em,{children:"clippy"})," won't say anything, cause there is literally nothing to suggest.\nHowever the outcome is not the same\u2026"]})]})}function h(e={}){const{wrapper:n}={...(0,s.a)(),...e.components};return n?(0,i.jsx)(n,{...e,children:(0,i.jsx)(c,{...e})}):c(e)}},11151:(e,n,t)=>{t.d(n,{Z:()=>a,a:()=>o});var i=t(67294);const s={},r=i.createContext(s);function o(e){const n=i.useContext(r);return i.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function a(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:o(e.components),i.createElement(r.Provider,{value:n},e.children)}}}]); |