Jak oni rdzewieją (Advent of Code 2021)

Jak oni rdzewieją (Advent of Code 2021)
Shalom
  • Rejestracja:około 21 lat
  • Ostatnio:prawie 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
9

Wrzucam na githuba moje mizerne próby z Rustem w ramach AoC, więc jeśli ktoś chciałby się popastwić i wyjaśnić czemu to wszystko jest źle, to zapraszam: https://github.com/Pharisaeus/AoC2021/tree/master/src :)

edit: dla jasności nie przerobiłem nawet żadnego kursu Rusta, odpalam IntelliJ i pisze "na czuja" :D


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
edytowany 2x, ostatnio: Shalom
KamilAdam
Czy pod coś takiego nie jest dział oceny i recenzje :p
Shalom
Ale tam nikt nie zagląda :P
p_agon
Zglaszac moda/ nie zglaszac. A zglosze xD
Shalom
Dodatkowo w sumie interesują mnie raczej "językowe" rzeczy, tzn "jak to powinno sie napisać w ruście"
LU
Ja mam jakieś zadanka z 2019 jeszcze, w pythonie, ale w połowie się zaciąłem i nie kontynuowałem bo nie było czasu :P https://github.com/lukascode/AoC/tree/master/2019
KamilAdam
  • Rejestracja:ponad 6 lat
  • Ostatnio:7 dni
  • Lokalizacja:Silesia/Marki
  • Postów:5505
5

Łooo panie, ile kodu. Nawet traity implementujesz. Jest sens implementować traity jak i tak nie potrzebujesz polimorfizmu? (Chyba że potrzebujesz, tylko okiem z komórki rzuciłem). Ja wiem że w OOP się trzecie te interfejsy i klasy czy jest to potrzebne czy nie (wyjątkiem jest chyba tylko Kotlin) ale czy w Ruscie tez jest taka konwencja?


Mama called me disappointment, Papa called me fat
Każdego eksperta można zastąpić backendowcem który ma się douczyć po godzinach. Tak zostałem ekspertem AI, Neo4j i Nest.js . Przez mianowanie
Shalom
Sugerujesz ze napisałem kod w Javie, który tylko udaje że jest Rustem? To jest całkiem możliwe :P
KamilAdam
Sugeruję że wielu z nas jest spaczonych OOP :D
jarekr000000
Kiedyś pisałem javę w Haskellu. Można.
Wibowit
  • Rejestracja:prawie 20 lat
  • Ostatnio:4 minuty
1
KamilAdam napisał(a):

Łooo panie, ile kodu. Nawet traity implementujesz. Jest sens implementować traity jak i tak nie potrzebujesz polimorfizmu?

Gdzie te implementacje traitów? impl Cośtam { ... } to nie jest implementacja traitu. Implementacja traitu to raczej coś a'la impl TraitName for StructName { ... }.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
Patryk27
Moderator
  • Rejestracja:ponad 17 lat
  • Ostatnio:ponad rok
  • Lokalizacja:Wrocław
  • Postów:13042
4
  1. Ostatnia instrukcja jest niejako "auto return", tzn. wystarczy:

    Kopiuj
    fn part1(commands: &[Command]) -> i32 {
        /* ... */
        pos * depth
    }
    

    Explicit return wykorzystywany jest jedynie w sytuacjach early-exit, wyjść z pętli itd.

  2. Dzięki temu, że pętla for item in items desugaruje się do:

    Kopiuj
    let items = items.into_iter();
    
    while let Some(item) = items.next() {
        /* ... */
    }
    

... oraz że istnieje IntoIterator dla &[T], wystarczy np. for command in commands {, bez wprost .iter().

  1. Kopiuj
    fn part1_fun(commands: &[Command]) -> i32 {
        let final_state = commands.iter()
            .fold(State1 { pos: 0, depth: 0 }, move_sub1);
        return final_state.pos * final_state.depth;
    }
    

    =>

    Kopiuj
    #[derive(Default)]
    struct State1 {
        /* ... */
    }
    
    impl State1 {
        fn score(self) -> i32 {
            /* ... */
        }
    }
    
    fn part1_fun(commands: &[Command]) -> i32 {
        commands
            .iter()
            .fold(Default::default(), move_sub1)
            .score()
    }
    
  2. Zwyczajowo dorzuca się adnotacje tylko do tych typów, z którymi kompilator ma problem - tzn.:

    Kopiuj
    let commands: Vec<Command> = contents.lines()
        .map(|line| Command::new(line))
        .collect();
    

    =>

    Kopiuj
    let commands: Vec<_> = contents.lines().map(Command::new).collect();
    
  3. Jeśli istnieje trait pokrywający daną funkcjonalność, to zazwyczaj nie robi się "freestanding" funkcji:

    Kopiuj
    impl Board {
        /* ... */
    
        fn clone(&self) -> Board {
            /* ... */
        }
    }
    

    ... a implementuje dany trait:

    Kopiuj
    impl Clone for Board {
        /* ... */
    }
    

    (clippy ma nawet na to linta! :-))

  4. Zwyczajowo settery nazywa się set_foo(), lecz gettery po prostu foo() (https://rust-lang.github.io/api-guidelines/naming.html).

  5. no sentinel values, only ADTs angry doge meme

    Kopiuj
    fn part1(data: &InputData) -> i32 {
        /* ... */
        for number in data.numbers.iter() {
            /* ... */
            if boards.is_any_winning() {
                return /* ... */;
            }
        }
        return -1;
    }
    

    =>

    Kopiuj
    fn part1(data: &InputData) -> Option<u32> {
        /* ... */
        for number in &data.numbers {
            /* ... */
            if boards.is_any_winning() {
                return Some(/* ... */);
            }
        }
        None
    }
    
  6. Kopiuj
    struct BinNumber {
        value: Vec<i32>,
    }
    

    =>

    Kopiuj
    struct BinNumber {
        value: Vec<bool>,
    }
    

    (to uprości np. negowanie)

Tak ogólnie to fajen - kod wygląda całkiem dobrze, a widzę, że i nawet miejsce na Itertools się znalazło :-)


edytowany 5x, ostatnio: Patryk27
Shalom
Jeśli istnieje trait pokrywający daną funkcjonalność to bym najpierw musiał wiedzieć co istnieje a co nie :D :D
Patryk27
Ano, i w tym miejscu wchodzi Clippy, cały na biało :D
Wibowit
  • Rejestracja:prawie 20 lat
  • Ostatnio:4 minuty
3
Patryk27 napisał(a):
  1. Jeśli istnieje trait pokrywający daną funkcjonalność, to zazwyczaj nie robi się "freestanding" funkcji:
    Kopiuj
    impl Board {
        /* ... */
    
        fn clone(&self) -> Board {
            /* ... */
        }
    }
    
    ... a implementuje dany trait:
    Kopiuj
    impl Clone for Board {
        /* ... */
    }
    
    (clippy ma nawet na to linta! :-))

Można wstawić #[derive(Clone, Copy, Debug, Eq, PartialEq)] przed struct czy enum i mieć za darmo implementacje różnych traitów, pod warunkiem, że wszystkie pola też mają (rekurencyjnie) implementacje tych traitów. Szczegóły: https://doc.rust-lang.org/rust-by-example/trait/derive.html

P.S. Copy to tylko do bardzo lekkich struktur. Do czegokolwiek nietrywialnego należy użyć Clone. Różnica jest taka, że kopiowanie jest niejawne, automatyczne, a klonowanie jest jawne, więc widać gdzie ponosimy koszt klonowania.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
edytowany 1x, ostatnio: Wibowit
Patryk27
True - przez chwilę myślałem, że te fn clone() mają jakąś własną logikę, ale faktycznie to tylko pass-through do .clone() ich pól.
Shalom
te wojny klonów tam wynikają tylko z dzielnej walki z borrow checkerem i lifetimami :P
KR
Różnica między Copy i Clone jest jeszcze na innym poziomie - Copy jest wyłącznie do kopiowania płaskich wartości, które można skopiować kopiując obszar pamięci jednym ciągiem (czyli przez memcpy). Nie da się zrobić głębokiego Copy schodzącego głębiej poprzez referencje. Zauważ, że Copy to tylko marker, nie ma żadnych metod.
KR
Moderator
  • Rejestracja:prawie 21 lat
  • Ostatnio:około 3 godziny
  • Postów:2964
1

Zwyczajowo dorzuca się adnotacje tylko do tych typów, z którymi kompilator ma problem - tzn.:

Kopiuj
let commands: Vec<_> = contents.lines().map(Command::new).collect();

Można użyć collect_vec() i wtedy w ogóle nie musisz podawać typu.

LW
A jeszcze można: ... .collect<Vec<_>>(). Wtedy plugin w idea wyświetli typ po commands taką samą czcionką co wszędzie indziej.
Shalom
  • Rejestracja:około 21 lat
  • Ostatnio:prawie 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
0

Chciałem dziś zrobić ładnie z .fold() ale kopiowanie boarda po oznaczeniu pola strasznie się ślimaczyło, a nie umiem przekonać borrow-checkera do jakiegoś:

Kopiuj
    lines.iter()
        .fold(board, |board, line| board.mark_line(line));

ale w sytuacji kiedy mark_line nie zwraca kopii Board tylko &Board

edit: Żeby wyjaśnić o co pytam. Można by napisać taki kod:

Kopiuj
use itertools::Itertools;

struct Board {
    board: Vec<i32>,
}

impl Board {
    fn new(size: i32) -> Board {
        return Board { board: (0..size).map(|x| 0).collect_vec() };
    }
    fn mark_field(&self, index: usize) -> Board {
        return Board {
            board: self.board
                .iter()
                .enumerate()
                .map(|(i, &value)| if i == index { value + 1 } else { value })
                .collect_vec()
        };
    }
}

pub(crate) fn solve() {
    let board = (0..10000)
        .fold(Board::new(10000), |board, index| board.mark_field(index));
    println!("{}", board.board[1]);
}

Czyli za każdym razem kiedy robimy mark_field zwracamy nową planszę. Tylko że jak plansza jest duża to nagle robi sie to bardzo wolne, z oczywistych względów.

I teraz zastanawiałem się, czy można tak zrobić, zeby nasze mark_field zwracało zmodyfikowaną początkową tablicę, a nie tworzyło nową za każdym razem (pytanie co jest "bardziej rustowe" i moze jest jakaś trzecia opcja?). I pisząc tego posta doszedłem do tego, ze mogę zrobić:

Kopiuj
    fn mark_field(mut self, index: usize) -> Board {
        self.board[index]+=1;
        return self;
    }

Bo mogę przyjąć sobie self a nie &self jako argument.


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
edytowany 2x, ostatnio: Shalom
Zobacz pozostałe 5 komentarzy
KR
Tak, to prawda. Ale wtedy zamiast fold robisz proste iterowanie z akumulatorem, co jest IMHO bardziej rustowe. Rust ma mechanizmy pozwalające bezpiecznie mutować i nie należy na siłę robić z niego Haskella. fold zaciemnia sytuację, bo właśnie sugeruje że dostajesz coś nowego, a tu chcemy po prostu zmodyfikować jeden istniejący obiekt.
hauleth
(0..size).map(|x| 0).collect_vec() bleh, vec![0, size]
Shalom
Wiedziałem że musi być łatwiejszy sposób :D :D
Patryk27
vec![0; size]; przecinek utworzyłby wektor z dwoma elementami ([0, size] vs [0, 0, 0, ...]).
hauleth
Ah tak, dawno nie pisałem w Ruscie.
FE
  • Rejestracja:ponad 11 lat
  • Ostatnio:prawie 3 lata
0

Jak wrażenia z Rusta po AoC2021? Jakieś refleksje?


Shalom
  • Rejestracja:około 21 lat
  • Ostatnio:prawie 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
1

Hmm dość specyficzny język. Moim zdaniem miejscami mocno autystyczny, kiedy np. nie możesz porównać &int z intem i musisz robić jakiś cyrk w stylu &0. Dodatkowo nie wyobrażam sobie pisania w Ruście bez silnego wsparcia IDE, które pokazuje wyniki inferencji typów, szczególnie kiedy nagle po jakimś .iter() czy w jakimś .map() dostajesz &&X.
Swoją drogą, biorąc pod uwagę brak raw-pointerów jako takich (w sensie jakiegoś int* ptr = 0x12345; w C), zastanawiam się, czemu składnia idzie w kierunku pointerów z C (w kontekście & i *) a nie czegoś na wzór referencji.


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
Shalom
Na co dzień to Java/Python
Shalom
Nie no w pracy raczej nie będę miał okazji użyć nigdzie rusta, a przynajmniej nie przewiduje :D
Patryk27
Moderator
  • Rejestracja:ponad 17 lat
  • Ostatnio:ponad rok
  • Lokalizacja:Wrocław
  • Postów:13042
0

Swoją drogą, biorąc pod uwagę brak raw-pointerów jako takich

FWIW, istnieją raw pointery:

Kopiuj
fn main() {
    let ptr = 0x12345 as *const i32;
    println!("{}", unsafe { *ptr }); // 99% szansy na segfault
}

a nie czegoś na wzór referencji.

W sensie, że bez ręcznego pisania *? Taki mechanizm już istnieje i nazywa się auto-deref :-)


edytowany 2x, ostatnio: Patryk27
Shalom
No tak ale unsafe to unsafe, a ja mówie bardziej o takim klasycznym zastosowaniu ;)
jarekr000000
  • Rejestracja:ponad 8 lat
  • Ostatnio:około 4 godziny
  • Lokalizacja:U krasnoludów - pod górą
  • Postów:4707
0
Shalom napisał(a):

Dodatkowo nie wyobrażam sobie pisania w Ruście bez silnego wsparcia IDE, które pokazuje wyniki inferencji typów, szczególnie kiedy nagle po jakimś .iter() czy w jakimś .map() dostajesz &&X.

To może być przyzwyczajenie javowe - zupełnie niepotrzebne natręctwo do podglądania typów. Podobny problem niektórzy raportują w kotlinie... podczas kiedy całkiem dobrze się pisze z wyłączonymi hintami typów. W haskellu to norma, że typów nie widzisz przez całe długie kawałki kodu i zwykle żadnego specjalnego wsparcia IDE nie ma.
A rust z wnioskowaniem typów jest bliżej nawet tego haskella.


jeden i pół terabajta powinno wystarczyć każdemu
Shalom
  • Rejestracja:około 21 lat
  • Ostatnio:prawie 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
0

@jarekr000000 może gdyby były jakieś naturalne koercje pomiędzy tymi & to by się dało, ale kiedy nie możesz porównać &int z intem, a gdzieś nagle dostajesz & albo w ogóle && to moim zdaniem jednak ciężko.


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
SL
  • Rejestracja:około 7 lat
  • Ostatnio:6 minut
  • Postów:866
0

Swoją drogą, biorąc pod uwagę brak raw-pointerów jako takich (w sensie jakiegoś int* ptr = 0x12345; w C), zastanawiam się, czemu składnia idzie w kierunku pointerów z C (w kontekście & i *) a nie czegoś na wzór referencji.

@Shalom pisanie przez parę lat w C++ skłoniło mnie do konkluzji, że implicit referencje to zło wcielone. Semantyka wskaźników używana w C czy Javie jest prosta i skuteczna. To, że w zależności od kontekstu referencja zachowuje się jak wskazywany typ albo nie powoduje dużo problemów np. nieustanne niepotrzebne kopie, konieczne cuda na kiju (jak std::reference_wrapper) popieprzone wyłomy w systemie typów. Zauważ, że w wielu przypadkach Rust idzie na rękę i sprawia, że referencje są przezroczyste w przypadkach, gdy jest to całkowicie nieszkodliwe np. wołanie metod.

Shalom
czy Javie ? Przecież w Javie masz właśnie ładne "niewidzialne" referencje i nigdy nie ma potrzeby robienia ręcznej dereferencji.
SL
ale nie masz też "normalnych" wartości (oprócz prymitywów, ale te z drugiej strony nie mają referencji/pointerów), więc wprowadzanie dereferencji nie ma sensu. Fundamentalną różnicą jest to, że w C++ da się zaimplementować swap(x, y) a w C/Javie nie.
Shalom
Wydaje mi się że jednak twój problem z referencjami w C++ wynika z mutowalności, co akurat w Ruście nie jest problemem bo tam musisz explicite oznaczyć coś jako mutable.
Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)