csos95's recent activity

  1. Comment on The real problem with toilet paper: Where it comes from in ~enviro

    csos95
    Link Parent
    I have a bidet so I only need enough to dry myself off. I use one or two squares depending on the absorbancy of the roll I have available.

    I have a bidet so I only need enough to dry myself off.
    I use one or two squares depending on the absorbancy of the roll I have available.

  2. Comment on Anyone interested in trying out Kagi? (trial giveaway: round #2) in ~tech

    csos95
    Link Parent
    I used one. Thank you very much!

    I used one.
    Thank you very much!

    1 vote
  3. Comment on Anyone interested in trying out Kagi? (trial giveaway: round #2) in ~tech

    csos95
    (edited )
    Link
    Edit: got an invite from luka below I've been hearing about this for so long, but put off trying it because duckduckgo/google wasn't that bad for me, but recently the experience has tanked quite...

    Edit: got an invite from luka below

    I've been hearing about this for so long, but put off trying it because duckduckgo/google wasn't that bad for me, but recently the experience has tanked quite quickly, especially on mobile.

    I'll do a search and get a full page of nothing but ai generated list articles with a table of contents and sections that over-explain every possible detail/background info about whatever question I searched for, but without an answer for what I wanted.

    So I'd like to give Kagi a try.

    1 vote
  4. Comment on What low-stakes drama is going on in your circles right now? in ~talk

    csos95
    Link Parent
    I thought it was low at first too, but then realized that "washes without soap" is including the people that pass their hands under the water for a split second and then walk out. Which I don't...

    I thought it was low at first too, but then realized that "washes without soap" is including the people that pass their hands under the water for a split second and then walk out.
    Which I don't count as washing your hands at all, but the people doing it probably do.

    3 votes
  5. Comment on Download and transfer for Kindle books discontinued on Feb 26 in ~books

    csos95
    (edited )
    Link Parent
    I briefly listed some of the pro's, but I can go into more specifics now that I've had a few days of using it to refresh my memory. Formats Most of the content I consume, in order of frequency, is...

    I briefly listed some of the pro's, but I can go into more specifics now that I've had a few days of using it to refresh my memory.

    Formats

    Most of the content I consume, in order of frequency, is epub, pdf, or cbz/cbr.

    For epub I had to either use "send to kindle" or convert to a supported format with Calibre and then sync, both of which were annoying extra steps to me.

    Kindles support pdf, but the reading experience is much better with KOReader.
    For me, a stock kindle is simply unusable for reading most pdf's.
    It has two views: fit the page on the screen (which makes the text too small to read comfortably) or zoom in way too much and require scrolling multiple times to read each line of text.
    With KOReader, I have multiple choices for how to fit the page on the screen, it trims the margin whitespaces, it can reflow text, and has a few more things that all add up to something that lets me comfortably read pdf's on my kindle.

    Manga comes in cbz/cbr files, which are the images of the pages in a zip or rar file, so now I can read manga on my kindle.

    Customization

    It has so many customization options that trying to list them all would take forever.
    It has toggles/selections for tons of common style tweaks (margin size/line height/word spacing/font size/font weight (granular instead of just ~3 presets), two column layout, paged/continuous view, toggle embedded styling) in the bottom bar that shows when you open the ui overlay, a menu tree has many more in-depth styling tweaks, a few good preset css stylesheets to choose from, and if that's not enough, you can put in your own css styles.

    Generally, I just set a few options how I like, set them as default, and that's it.
    It's an very nice improvement to me, but not so much that I couldn't stand reading on a stock kindle that doesn't have them.
    Sometimes however, I'll open a book with some truly terrible formatting that would be difficult to read on a stock kindle.
    With KOReader I can just disable the embedded styles and that usually takes care of it.

    OPDS

    The Open Publication Distribution System (OPDS) is a catalog format based on Atom that lets you browse and download content.

    The stock kindle content importing requires that I buy from amazon or email one of a few supported file types to a special email.
    I don't want to buy from amazon and I don't want to have to download a book, email it, wait a bit, and then download it on my kindle.
    The old alternative was to connect my kindle to my computer and sync with Calibre, but I've moved to hosting most of my Calibre library on a remote server rather than locally and having to go to my desktop to physically sync is already annoying enough without having to download and add to my local library first.

    With OPDS catalogs, I can browse, download, and read all on my kindle.

    Right now I have catalogs for Project Gutenberg, Standard Ebooks, my Calibre server instance, and my friend's Komga instance.
    I've been working, very intermittently, on a fanfiction story tracker/reader application.
    The plan is to implement OPDS so that I'll be able to browse and download stories and also have "daily/weekly/unread updates" entries that'll be the new chapters for a given period for the stories I follow in one book.

    Progress Sync

    KOReader has a sync server that lets you sync reading progress.
    It's open source, written in easy to understand lua, and is small enough that it can be quickly implemented in other services (I know at least komga implements it, haven't looked for others).

    I don't just read on my kindle, I read on my phone, tablet, and desktop (and none of them through the kindle apps because they are not great).
    With stock kindle I have to manually move to where I left off reading when changing devices, with this it's synced automatically on all of them.

    6 votes
  6. Comment on Download and transfer for Kindle books discontinued on Feb 26 in ~books

    csos95
    (edited )
    Link
    Relatedly, I found out just recently that there's a new kindle jailbreak that works on all models. I used it yesterday so I can finally use KOReader on my kindle oasis instead of having to switch...
    • Exemplary

    Relatedly, I found out just recently that there's a new kindle jailbreak that works on all models.
    I used it yesterday so I can finally use KOReader on my kindle oasis instead of having to switch to my older kindle paperwhite (jailbroken years ago) when I want to read stuff I didn't buy on amazon or use "send to kindle" with.

    KOReader supports many formats, has a ton of customization options, and supports opds so I can download books directly from sources such as Project Gutenber, Standard Ebooks, and my Calibre instance.

    https://kindlemodding.org/jailbreaking/WinterBreak/

    28 votes
  7. Comment on Day 9: Disk Fragmenter in ~comp.advent_of_code

    csos95
    Link
    Took me longer to get around to doing it than I expected. Had to fiddle with part two a bit to get it to complete in a reasonable time because of Rhai's performance. Rhai Solution import "utils"...

    Took me longer to get around to doing it than I expected.
    Had to fiddle with part two a bit to get it to complete in a reasonable time because of Rhai's performance.

    Rhai Solution
    import "utils" as utils;
    
    let input = utils::get_input(9, false);
    
    // PART 1
    let data = input.to_chars().map(|c| c.to_string().parse_int());
    let head = 0;
    let tail = if data.len() % 2 == 0 {
        data.len() - 2
    } else {
        data.len() - 1
    };
    
    let output = [];
    
    while head <= tail {
        let free_size = data[head];
        let file_size = data[tail];
        let file_position = tail/2;
    
        if head % 2 == 0 {
            output.push([head/2, data[head]]);
            head += 1;
        } else if free_size == file_size {
            output.push([file_position, file_size]);
            head += 1;
            tail -= 2;
        } else if free_size > file_size {
            output.push([file_position, file_size]);
            data[head] -= file_size;
            tail -= 2;
        } else if free_size < file_size {
            output.push([file_position, free_size]);
            data[tail] -= free_size;
            head += 1;
        }
    }
    
    let i = 0;
    let total = 0;
    for file in output {
        for j in range(0, file[1]) {
            total += (i + j) * file[0];
        }
        i += file[1];
    }
    
    print(`part 1: ${total}`);
    
    // PART 2
    let data = input.to_chars().map(|c, i| {
        let size = c.to_string().parse_int();
        if i % 2 == 0 {
            [i/2, size]
        } else {
            [-1, size]
        }
    });
    
    let num_files = if data.len() % 2 == 0 {
        data.len() / 2
    } else {
        data.len() / 2 + 1
    };
    
    let last_file_pos = data.len()-1;
    let first_free_pos = 0;
    for file_id in range(num_files-1, 0, -1) {
        let file_pos = -1;
        for i in range(last_file_pos, 0, -1) {
            if data[i][0] == file_id {
                file_pos = i;
                last_file_pos = i;
                break;
            }
        }
        let file = data[file_pos];
    
        let free_pos = -1;
        let first_free = false;
        for i in range(first_free_pos, file_pos) {
            let entry = data[i];
            if entry[0] == -1 {
                if !first_free {
                    first_free = true;
                    first_free_pos = i;
                }
                if entry[1] >= file[1] {
                    free_pos = i;
                    break;
                }
            }
        }
    
        if free_pos == -1 {
            continue;
        } else if data[free_pos][1] > file[1] {
            data[free_pos][1] -= file[1];
            data[file_pos][0] = -1;
            data.insert(free_pos, file);
        } else {
            data[free_pos][0] = file[0];
            data[file_pos][0] = -1;
        }
    }
    
    let i = 0;
    let total = 0;
    for file in data {
        if file[0] != -1 {
            for j in range(0, file[1]) {
                total += (i + j) * file[0];
            }
        }
        i += file[1];
    }
    
    print(`part 2: ${total}`);
    
    1 vote
  8. Comment on Day 8: Resonant Collinearity in ~comp.advent_of_code

    csos95
    Link
    Another easy day and one that didn't have Rhai's performance hurting me with the solution I came up with. I was really expecting tonight's to add onto day 3's. I'm starting to wonder if any...

    Another easy day and one that didn't have Rhai's performance hurting me with the solution I came up with.
    I was really expecting tonight's to add onto day 3's.
    I'm starting to wonder if any Intcode-like thing is coming, I was really looking forward to it.

    Rhai Solution
    import "utils" as utils;
    
    let input = utils::get_input(8, false);
    
    let grid = new_grid(input.split("\n").map(|line| line.to_chars()));
    
    let antennas = #{};
    for pos in grid.cell_positions() {
        let cell = grid.cell(pos).to_string();
        if cell == "." {
            continue;
        }
        let positions = antennas.get(cell) ?? [];
        positions.push(pos);
        antennas.set(cell, positions);
    }
    
    // PART 1
    let antinodes = #{};
    for frequency in antennas.keys() {
        let positions = antennas.get(frequency);
        for pair in positions.combinations(2) {
            let diff = new_point(pair[0].x - pair[1].x, pair[0].y - pair[1].y);
            for point in [pair[0] - diff, pair[0] + diff, pair[1] - diff, pair[1] + diff] {
                if point == pair[0] || point == pair[1] || !grid.is_valid(point) {
                    continue;
                }
                antinodes.set(point.to_string(), true);
            }
        }
    }
    
    print(`part 1: ${antinodes.len()}`);
    
    // PART 2
    let antinodes = #{};
    for frequency in antennas.keys() {
        let positions = antennas.get(frequency);
        for pair in positions.combinations(2) {
            let diff = new_point(pair[0].x - pair[1].x, pair[0].y - pair[1].y);
            for situation in 0..4 {
                let point = switch situation {
                    0 | 1 => pair[0],
                    2 | 3 => pair[1],
                };
    
                loop {
                    if !grid.is_valid(point) {
                        break;
                    }
                    antinodes.set(point.to_string(), true);
                    switch situation {
                        0 | 2 => point -= diff,
                        1 | 3 => point += diff,
                    }
                }
            }
        }
    }
    
    print(`part 2: ${antinodes.len()}`);
    
    3 votes
  9. Comment on Day 7: Bridge Repair in ~comp.advent_of_code

    csos95
    (edited )
    Link
    Tonight's was really easy compared to last night's (at least compared to my attempt at a non-brute for solution to that using graphs). Most of the time it took me to do part one was just searching...

    Tonight's was really easy compared to last night's (at least compared to my attempt at a non-brute for solution to that using graphs).
    Most of the time it took me to do part one was just searching for the right iterator combination in itertools and most of the time to do part two was runtime (this is the second night Rhai's low performance showed up, 2:08 runtime).

    Rhai Solution
    import "utils" as utils;
    
    let input = utils::get_input(7, false).split("\n").map(|line| {
        let parts = line.split(":");
        let solution = parts[0].parse_int();
        parts[1].trim();
        let values = parts[1].split(" ").map(|v| v.parse_int());
        #{solution: solution, values: values}
    });
    
    let total = 0;
    for equation in input {
        let values = equation.values;
        let op_sets = ["+", "*"].permutations_with_replacement(values.len()-1);
        for ops in op_sets {
            let value = values[0];
            for (op, i) in ops {
                switch op {
                    "+" => value += values[i + 1],
                    "*" => value *= values[i + 1],
                }
            }
            if value == equation.solution {
                total += value;
                break;
            }
        }
    }
    
    print(`part 1: ${total}`);
    
    let total = 0;
    for equation in input {
        let values = equation.values;
        let op_sets = ["+", "*", "||"].permutations_with_replacement(values.len()-1);
        for ops in op_sets {
            let value = values[0];
            for (op, i) in ops {
                switch op {
                    "+" => value += values[i + 1],
                    "*" => value *= values[i + 1],
                    "||" => value = `${value}${values[i + 1]}`.parse_int(),
                }
            }
            if value == equation.solution {
                total += value;
                break;
            }
        }
    }
    
    print(`part 2: ${total}`);
    
    2 votes
  10. Comment on Day 6: Guard Gallivant in ~comp.advent_of_code

    csos95
    (edited )
    Link
    Part two was rough. I had a nice solution, but it didn't work for the full input. I worked on it for a while, but eventually gave up and did a brute force solution. While it was running, I...

    Part two was rough.
    I had a nice solution, but it didn't work for the full input.
    I worked on it for a while, but eventually gave up and did a brute force solution.
    While it was running, I realized why my nice solution wouldn't work in all cases.
    And the extension to make it work is a lesser brute-force, but I also have to juggle a bunch of extra temporary state.

    This is the first one where the performance of Rhai really reared its head (not only is it a tree-walk interpreter, it passes everything by value).
    It took 7:48 to run part two.

    I'll likely come back later and clean it up with a better solution.
    I have a few ideas for what to do better, but I'm a bit tired and salty at the moment.

    Rhai Solution
    import "utils" as utils;
    
    let input = utils::get_input(6, false);
    
    // AUXILIARY
    fn next_dir(dir) {
        switch dir {
            "up" => "right",
            "right" => "down",
            "down" => "left",
            "left" => "up",
        }
    }
    
    fn dir_offset(dir) {
        switch dir {
            "up" => new_point(0, -1),
            "down" => new_point(0, 1),
            "left" => new_point(-1, 0),
            "right" => new_point(1, 0),
        }
    }
    
    // PART 1
    let grid = new_grid(input.split("\n").map(|line| line.to_chars()));
    
    let guard_pos = ();
    let guard_dir = ();
    for item in grid.cells().zip(grid.cell_positions()) {
        if item[0] == '^' {
            guard_dir = "up";
        } if item[0] == 'v' {
            guard_dir = "down";
        } if item[0] == '<' {
            guard_dir = "left";
        } else if item[0] == '>' {
            guard_dir = "right";
        }
        if guard_dir != () {
            guard_pos = item[1];
            grid.set_cell(guard_pos, 'X');
            break;
        }
    }
    
    while grid.is_valid(guard_pos) {
        let offset = dir_offset(guard_dir);
    
        if grid.cell(guard_pos + offset) != '#' {
            guard_pos = guard_pos + offset;
            grid.set_cell(guard_pos, 'X');
        } else {
            guard_dir = next_dir(guard_dir);
        }
    }
    
    let total = 0;
    for cell in grid.cells() {
        if cell == 'X' {
            total += 1;
        }
    }
    
    print(`part 1: ${total}`);
    
    // PART 2
    let prev_grid = grid;
    let grid = new_grid(input.split("\n").map(|line| line.to_chars()));
    
    let guard_pos = ();
    let guard_dir = ();
    for item in grid.cells().zip(grid.cell_positions()) {
        if item[0] == '^' {
            guard_dir = "up";
        } if item[0] == 'v' {
            guard_dir = "down";
        } if item[0] == '<' {
            guard_dir = "left";
        } else if item[0] == '>' {
            guard_dir = "right";
        }
        if guard_dir != () {
            guard_pos = item[1];
            grid.set_cell(guard_pos, '.');
            break;
        }
    }
    
    let total = 0;
    let original_guard_pos = guard_pos;
    let original_guard_dir = guard_dir;
    for pos in grid.cell_positions() {
        if (pos.x == original_guard_pos.x && pos.y == original_guard_pos.y) || prev_grid.cell(pos) != 'X' {
            continue;
        }
    
        guard_pos = original_guard_pos;
        guard_dir = original_guard_dir;
    
        let graph = new_graph();
        let point_to_node = #{};
        point_to_node[`${guard_pos}-${guard_dir}`] = graph.add_node(0.0);
    
        grid.set_cell(pos, '#');
    
        let found = false;
        let prev_node = point_to_node[`${guard_pos}-${guard_dir}`];
        let curr_node = ();
        while grid.is_valid(guard_pos) {
            let offset = dir_offset(guard_dir);
    
            if grid.cell(guard_pos + offset) != '#' {
                guard_pos = guard_pos + offset;
            } else {
                guard_dir = next_dir(guard_dir);
            }
    
            curr_node = point_to_node[`${guard_pos}-${guard_dir}`];
            if curr_node == () {
                curr_node = graph.add_node(0.0);
                point_to_node[`${guard_pos}-${guard_dir}`] = curr_node;
            }
    
            if !graph.contains_edge(prev_node, curr_node) {
                graph.add_edge(prev_node, curr_node, 0.0);
            }
            if graph.is_cyclic_directed() {
                found = true;
                break;
            }
            prev_node = curr_node;
        }
    
        if found {
            total += 1;
        }
    
        grid.set_cell(pos, '.');
    }
    
    print(`part 2: ${total}`);
    
    2 votes
  11. Comment on Day 5: Print Queue in ~comp.advent_of_code

    csos95
    (edited )
    Link
    This one really tripped me up. When I read the challenge, the first thing that popped into my head was "oh! A graph problem!" and I set to work adding an interface to the petgraph library. After...

    This one really tripped me up.

    When I read the challenge, the first thing that popped into my head was "oh! A graph problem!" and I set to work adding an interface to the petgraph library.
    After finishing it, getting all the data inserted with pages as nodes and rules as edges, I did a topological sort on the graph to get an ordering of all pages so I could just check that each page in an update has a higher index than the previous one.

    I ran it on the example input and it worked perfectly!
    Then I ran it on the actual input and it errored!
    There was a cycle somewhere!

    I tried using a few functions to find the page cycle (the error only returns the node it was at when it found a cylce), but couldn't figure it out.
    However, while looking at the input, I realized that I didn't even need to use a graph to begin with.
    The rules had relations between relevant pages directly, I didn't need to check transitive rules.

    I'd already gone through the trouble of building a graph so I went ahead and used it for the simpler problem of checking if two pages have a rule.

    Rhai Solution
    import "utils" as utils;
    
    let input = utils::get_input(5, false).split("\n\n");
    
    let ordering_rules = input[0]
                    .split("\n")
                    .map(|line| line
                                    .split("|")
                                    .map(|id| id.parse_int()));
    
    let page_updates = input[1]
                     .split("\n")
                     .map(|line| line
                                    .split(",")
                                    .map(|id| id.parse_int()));
    
    // get all page numbers, deduplicated
    let pages = [];
    for rule in ordering_rules {
        pages.push(rule[0]);
        pages.push(rule[1]);
    }
    
    for update in page_updates {
        for page in update {
            pages.push(page);
        }
    }
    
    pages.sort();
    pages.dedup();
    
    // add a node for each page and keep a mapping of page to node and vice versa
    let page_graph = new_graph();
    let page_to_node = new_int_map();
    let node_to_page = new_int_map();
    for page in pages {
        let node = page_graph.add_node(0.0);
        page_to_node[page] = node;
        node_to_page[node.index()] = page;
    }
    
    // add ordering rules as nodes
    for rule in ordering_rules {
        if rule[0] != rule[1] {
            page_graph.add_edge(page_to_node[rule[0]], page_to_node[rule[1]], 0.0);
        }
    }
    
    // PART 1
    let total = 0;
    for update in page_updates {
        // print(update);
        let valid = true;
        for i in 0..update.len()-1 {
            if page_graph.contains_edge(page_to_node[update[i+1]], page_to_node[update[i]]) {
                valid = false;
                break;
            }
        }
        if valid {
            total += update[update.len() / 2];
        }
    }
    
    print(`part 1: ${total}`);
    
    // PART 2
    let total = 0;
    for update in page_updates {
        let valid = true;
        for i in 0..update.len()-1 {
            if page_graph.contains_edge(page_to_node[update[i+1]], page_to_node[update[i]]) {
                valid = false;
                break;
            }
        }
        if !valid {
            let i = 0;
            while i < update.len()-1 {
                if page_graph.contains_edge(page_to_node[update[i+1]], page_to_node[update[i]]) {
                    let a = update[i];
                    update[i] = update[i+1];
                    update[i+1] = a;
                    i = 0;
                } else {
                    i += 1;
                }
            }
            total += update[update.len() / 2];
        }
    }
    
    print(`part 2: ${total}`);
    

    Edit: I found out from a reddit post why the cycle caused me issues with topological sort, but not in the other solution.

    Excerpt from the challenge:

    The notation X|Y means that if both page number X and page number Y are to be produced as part of an update, page number X must be printed at some point before page number Y.

    The cycle never appears among the pages for a given update.

    3 votes
  12. Comment on Misogynist hacker who threatened the wrong woman (hacker) and found out in ~comp

    csos95
    Link Parent
    It loads the rest of the article with Javascript after about ten seconds with no indication that it's not the entire article and there's more to load.

    It loads the rest of the article with Javascript after about ten seconds with no indication that it's not the entire article and there's more to load.

    4 votes
  13. Comment on Day 4: Ceres Search in ~comp.advent_of_code

    csos95
    (edited )
    Link
    My initial solution isn't as nice as I'd like it to be, but it works. I might go back over it tomorrow and add some functions to my utils module to make working with grids easier. Rhai Solution...

    My initial solution isn't as nice as I'd like it to be, but it works.
    I might go back over it tomorrow and add some functions to my utils module to make working with grids easier.

    Rhai Solution
    import "utils" as utils;
    
    let input = utils::get_input(4, false);
    
    let grid = input.split("\n").map(|line| line.to_chars());
    let height = grid.len();
    let width = grid[0].len();
    
    let total = 0;
    for y in 0..height {
        for x in 0..width {
            for vy in range(-1, 2, 1) {
                for vx in range(-1, 2, 1) {
                    if vy == 0 && vx == 0 {
                        continue;
                    }
                    if has_xmas!(x, y, vx, vy) {
                        total += 1;
                    }
                }
            }
        }
    }
    
    fn has_xmas(x, y, vx, vy) {
        x+vx*3 < width && y+vy*3 < height
          && x+vx*3 >= 0 && y+vy*3 >= 0
          &&grid[y][x] == 'X'
          && grid[y+vy*1][x+vx*1] == 'M'
          && grid[y+vy*2][x+vx*2] == 'A'
          && grid[y+vy*3][x+vx*3] == 'S'
    }
    
    print(`part 1: ${total}`);
    
    let total = 0;
    for y in 0..height {
        for x in 0..width {
            if grid[y][x] != 'A' || x-1 < 0 || x+1 >= width || y-1 < 0 || y+1 >= height {
                continue;
            }
            let first_diag = grid[y-1][x-1] + grid[y][x] + grid[y+1][x+1];
            let second_diag = grid[y-1][x+1] + grid[y][x] + grid[y+1][x-1];
            if (first_diag == "MAS" || first_diag == "SAM") && (second_diag == "MAS" || second_diag == "SAM") {
                total += 1;
            }
        }
    }
    
    print(`part 2: ${total}`);
    

    Edit: I added grid and point types and redid my solution using them.

    Rhai Solution
    import "utils" as utils;
    
    let input = utils::get_input(4, false);
    
    let grid = new_grid(input.split("\n").map(|line| line.to_chars()));
    
    let total = 0;
    for position in grid.cell_positions() {
        for offset in neighbor_offsets() {
            if has_xmas!(position, offset) {
                total += 1;
            }
        }
    }
    
    fn has_xmas(point, dp) {
        grid.is_valid(point + dp * 3)
          && grid.cell(point) == 'X'
          && grid.cell(point + dp) == 'M'
          && grid.cell(point + dp * 2) == 'A'
          && grid.cell(point + dp * 3) == 'S'
    }
    
    print(`part 1: ${total}`);
    
    let total = 0;
    for pos in grid.cell_positions() {
        if grid.cell(pos) != 'A'
          || !grid.is_valid(pos - 1)
          || !grid.is_valid(pos + 1) {
            continue;
        }
        let first_diag = grid.cell(pos - 1) + grid.cell(pos) + grid.cell(pos + 1);
        let second_diag = grid.cell(pos + new_point(1, -1)) + grid.cell(pos) + grid.cell(pos + new_point(-1, 1));
        if first_diag in ["MAS", "SAM"] && second_diag in ["MAS", "SAM"] {
            total += 1;
        }
    }
    
    print(`part 2: ${total}`);
    
    1 vote
  14. Comment on Day 3: Mull It Over in ~comp.advent_of_code

    csos95
    Link Parent
    That was my first thought when I read the challenge and I'm looking forward to it!

    That was my first thought when I read the challenge and I'm looking forward to it!

  15. Comment on Day 3: Mull It Over in ~comp.advent_of_code

    csos95
    (edited )
    Link
    I'm pretty tired tonight (finally remembered to drag myself to the gym a few hours after dinner) so my initial solution is a bit less compact than it could be. I'll probably go back over it...

    I'm pretty tired tonight (finally remembered to drag myself to the gym a few hours after dinner) so my initial solution is a bit less compact than it could be.
    I'll probably go back over it tomorrow and also write a few functions for my utils module.

    Rhai Solution
    import "utils" as utils;
    
    let input = utils::get_input(3, false);
    
    // PART 1
    let total = 0;
    for (section, i) in input.split("mul(").extract(1) {
        let possible_params = section.split(")");
        if possible_params.len() < 2 {
            continue;
        }
        let possible_digits = possible_params[0].split(",");
        if possible_digits.len() != 2
           || possible_digits[0].to_chars().some(|c| c < '0' || c > '9')
           || possible_digits[1].to_chars().some(|c| c < '0' || c > '9') {
            continue;
        }
        let x = possible_digits[0].parse_int();
        let y = possible_digits[1].parse_int();
        total += x * y;
    }
    
    print(`part 1: ${total}`);
    
    // PART 2
    let total = 0;
    let enabled = true;
    for (section, i) in input.split("mul(") {
        if enabled && i != 0 {
            let possible_params = section.split(")");
            if possible_params.len() < 2 {
                continue;
            }
            let possible_digits = possible_params[0].split(",");
            if possible_digits.len() != 2
               || possible_digits[0].to_chars().some(|c| c < '0' || c > '9')
               || possible_digits[1].to_chars().some(|c| c < '0' || c > '9') {
                continue;
            }
            let x = possible_digits[0].parse_int();
            let y = possible_digits[1].parse_int();
            total += x * y;
        }
        if enabled && section.contains("don't()") {
            enabled = false;
        }
        if !enabled && section.contains("do()") {
            enabled = true;
        }
    }
    
    print(`part 2: ${total}`);
    
    My current utils.rhai
    fn get_input(day, example) {
        if example {
            let input = open_file(`day${day}_example.txt`)
                .read_string();
            input.trim();
            input
        } else {
            let input = open_file(`day${day}.txt`)
                .read_string();
            input.trim();
            input
        }
    }
    
    fn is_sorted(array) {
        for i in 0..array.len()-1 {
            if array[i] > array[i+1] {
                return false;
            }
        }
        true
    }
    
    fn is_rev_sorted(array) {
        for i in range(array.len()-1, 0, -1) {
            if array[i] > array[i-1] {
                return false;
            }
        }
        true
    }
    
    fn is_ordered(array) {
        let fails = 0;
        for i in 0..array.len()-1 {
            if array[i] > array[i+1] {
                fails += 1;
                break;
            }
        }
        for i in range(array.len()-1, 0, -1) {
            if array[i] > array[i-1] {
                fails += 1;
                break;
            }
        }
        fails < 2
    }
    

    Edit: I added basic regex to Rhai with new_regex(regex_string) and captures(Regex, input) functions using the regex crate and made a shorter solution.

    Rhai Solution
    import "utils" as utils;
    
    let input = utils::get_input(3, false);
    
    // PART 1
    let total = 0;
    let regex = new_regex(#"mul\(([0-9]+),([0-9]+)\)"#);
    for captures in regex.captures(input) {
        let x = captures[1].parse_int();
        let y = captures[2].parse_int();
        total += x * y;
    }
    
    print(`part 1: ${total}`);
    
    // PART 2
    let total = 0;
    let enabled = true;
    let regex = new_regex(#"(mul|do|don't)\((?:([0-9]+),([0-9]+))?\)"#);
    for captures in regex.captures(input) {
        switch captures[1] {
            "don't" => enabled = false,
            "do" => enabled = true,
            "mul" if enabled => {
                let x = captures[2].parse_int();
                let y = captures[3].parse_int();
                total += x * y;
            }
        }
    }
    
    print(`part 2: ${total}`);
    
    2 votes
  16. Comment on Day 2: Red-Nosed Reports in ~comp.advent_of_code

    csos95
    (edited )
    Link
    Today's took me a bit longer for the second part because I misunderstood the change and for some reason thought if there was a single bad level I could simply remove it without checking if the new...

    Today's took me a bit longer for the second part because I misunderstood the change and for some reason thought if there was a single bad level I could simply remove it without checking if the new report is safe.

    Once I realized my mistake it was pretty straightforward.

    My only real annoyance with Rhai so far is that there's no good mode for emacs for it.
    I found an old one someone made, but the way it handles the indentations is really janky.
    It seems like it's sort of trying to line the indentation up with the first letter of the second token on the line above and if that fails, it just does a massive amount of indentation.
    So I've got anywhere from two to four spaces of indentation depending on the line.

    Rhai Code
    fn get_input(day, example) {
       if example {
          let input = open_file(`day${day}_example.txt`)
          	  .read_string();
          input.trim();
          input
       } else {
          let input = open_file(`day${day}.txt`)
              .read_string();
          input.trim();
          input
       }
    }
    
    // INPUT
    let input = get_input(2, false);
    let reports = [];
    for line in input.split("\n") {
        let report = [];
        for level in line.split() {
        	report.push(level.parse_int());
        }
        reports.push(report);
    }
    
    // AUXILIARY
    fn is_decreasing(report) {
       if report.len() < 2 {
          true
       } else {
          report[0] > report[1] && is_decreasing(report.extract(1))
       }
    }
    
    fn is_increasing(report) {
       if report.len() < 2 {
          true
       } else {
          report[0] < report[1] && is_increasing(report.extract(1))
       }
    }
    
    fn is_gradual(report) {
       if report.len() < 2 {
          true
       } else {
          let diff = (report[0] - report[1]).abs();
          diff > 0 && diff < 4 && is_gradual(report.extract(1))
       }
    }
    
    // PART 1
    let safe = 0;
    for report in reports {
        if (is_decreasing(report) || is_increasing(report)) && is_gradual(report) {
           safe += 1;
        }
    }
    
    print(`part 1: ${safe}`);
    
    // PART 2
    let safe = 0;
    
    for report in reports {
        if (is_decreasing(report) || is_increasing(report)) && is_gradual(report) {
           safe += 1;
        } else {
           for i in 0..report.len() {
              let report_copy = report;
    	  report_copy.remove(i);
              if (is_decreasing(report_copy) || is_increasing(report_copy)) && is_gradual(report_copy) {
                 safe += 1;
    	     break;
    	  }
           }
        }
    }
    
    print(`part 2: ${safe}`);
    

    Edit: I made it more compact and moved the get_input function to a separate file since it'll be used every day.

    Rhai Code
    import "utils" as utils;
    
    // INPUT
    let reports = utils::get_input(2, false)
       .split("\n")
       .map(|report| report
       		    .split()
    		    .map(|l| l.parse_int()));
    
    // AUXILIARY
    fn is_safe_inner(report, last_diff) {
       if report.len() < 2 {
          return true;
       }
       let diff = report[0] - report[1];
       let diff_abs = diff.abs();
       if last_diff != () && last_diff * diff < 0 {
          return false;
       }
       diff_abs > 0 && diff_abs < 4 && is_safe_inner(report.extract(1), diff)
    }
    
    fn is_safe(report) {
       is_safe_inner(report, ())
    }
    
    // PART 1
    let safe = reports.filter(is_safe).len();
    
    print(`part 1: ${safe}`);
    
    // PART 2
    let safe = reports.filter(|report| {
        if is_safe(report) {
           return true;
        } else {
           for i in 0..report.len() {
              let report = report;
    	  report.remove(i);
              if is_safe(report) {
    	     return true;
    	  }
           }
        }
        false
    }).len();
    
    print(`part 2: ${safe}`);
    
    4 votes
  17. Comment on Day 1: Historian Hysteria in ~comp.advent_of_code

    csos95
    Link
    This year I'm doing Advent of Code in rhai, an embedded scripting language for rust, because I've been meaning to try it out for a while. It's not amazing speed-wise because it's a treewalk...

    This year I'm doing Advent of Code in rhai, an embedded scripting language for rust, because I've been meaning to try it out for a while.

    It's not amazing speed-wise because it's a treewalk interpreter, but it has a lot of nice features such as many safety options for when you need to run untrusted code, custom operators/syntax, and a decent api for making rust types and functions usable within it.

    I initially did it using the built-in object map type, but it seems to only support string keys and I was annoyed that I had to convert the integer keys I had with every get and set.
    I could've just re-processed the input to have the values as strings again and not need to do any conversion, but I'm almost certainly going to need to use integer keys more than once so I added an HashMap<INT, Dynamic> type named IntMap to Rhai.

    It was very easy to get started with, I had no trouble finding the methods I needed in the docs to complete the initial challenge or for creating the IntMap type and methods.
    I'm quite impressed with how easy it was to extend.

    Rhai Code
    fn get_input(day) {
       let input = open_file(`day${day}.txt`)
           .read_string();
       input.trim();
       input
    }
    
    let input = get_input(1);
    
    // PART 1
    let list1 = [];
    let list2 = [];
    for line in input.split("\n") {
        let items = line.split();
        list1.push(items[0].parse_int());
        list2.push(items[1].parse_int());
    }
    
    list1.sort();
    list2.sort();
    
    let total = 0;
    for i in 0..list1.len() {
        total += (list1[i] - list2[i]).abs();
    }
    
    print(`part1: ${total}`);
    
    // PART 2
    let list2_occurrences = new_int_map();
    
    for location in list2 {
        let prev = list2_occurrences[location] ?? 0;
        list2_occurrences[location] = prev + 1;
    }
    
    let total = 0;
    for location in list1 {
        let occurrences = list2_occurrences[location] ?? 0;
        total += location * occurrences;
    }
    
    print(`part2: ${total}`);
    
    4 votes
  18. Comment on How do you build strong online communities? in ~talk

    csos95
    Link Parent
    Every funeral I've been to (which isn't a ton, but it's more than a few) has had plenty of jokes told. Everyone grieves differently, but if I were at a funeral with zero jokes told I'd probably...

    I mean if you tell a joke at a funeral you cannot read the room.

    Every funeral I've been to (which isn't a ton, but it's more than a few) has had plenty of jokes told.
    Everyone grieves differently, but if I were at a funeral with zero jokes told I'd probably wonder if anyone around even liked the person or ever had any good times with them.

    11 votes
  19. Comment on Any recommendations for books, novellas and short story collections? in ~books

    csos95
    Link
    I recently read The Dispatcher by John Scalzi and enjoyed it.

    I recently read The Dispatcher by John Scalzi and enjoyed it.

    In the wake of an unexplained phenomenon worldwide — when people are deliberately killed, they almost always disappear from their site of death and reappear, reset to several hours earlier, in a safe place — the profession of "Dispatcher" evolves. Dispatchers euthanize mortally-injured people before their natural deaths, enabling them to reset. Tony Valdez is a Dispatcher recruited by the police to assist in investigating the disappearance of another Dispatcher.

    4 votes
  20. Comment on Tildes Book Club - Spring 2025 nomination thread - Books from minority or diverse or disadvantaged perspectives in ~books

    csos95
    (edited )
    Link
    Born a Crime: Stories from a South African Childhood by Trevor Noah.

    Born a Crime: Stories from a South African Childhood by Trevor Noah.

    The book details Trevor Noah's experiences growing up in South Africa during the apartheid era. Noah's parents were a white Swiss-German father and a black Xhosa mother. At the time of Noah's birth in 1984, their interracial relationship was illegal under the Immorality Act, 1957. According to Noah, "for [him] to be born as a mixed-race baby" was to be "born a crime." Interracial relations were decriminalised when the Immorality Act was amended in 1985. As a mixed-race person, Noah was classified as a "Coloured" in accordance to the apartheid system of racial classification. Noah was raised primarily by his mother and maternal grandmother in Soweto.

    5 votes