csos95's recent activity

  1. Comment on Intuit is shutting down the personal finance service Mint and shifting users to Credit Karma in ~finance

    csos95
    Link Parent
    There was a new comment: https://tildes.net/~finance/1btc/intuit_is_shutting_down_the_personal_finance_service_mint_and_shifting_users_to_credit_karma#comment-cxn5 For future reference, the topic...

    There was a new comment: https://tildes.net/~finance/1btc/intuit_is_shutting_down_the_personal_finance_service_mint_and_shifting_users_to_credit_karma#comment-cxn5

    For future reference, the topic log on the right sidebar (if you’re on mobile you can open it by clicking the “sidebar” link on the top right of the page) shows when the last comment was made and links to it.

    So for me right now it shows

    Last comment posted
    7m ago

    4 votes
  2. Comment on Database schema for an upcoming comment hosting system in ~comp

    csos95
    Link Parent
    Here is the documentation on strict tables. I haven't bothered to use them because I do all my stuff in rust (usually with diesel) and have strong type checking to prevent me from using the wrong...

    Here is the documentation on strict tables.
    I haven't bothered to use them because I do all my stuff in rust (usually with diesel) and have strong type checking to prevent me from using the wrong type by accident.

    but not sure even referential integrity is maintained by Sqlite unless we use the strict mode?

    Strict tables are unrelated to foreign key constraints.
    The default for SQLite (another one of those "backwards compatibility" oddities) is to not enforce foreign key constraints.
    You have to enable it with a pragma statement: pragma foreign_keys = on;
    A full list of pragma statements is available here.

    I usually use journal_mode = wal, synchronous = normal, foreign_keys = on, and busy_timeout = SOME_TIMEOUT_IN_MS in my projects.
    This will use a write ahead log for writes (much faster and makes it so readers and writers (still limited to one writer at a time though) do not block each other), make it do less syncing to the filesystem, enable foreign key checking, and automatically waits if a table is locked with a default busy_handler.

    Something to note for that last option is that it mostly will take care of database is locked errors that occur when you try to read/write when the needed table is locked.
    The one case it doesn't cover is when a deferred transaction (the default) is "upgraded" to a write transaction.
    In this case, if a needed table is locked, it will immediately error instead of using the busy_handler.
    So if you use transactions, you should make them immediate when you are going to be writing.
    Ex: begin immediate transaction
    Transactions documentation here

    3 votes
  3. Comment on Database schema for an upcoming comment hosting system in ~comp

    csos95
    Link Parent
    Adding to what unkz said, php has built-in functions for secure password hashing and verification. password_hash and password_verify.

    Adding to what unkz said, php has built-in functions for secure password hashing and verification.
    password_hash and password_verify.

    11 votes
  4. Comment on Database schema for an upcoming comment hosting system in ~comp

    csos95
    (edited )
    Link
    Something to be aware of is that in SQLite the types are more of a hint for how to store the data for a column rather than a constraint. You can store any type of data in any type of column...

    Something to be aware of is that in SQLite the types are more of a hint for how to store the data for a column rather than a constraint.
    You can store any type of data in any type of column (unless you specifically use strict tables).
    It will attempt to convert the data to the storage class indicated by the column type used, but if it fails it will store the data as is.
    This page of the SQLite documentation explains this in detail.
    I come back to it often to remind myself of what column types are associated with what affinities.

    Additionally, the numeric parameters on types are ignored.
    So a column of type varchar(255) will store a 300 character string without complaint.
    To add a constraint on the length you need to add a check constraint to the end.
    So instead of name varchar(255) not null, you can do name text not null check(length(name) <= 255) (I generally prefer using the five affinity type names so I don't have to remember the exact rules for affinity mapping).

    EDIT: I just noticed you have two primary keys set for the comments table.
    That won't work.
    The id column should probably be defined as id integer primary key autoincrement and the user_id should be defined as user_id integer not null references users(id).
    The autoincrement prevents rowids from being reused in certain situations and isn't strictly necessary, but I always use it to be safe because it doesn't really affect performance for anything I've used SQLite for.
    Additionally if you ever want to have a primary key that isn't integer (which is a special case that maps to the rowid), you need to add not null to it.
    Otherwise you could end up with every row having a NULL id.
    This is because of two issues:

    1. primary key does not imply not null in SQLite because there was a bug early on and changing it to follow the SQL standard would break programs that relied on that behavior (that's the general reason behind many weird behaviors/default values in SQLite).
    2. In SQL null is not equal to anything, even null, so all of the null primary key values would be considered different.

    Another thing is that you can put the unique constraints directly on the column they refer to when it's just a single column.
    Ex: username text not null unique

    13 votes
  5. Comment on Megathread: April Fools’ Day 2024 on the internet in ~talk

  6. Comment on Florida latest to restrict social media for kids as legal battle looms in ~tech

    csos95
    (edited )
    Link Parent
    Something I've wanted a few times (usually after dealing with young children popping into a discord server that usually leans towards adult conversations) is a government run oauth service that...

    Something I've wanted a few times (usually after dealing with young children popping into a discord server that usually leans towards adult conversations) is a government run oauth service that provides a property with what age group the person is in.

    If that were a thing, you wouldn't need to worry about each company's privacy/security policies around verifying IDs, you just need to login with that service and they'll have access to a few basic details about you that are listed before you approve the authorization.

    1 vote
  7. Comment on Apple is turning William Gibson’s Neuromancer into a TV series in ~tv

    csos95
    Link
    I'm not familiar with this work, but I first heard about this adaptation yesterday in a discord server when someone posted this Twitter thread from the author and I thought it might be useful for...

    I'm not familiar with this work, but I first heard about this adaptation yesterday in a discord server when someone posted this Twitter thread from the author and I thought it might be useful for fans wondering how faithful it will be to the book.

    text of the tweets

    I’m not going to be answering many questions here, about Apple TV’s adaptation of Neuromancer. I’ll have to be answering too many elsewhere, and doing my part on the production. So I thought I’d try to describe that, my part.

    I answer showrunner’s and director’s questions about the source material. I read drafts and make suggestions. And that’s it, really, though my previous experience has been that that winds up being quite a lot of work in itself.

    I don’t have veto power. The showrunner and director do, because the adaptation’s their creation, not mine. A novel is a solitary creation. An adaptation is a fundamentally collaborative creation, so first of all isn’t going to “be the book”.

    Particularly not the one you saw behind your forehead when you read the book, because that one is yours alone. So for now let’s leave it at that.

    13 votes
  8. Comment on Collapse comments? in ~tildes

    csos95
    Link Parent
    I pretty much always like seeing comments with <details> tags. It makes navigation much easier for me on both mobile and desktop.

    I pretty much always like seeing comments with <details> tags.
    It makes navigation much easier for me on both mobile and desktop.

    6 votes
  9. Comment on Tildes Book Club - How is it going? Discussion of Cloud Atlas will begin the second full week in March. in ~books

    csos95
    Link
    I started the book yesterday, but have been having trouble getting through the first section. Maybe spoilers (it's the very start of the book though so not much) and negative feelings on the book...

    I started the book yesterday, but have been having trouble getting through the first section.

    Maybe spoilers (it's the very start of the book though so not much) and negative feelings on the book

    I get that the writing is supposed to make you feel like it was written by someone from the 1800s, that the racism is in your face as soon as possible to really hammer home it's a different time without "good old days" sugar coating, and maybe to make you not as sympathetic to the narrator.

    My issue is that the end result of that is still reading about two wealthy white guys traipsing around the colonies while using as many obscure words (that almost all have no results on my kindle from the built-in dictionary or Wikipedia) and racist slurs as possible.
    So I'm having a lot of trouble getting into it.

    For those who are further along/have finished: Do they cool it with the racism anytime soon?

    I've heard the other sections are better, but I'm also aware that the sections are split so the second half of the first section is also the ending to the book.
    I'm not sure I'm looking forward to getting to the end if it's going to be jumping back to more of this.

    If I can't get through this section tomorrow, I think I'll try just skipping it and seeing if the rest of the book interests me more.

    2 votes
  10. Comment on Borderlands | Official trailer in ~movies

    csos95
    Link
    I've been a big fan of the Borderlands games (besides the third, haven't played it), but I somehow missed that there was a movie coming. I'm cautiously optimistic because Borderlands didn't really...

    I've been a big fan of the Borderlands games (besides the third, haven't played it), but I somehow missed that there was a movie coming.

    I'm cautiously optimistic because Borderlands didn't really have a good or complex story that can be ruined in the translation to the movie.
    There are vault hunters, they're looking for a vault, and there are other groups trying to find it first.

    The fun of it for me (beyond the gameplay) was the world, aesthetics, and short bits of funny dialogue.
    And it seems to me like this has that from what the trailer showed.

    5 votes
  11. Comment on The carry-on-baggage bubble is about to pop in ~travel

    csos95
    Link Parent
    Yes, they are free. A few people I know bring a backpack and a suitcase that is exactly the allowed carry-on size and board last with the knowledge that there's a very low chance there will be...

    When bags are checked at the gate, are those free? I wonder if that sets up some perverse incentive, where people, not wanting to pay the expensive fee for checked bags, hope that they can a free checked bag at the gate.

    Yes, they are free.
    A few people I know bring a backpack and a suitcase that is exactly the allowed carry-on size and board last with the knowledge that there's a very low chance there will be space available when they get on.
    That way they'll be able to get on (the bins usually fill up and they're asked to check before they get to the boarding ramp) and off the plane without digging through overhead bins or paying for the bag check.

    15 votes
  12. Comment on Tildes Book Club - Should we allow nomination of trilogies? Should we have a length limit? in ~books

    csos95
    Link Parent
    No - In my opinion, nominating the first book in a series is fine, but nominating an entire series would be too much.

    No - In my opinion, nominating the first book in a series is fine, but nominating an entire series would be too much.

    4 votes
  13. Comment on How much does a creator's worldview influence whether you use their tech or consume their media? in ~talk

    csos95
    Link Parent
    He does not profit from me using any of the things you listed. My line is pretty simple: I don't use things he profits from.

    He does not profit from me using any of the things you listed.
    My line is pretty simple: I don't use things he profits from.

    7 votes
  14. Comment on Day 7: Camel Cards in ~comp.advent_of_code

    csos95
    Link
    Part one took me a while because I assumed something that wasn't in the wording of the problem. Once I realized that, it was a quick fix and part two didn't take long. What I assumed I assumed...

    Part one took me a while because I assumed something that wasn't in the wording of the problem.
    Once I realized that, it was a quick fix and part two didn't take long.

    What I assumed I assumed that the cards in the hand needed to be sorted, not left in the order they appear in.
    Rust
    use std::collections::HashMap;
    
    #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
    enum Card1 {
        Two,
        Three,
        Four,
        Five,
        Six,
        Seven,
        Eight,
        Nine,
        Ten,
        Jack,
        Queen,
        King,
        Ace,
    }
    
    #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
    enum Hand {
        High,
        OnePair,
        TwoPair,
        Three,
        Full,
        Four,
        Five,
    }
    
    fn hand_type_part1(cards: &[Card1]) -> Hand {
        use Hand::*;
        let mut hand: HashMap<Card1, usize> = HashMap::new();
        for card in cards {
            *hand.entry(*card).or_default() += 1;
        }
        match hand.len() {
            5 => High,
            4 => OnePair,
            3 if *hand.values().max().unwrap() == 2 => TwoPair,
            3 if *hand.values().max().unwrap() == 3 => Three,
            2 if *hand.values().max().unwrap() == 3 => Full,
            2 if *hand.values().max().unwrap() == 4 => Four,
            1 => Five,
            _ => unreachable!(),
        }
    }
    
    #[aoc(day7, part1)]
    fn part1(input: &str) -> usize {
        let mut hands: Vec<(Hand, Vec<Card1>, usize)> = input
            .lines()
            .map(|line| {
                let mut parts = line.split(" ");
                let cards: Vec<Card1> = parts
                    .next()
                    .unwrap()
                    .chars()
                    .map(|c| {
                        use Card1::*;
                        match c {
                            '2' => Two,
                            '3' => Three,
                            '4' => Four,
                            '5' => Five,
                            '6' => Six,
                            '7' => Seven,
                            '8' => Eight,
                            '9' => Nine,
                            'T' => Ten,
                            'J' => Jack,
                            'Q' => Queen,
                            'K' => King,
                            'A' => Ace,
                            _ => unreachable!(),
                        }
                    })
                    .collect();
    
                let hand = hand_type_part1(&cards);
                let bid: usize = parts.next().unwrap().parse().unwrap();
                (hand, cards, bid)
            })
            .collect();
    
        hands.sort();
    
        hands
            .into_iter()
            .enumerate()
            .map(|(i, (_, _, bid))| (i + 1) * bid)
            .sum()
    }
    
    #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
    enum Card2 {
        Joker,
        Two,
        Three,
        Four,
        Five,
        Six,
        Seven,
        Eight,
        Nine,
        Ten,
        Queen,
        King,
        Ace,
    }
    
    fn hand_type_part2(cards: &[Card2]) -> Hand {
        use Hand::*;
        let mut hand: HashMap<Card2, usize> = HashMap::new();
        for card in cards {
            *hand.entry(*card).or_default() += 1;
        }
    
        let jokers = *hand.get(&Card2::Joker).unwrap_or(&0);
    
        let max = *hand.values().max().unwrap();
    
        match hand.len() {
            // 0, 1
            5 if jokers == 0 => High,
            5 => OnePair,
            // 0, 1, 2
            4 if jokers == 0 => OnePair,
            4 if jokers == 1 => Three,
            4 if jokers == 2 => Three,
            // 0, 1, 2
            3 if max == 2 && jokers == 0 => TwoPair,
            3 if max == 2 && jokers == 1 => Full,
            3 if max == 2 && jokers == 2 => Four,
            // 0, 1, 3
            3 if max == 3 && jokers == 0 => Three,
            3 if max == 3 && jokers == 1 => Four,
            3 if max == 3 && jokers == 3 => Four,
            // 0, 2, 3
            2 if max == 3 && jokers == 0 => Full,
            2 if max == 3 && jokers == 2 => Five,
            2 if max == 3 && jokers == 3 => Five,
            // 0, 1, 4
            2 if max == 4 && jokers == 0 => Four,
            2 if max == 4 && jokers == 1 => Five,
            2 if max == 4 && jokers == 4 => Five,
            // 0, 5
            1 => Five,
            _ => unreachable!(),
        }
    }
    
    #[aoc(day7, part2)]
    fn part2(input: &str) -> usize {
        let mut hands: Vec<(Hand, Vec<Card2>, usize)> = input
            .lines()
            .map(|line| {
                let mut parts = line.split(" ");
                let cards: Vec<Card2> = parts
                    .next()
                    .unwrap()
                    .chars()
                    .map(|c| {
                        use Card2::*;
                        match c {
                            'J' => Joker,
                            '2' => Two,
                            '3' => Three,
                            '4' => Four,
                            '5' => Five,
                            '6' => Six,
                            '7' => Seven,
                            '8' => Eight,
                            '9' => Nine,
                            'T' => Ten,
                            'Q' => Queen,
                            'K' => King,
                            'A' => Ace,
                            _ => unreachable!(),
                        }
                    })
                    .collect();
    
                let hand = hand_type_part2(&cards);
                let bid: usize = parts.next().unwrap().parse().unwrap();
                (hand, cards, bid)
            })
            .collect();
    
        hands.sort();
    
        hands
            .into_iter()
            .enumerate()
            .map(|(i, (_, _, bid))| (i + 1) * bid)
            .sum()
    }
    
    2 votes
  15. Comment on Day 5: If You Give A Seed A Fertilizer in ~comp.advent_of_code

    csos95
    Link
    I had to hold off on doing part two until today because my brain felt like mush all yesterday and it was just not happening. XD I decided not to go the bruteforce route with this one. At first I...

    I had to hold off on doing part two until today because my brain felt like mush all yesterday and it was just not happening. XD

    I decided not to go the bruteforce route with this one.
    At first I tried figuring out the different range overlap cases myself, but I eventually gave up and used the ranges library to calculate them.

    Part one takes 52.506μs and part two takes 114.60μs on my desktop with a ryzen 2700x.

    Rust
    use itertools::Itertools;
    use ranges::{GenericRange, Relation};
    use std::{
        collections::BTreeMap,
        ops::{Bound::*, RangeBounds},
    };
    
    #[aoc(day5, part1)]
    fn part1(input: &str) -> usize {
        let mut sections = input.split("\n\n");
        let mut seeds: Vec<usize> = sections
            .next()
            .unwrap()
            .strip_prefix("seeds: ")
            .unwrap()
            .split(" ")
            .map(|n| n.parse().unwrap())
            .collect();
    
        for section in sections {
            let section: BTreeMap<usize, (usize, usize)> =
                section
                    .split("\n")
                    .skip(1)
                    .fold(BTreeMap::new(), |mut mapping, line| {
                        let nums: Vec<usize> = line.split(" ").map(|n| n.parse().unwrap()).collect();
                        let dest = nums[0];
                        let src = nums[1];
                        let len = nums[2];
                        mapping.insert(src, (dest, len));
                        mapping
                    });
    
            seeds = seeds
                .into_iter()
                .map(|seed| {
                    let cursor = section.upper_bound(Included(&seed));
                    if let Some((src, (dest, len))) = cursor.key_value() {
                        if src + len > seed {
                            dest + (seed - src)
                        } else {
                            seed
                        }
                    } else {
                        seed
                    }
                })
                .collect();
        }
    
        seeds.into_iter().min().unwrap()
    }
    
    fn translate_range(range: GenericRange<usize>, offset: i64) -> GenericRange<usize> {
        let start = match range.start_bound() {
            Included(n) => Included(((*n as i64) + offset) as usize),
            Excluded(n) => Excluded(((*n as i64) + offset) as usize),
            Unbounded => Unbounded,
        };
        let end = match range.end_bound() {
            Included(n) => Included(((*n as i64) + offset) as usize),
            Excluded(n) => Excluded(((*n as i64) + offset) as usize),
            Unbounded => Unbounded,
        };
    
        GenericRange::new_with_bounds(start, end)
    }
    
    #[aoc(day5, part2)]
    fn part2(input: &str) -> usize {
        let mut sections = input.split("\n\n");
        let mut seeds: Vec<GenericRange<usize>> = sections
            .next()
            .unwrap()
            .strip_prefix("seeds: ")
            .unwrap()
            .split(" ")
            .map(|n| n.parse().unwrap())
            .chunks(2)
            .into_iter()
            .map(|chunk| {
                let (start, len) = chunk.collect_tuple().unwrap();
                GenericRange::new_closed_open(start, start + len)
            })
            .collect();
    
        for section in sections {
            let section: BTreeMap<usize, (usize, usize)> =
                section
                    .split("\n")
                    .skip(1)
                    .fold(BTreeMap::new(), |mut mapping, line| {
                        let nums: Vec<usize> = line.split(" ").map(|n| n.parse().unwrap()).collect();
                        let dest = nums[0];
                        let src = nums[1];
                        let len = nums[2];
                        mapping.insert(src, (dest, len));
                        mapping
                    });
    
            seeds = seeds
                .into_iter()
                .flat_map(|mut seed_range| {
                    let mut ranges = Vec::new();
                    let cursor = section.upper_bound(seed_range.start_bound());
                    let search_range = if let Some((src, _)) = cursor.key_value() {
                        GenericRange::new_with_bounds(Included(*src), seed_range.end_bound().cloned())
                    } else {
                        seed_range
                    };
    
                    let mut done = false;
                    for (src, (dest, len)) in section.range(search_range) {
                        let mapping_range = GenericRange::new_closed_open(*src, src + len);
                        let offset = (*dest as i64) - (*src as i64);
    
                        match seed_range.relation(mapping_range) {
                            Relation::Disjoint {
                                first, self_less, ..
                            } => {
                                if self_less {
                                    ranges.push(first);
                                    done = true;
                                    break;
                                }
                            }
                            Relation::Touching {
                                first, self_less, ..
                            } => {
                                if self_less {
                                    ranges.push(first);
                                    done = true;
                                    break;
                                }
                            }
                            Relation::Overlapping {
                                first_disjoint,
                                second_disjoint,
                                overlap,
                                self_less,
                                ..
                            } => {
                                if self_less {
                                    ranges.push(first_disjoint);
                                    ranges.push(translate_range(overlap, offset));
                                    done = true;
                                    break;
                                } else {
                                    ranges.push(translate_range(overlap, offset));
                                    seed_range = second_disjoint;
                                }
                            }
                            Relation::Containing {
                                first_disjoint,
                                second_disjoint,
                                overlap,
                                self_shorter,
                            } => {
                                if self_shorter {
                                    ranges.push(translate_range(overlap, offset));
                                    done = true;
                                    break;
                                } else {
                                    ranges.push(first_disjoint);
                                    ranges.push(translate_range(overlap, offset));
                                    seed_range = second_disjoint;
                                }
                            }
                            Relation::Starting {
                                overlap,
                                disjoint,
                                self_shorter,
                                ..
                            } => {
                                if self_shorter {
                                    ranges.push(translate_range(overlap, offset));
                                    done = true;
                                    break;
                                } else {
                                    ranges.push(translate_range(overlap, offset));
                                    seed_range = disjoint;
                                }
                            }
                            Relation::Ending {
                                disjoint,
                                overlap,
                                self_shorter,
                                ..
                            } => {
                                if self_shorter {
                                    ranges.push(translate_range(overlap, offset));
                                    done = true;
                                    break;
                                } else {
                                    ranges.push(disjoint);
                                    ranges.push(translate_range(overlap, offset));
                                    done = true;
                                    break;
                                }
                            }
                            Relation::Equal(range) => {
                                ranges.push(translate_range(range, offset));
                            }
                            Relation::Empty {
                                non_empty,
                                self_empty,
                            } => {
                                if let (Some(range), Some(true)) = (non_empty, self_empty) {
                                    ranges.push(range);
                                } else {
                                    done = true;
                                    break;
                                }
                            }
                        }
                    }
    
                    if !done {
                        ranges.push(seed_range);
                    }
                    ranges
                })
                .collect();
        }
    
        seeds
            .into_iter()
            .map(|range| match range.start_bound().cloned() {
                Included(n) => n,
                Excluded(n) => n,
                Unbounded => 0,
            })
            .min()
            .unwrap()
    }
    
    1 vote
  16. Comment on Day 4: Scratchcards in ~comp.advent_of_code

    csos95
    Link
    As others have said, it's a pretty simple one today. I redid part 1 of day 1 with nom and then decided not to bother with it any further if I don't have to. I had forgotten how annoyingly opaque...

    As others have said, it's a pretty simple one today.
    I redid part 1 of day 1 with nom and then decided not to bother with it any further if I don't have to.
    I had forgotten how annoyingly opaque its errors are.

    Rust
    use regex::Regex;
    use std::collections::{HashSet, VecDeque};
    
    #[aoc(day4, part1)]
    fn part1(input: &str) -> usize {
        let number_re = Regex::new("([0-9]+)").unwrap();
        input
            .lines()
            .map(|line| {
                let (_card, matches) = number_re
                    .captures_iter(line)
                    .skip(1)
                    .map(|c| c.get(1).unwrap().as_str())
                    .enumerate()
                    .fold((HashSet::new(), 0), |(mut card, mut matches), (i, n)| {
                        if i < 10 {
                            card.insert(n);
                        } else if card.contains(n) {
                            matches += 1;
                        }
                        (card, matches)
                    });
    
                if matches == 0 {
                    0
                } else {
                    2usize.pow(matches - 1)
                }
            })
            .sum()
    }
    
    #[aoc(day4, part2)]
    fn part2(input: &str) -> usize {
        let number_re = Regex::new("([0-9]+)").unwrap();
        let card_matches: Vec<usize> = input
            .lines()
            .map(|line| {
                let (_card, matches) = number_re
                    .captures_iter(line)
                    .skip(1)
                    .map(|c| c.get(1).unwrap().as_str())
                    .enumerate()
                    .fold((HashSet::new(), 0), |(mut card, mut matches), (i, n)| {
                        if i < 10 {
                            card.insert(n);
                        } else if card.contains(n) {
                            matches += 1;
                        }
                        (card, matches)
                    });
                matches
            })
            .collect();
    
        let mut processing_cards = VecDeque::new();
    
        for (i, _) in card_matches.iter().enumerate() {
            processing_cards.push_back(i);
        }
    
        let mut total_cards = 0;
        while !processing_cards.is_empty() {
            let card_i = processing_cards.pop_front().unwrap();
            let matches = card_matches[card_i];
            for i in 0..matches {
                processing_cards.push_back(card_i + i + 1);
            }
            total_cards += 1;
        }
    
        total_cards
    }
    
    1 vote
  17. Comment on Day 3: Gear Ratios in ~comp.advent_of_code

    csos95
    (edited )
    Link
    Today's puzzle tripped me up for a bit and then I got stuck at part two because of a dumb issue where I set the final result to the last calculation rather than summing them up. I'll probably go...

    Today's puzzle tripped me up for a bit and then I got stuck at part two because of a dumb issue where I set the final result to the last calculation rather than summing them up.
    I'll probably go back over it tomorrow to make a better/more concise version.

    Rust
    use aoc_runner_derive::aoc;
    use logos::{Lexer, Logos};
    use std::collections::{HashMap, HashSet};
    
    fn get_count(lex: &mut Lexer<Num>) -> Option<i64> {
        Some(lex.slice().parse().unwrap())
    }
    
    #[derive(Logos)]
    enum Num {
        #[regex("[0-9]+", get_count)]
        Num(i64),
    
        #[error]
        #[regex(r"([^0-9])", logos::skip)]
        Error,
    }
    
    #[aoc(day3, part1)]
    fn part1(input: &str) -> i64 {
        let grid: Vec<Vec<char>> = input.lines().map(|line| line.chars().collect()).collect();
    
        let mut part_sum = 0;
        input.lines().enumerate().for_each(|(row, line)| {
            let row = row as i64;
            for (num, range) in Num::lexer(line).spanned() {
                match num {
                    Num::Num(n) => {
                        for col in range {
                            let col = col as i64;
                            let mut to_check = Vec::new();
                            let up = row - 1;
                            let down = row + 1;
                            let left = col - 1;
                            let right = col + 1;
                            if up > 0 {
                                to_check.push((up, col));
                            }
                            if down < 140 {
                                to_check.push((down, col));
                            }
                            if left > 0 {
                                to_check.push((row, left));
                            }
                            if right < 140 {
                                to_check.push((row, right));
                            }
                            if up > 0 && left > 0 {
                                to_check.push((up, left));
                            }
                            if down < 140 && right < 140 {
                                to_check.push((down, right));
                            }
                            if left > 0 && down < 140 {
                                to_check.push((down, left));
                            }
                            if right < 140 && up > 0 {
                                to_check.push((up, right));
                            }
    
                            let mut is_part_num = false;
                            for (x, y) in to_check {
                                if !grid[x as usize][y as usize].is_ascii_digit()
                                    && grid[x as usize][y as usize] != '.'
                                {
                                    is_part_num = true;
                                    break;
                                }
                            }
                            if is_part_num {
                                part_sum += n;
                                break;
                            }
                        }
                    }
                    _ => {}
                }
            }
        });
    
        part_sum
    }
    
    #[aoc(day3, part2)]
    fn part2(input: &str) -> i64 {
        let grid: Vec<Vec<char>> = input.lines().map(|line| line.chars().collect()).collect();
    
        let mut gear_nums: HashMap<(i64, i64), Vec<i64>> = HashMap::new();
        input.lines().enumerate().for_each(|(row, line)| {
            let row = row as i64;
            for (num, range) in Num::lexer(line).spanned() {
                match num {
                    Num::Num(n) => {
                        let mut seen_gears: HashSet<(i64, i64)> = HashSet::new();
                        for col in range {
                            let col = col as i64;
                            let mut to_check = Vec::new();
                            let up = row - 1;
                            let down = row + 1;
                            let left = col - 1;
                            let right = col + 1;
    
                            if up > 0 {
                                to_check.push((up, col));
                            }
                            if down < 140 {
                                to_check.push((down, col));
                            }
                            if left > 0 {
                                to_check.push((row, left));
                            }
                            if right < 140 {
                                to_check.push((row, right));
                            }
                            if up > 0 && left > 0 {
                                to_check.push((up, left));
                            }
                            if down < 140 && right < 140 {
                                to_check.push((down, right));
                            }
                            if left > 0 && down < 140 {
                                to_check.push((down, left));
                            }
                            if right < 140 && up > 0 {
                                to_check.push((up, right));
                            }
    
                            for (x, y) in to_check {
                                if grid[x as usize][y as usize] == '*' && !seen_gears.contains(&(x, y))
                                {
                                    gear_nums.entry((x, y)).or_default().push(n);
                                    seen_gears.insert((x, y)); //
                                }
                            }
                        }
                    }
                    _ => {}
                }
            }
        });
    
        let mut gear_sum: i64 = 0;
        for (_, nums) in gear_nums {
            if nums.len() == 2 {
                gear_sum += nums.into_iter().product::<i64>();
            }
        }
    
        gear_sum
    }
    
    2 votes
  18. Comment on Day 2: Cube Conundrum in ~comp.advent_of_code

    csos95
    (edited )
    Link
    Today's problem was very straightforward without any gotchas that I ran into. I'm going for solve speed rather than code golfing for now so there's nothing fancy in my solution. Rust use...

    Today's problem was very straightforward without any gotchas that I ran into.

    I'm going for solve speed rather than code golfing for now so there's nothing fancy in my solution.

    Rust
    use aoc_runner_derive::aoc;
    use regex::Regex;
    
    #[aoc(day2, part1)]
    fn part1(input: &str) -> usize {
        let game_re = Regex::new("Game ([0-9]+):").unwrap();
        let set_re = Regex::new("(?::|;)(.*?)(?:;|$)").unwrap();
        let red_re = Regex::new("([0-9]+) red").unwrap();
        let green_re = Regex::new("([0-9]+) green").unwrap();
        let blue_re = Regex::new("([0-9]+) blue").unwrap();
        input
            .lines()
            .filter_map(|line| {
                let game: usize = game_re.captures(line).unwrap()[1].parse().unwrap();
                let mut i = 0;
                while i < line.len() {
                    if let Some(s) = set_re.find_at(line, i) {
                        if red_re
                            .captures(s.as_str())
                            .map(|c| c[1].parse().unwrap())
                            .unwrap_or(0)
                            > 12
                        {
                            break;
                        }
                        if green_re
                            .captures(s.as_str())
                            .map(|c| c[1].parse().unwrap())
                            .unwrap_or(0)
                            > 13
                        {
                            break;
                        }
                        if blue_re
                            .captures(s.as_str())
                            .map(|c| c[1].parse().unwrap())
                            .unwrap_or(0)
                            > 14
                        {
                            break;
                        }
                        i = s.start() + 1;
                    } else {
                        return Some(game);
                    }
                }
    
                None
            })
            .sum()
    }
    
    #[aoc(day2, part2)]
    fn part2(input: &str) -> usize {
        let set_re = Regex::new("(?::|;)(.*?)(?:;|$)").unwrap();
        let red_re = Regex::new("([0-9]+) red").unwrap();
        let green_re = Regex::new("([0-9]+) green").unwrap();
        let blue_re = Regex::new("([0-9]+) blue").unwrap();
        input
            .lines()
            .map(|line| {
                let mut max_red = 0;
                let mut max_green = 0;
                let mut max_blue = 0;
                let mut i = 0;
                while i < line.len() {
                    if let Some(s) = set_re.find_at(line, i) {
                        max_red = max_red.max(
                            red_re
                                .captures(s.as_str())
                                .map(|c| c[1].parse().unwrap())
                                .unwrap_or(0),
                        );
                        max_green = max_green.max(
                            green_re
                                .captures(s.as_str())
                                .map(|c| c[1].parse().unwrap())
                                .unwrap_or(0),
                        );
                        max_blue = max_blue.max(
                            blue_re
                                .captures(s.as_str())
                                .map(|c| c[1].parse().unwrap())
                                .unwrap_or(0),
                        );
                        i = s.start() + 1;
                    } else {
                        break;
                    }
                }
    
                max_red * max_green * max_blue
            })
            .sum()
    }
    

    Edit: I'll almost certainly need to use something more powerful than just regex at some point so I decided to make another version using logos to refresh my memory of it.
    I'll probably also make a nom version of today's or tomorrow's puzzle.

    Rust - Logos
    use aoc_runner_derive::aoc;
    use logos::{Lexer, Logos};
    
    fn get_count(lex: &mut Lexer<Color>) -> Option<usize> {
        Some(lex.slice().split_once(" ").unwrap().0.parse().unwrap())
    }
    
    #[derive(Logos)]
    enum Color {
        #[regex("[0-9]+ red", get_count)]
        Red(usize),
        #[regex("[0-9]+ green", get_count)]
        Green(usize),
        #[regex("[0-9]+ blue", get_count)]
        Blue(usize),
    
        #[error]
        #[regex(r"(Game [0-9]+|: |, |; )", logos::skip)]
        Error,
    }
    
    #[aoc(day2, part1, logos)]
    fn part1_logos(input: &str) -> usize {
        input
            .lines()
            .enumerate()
            .filter_map(|(game, line)| {
                let mut possible = true;
                for color in Color::lexer(line) {
                    match color {
                        Color::Red(n) if n > 12 => possible = false,
                        Color::Green(n) if n > 13 => possible = false,
                        Color::Blue(n) if n > 14 => possible = false,
                        _ => {},
                    }
                }
    
                if possible {
                    Some(game + 1)
                } else {
                    None
                }
            })
            .sum()
    }
    
    #[aoc(day2, part2, logos)]
    fn part2_logos(input: &str) -> usize {
        input
            .lines()
            .map(|line| {
                let mut max_red = 0;
                let mut max_green = 0;
                let mut max_blue = 0;
                for color in Color::lexer(line) {
                    match color {
                        Color::Red(n) => max_red = max_red.max(n),
                        Color::Green(n) => max_green = max_green.max(n),
                        Color::Blue(n) => max_blue = max_blue.max(n),
                        _ => {},
                    }
                }
    
                max_red * max_green * max_blue
            })
            .sum()
    }
    
    Run Times
    AOC 2023
    Day 2 - Part 1 : 3035
    	generator: 2.863µs,
    	runner: 815.616µs
    
    Day 2 - Part 1 - logos : 3035
    	generator: 2.375µs,
    	runner: 45.886µs
    
    Day 2 - Part 2 : 66027
    	generator: 2.374µs,
    	runner: 683.266µs
    
    Day 2 - Part 2 - logos : 66027
    	generator: 2.375µs,
    	runner: 46.166µs
    
    2 votes
  19. Comment on Day 1: Trebuchet?! in ~comp.advent_of_code

    csos95
    (edited )
    Link
    Rust use aoc_runner_derive::aoc; use regex::Regex; #[aoc(day1, part1)] fn part1(input: &str) -> usize { input .lines() .map(|line| { let mut digits = line.chars().filter(char::is_ascii_digit); let...
    Rust
    use aoc_runner_derive::aoc;
    use regex::Regex;
    
    #[aoc(day1, part1)]
    fn part1(input: &str) -> usize {
        input
            .lines()
            .map(|line| {
                let mut digits = line.chars().filter(char::is_ascii_digit);
    
                let first = digits.next().unwrap();
                let last = match digits.last() {
                    Some(last) => last,
                    None => first,
                };
                let n: usize = format!("{first}{last}").parse().unwrap();
                n
            })
            .sum()
    }
    
    #[aoc(day1, part2)]
    fn part2(input: &str) -> usize {
        let digit_re = Regex::new("([1-9]|one|two|three|four|five|six|seven|eight|nine)").unwrap();
        input
            .lines()
            .map(|line| {
                let mut digits = Vec::new();
                let mut i = 0;
                while i < line.len() {
                    if let Some(m) = digit_re.find_at(line, i) {
                        digits.push(match m.as_str() {
                            "one" => "1",
                            "two" => "2",
                            "three" => "3",
                            "four" => "4",
                            "five" => "5",
                            "six" => "6",
                            "seven" => "7",
                            "eight" => "8",
                            "nine" => "9",
                            c => c,
                        });
                        i = m.start() + 1;
                    } else {
                        break;
                    }
                }
    
                let first = digits.first().unwrap();
                let last = match digits.last() {
                    Some(last) => last,
                    None => first,
                };
                let n: usize = format!("{first}{last}").parse().unwrap();
                n
            })
            .sum()
    }
    

    I'm using cargo-aoc to download my inputs and generate runners.

    There was a little "gotcha" with the second part, that I had to search through my input to figure out.

    Part 2 Hint The spelled out digits can overlap, so if you're using a non-overlapping regex iterator, it'll miss things.

    The furthest I've gotten in AOC before being distracted by something else is day 10, lets see how far I get this year. :D

    EDIT: Went back and did part 1 with nom to refresh myself on it in case it's needed later on.
    I quickly remembered how annoyingly opaque its errors are and have decided not to bother with it for future puzzles unless I absolutely need to.
    I'll stick to regex and logos otherwise.

    Rust - Nom Part 1
    use aoc_runner_derive::aoc;
    use nom::{
        bytes::complete::{take_till, take_while1},
        error::Error,
        multi::many1,
        sequence::preceded,
    };
    
    #[aoc(day1, part1, nom)]
    fn part1_nom(input: &str) -> usize {
        input
            .lines()
            .map(|line| {
                let (_, digits) = many1::<_, _, Error<_>, _>(preceded(
                    take_till(|c: char| c.is_ascii_digit()),
                    take_while1(|c: char| c.is_ascii_digit()),
                ))(line)
                .unwrap();
    
                let first = digits.first().unwrap();
                let last = digits.last().unwrap();
                let n: usize = format!("{first}{last}").parse().unwrap();
                n
            })
            .sum()
    }
    
    6 votes
  20. Comment on Some call us ungrateful middle-class feminists – but this is why women went on strike in Iceland in ~life.women

    csos95
    Link Parent
    It was posted in ~news and wasn't moved to ~life.women until a few hours after the comment you replied to.

    This topic is posted in ~life.women and is about a women's strike.

    It was posted in ~news and wasn't moved to ~life.women until a few hours after the comment you replied to.

    Topic log (4)

       mycketforvirrad added tags 'equality.gender', 'work', 'opinion' and removed tag 'women' (3h 55m ago)
       mycketforvirrad moved from ~life to ~life.women (3h 56m ago)
       mycketforvirrad moved from ~news to ~life (3h 57m ago)
       Fal added tags 'author.maria hjalmtysdottir', 'iceland', 'women', 'strikes', 'feminism', 'protests' (11h 19m ago)
    
    6 votes