spit-evil-olive-tips's recent activity
-
Comment on Headphone/Earbud recommendation - Is there one product that fits all my needs? in ~tech
-
Comment on Factorio will increase in price next week "to account for the level of inflation" in ~games
spit-evil-olive-tips there's always one OK, would you be satisfied if I phrased it as "there haven't been any DLC or microtransactions in the past 7 years, but there is an upcoming expansion announced"there's no DLC or microtransactions, ever.
That's actually not the case.
there's always one
OK, would you be satisfied if I phrased it as "there haven't been any DLC or microtransactions in the past 7 years, but there is an upcoming expansion announced"
-
Comment on Factorio will increase in price next week "to account for the level of inflation" in ~games
spit-evil-olive-tips for some important context, Factorio doesn't participate in Steam sales. from the company founder / lead developer, in 2016: the response to this news seems to have been pretty polarizing. most...for some important context, Factorio doesn't participate in Steam sales. from the company founder / lead developer, in 2016:
Not having a sale ever is part of our philosophy. In short term, they are good and bring extra money, but we are targeting long term. I believe that searching for sales is wasted time, and people should decide on the price and value, but putting option of wasting time to search for deals or waiting seems like bad part of the equation.
the response to this news seems to have been pretty polarizing. most existing players, including myself, seem to be shrugging and saying it's no big deal (typified by this "always has been" post on the /r/factoriohno meme subreddit). I have over 1000 hours in the game, which works out to 3 cents per hour. Factorio is by at least an order of magnitude the most cost-effective form of entertainment I've ever bought.
meanwhile, the backlash that I've seen has been mostly among people who don't own the game, and it has seemed outsized relative to a $5 increase (a 16.7% increase in 7 years since the launch amounts to 2.2% annualized increase, well below the rate of inflation).
reading between the lines, I think there's a lot of people who've been conditioned by Steam sales to think that all games follow that model, and that you can get any game for 50-95% off if you just wait long enough. I think they may have had Factorio sitting on their wishlist, possibly for years, and the news disappointed them because it's not a difference between $30 and $35 but between a hypothetical $5 or $10 sale price and $35.
the standard practice in most of the games industry has become pretty extreme market segmentation - if you want to play the game on launch day, you'll pay full price, typically $60 or $70 for a AAA game. if you don't mind waiting a few years (and paying attention to your Steam "a game on your wishlist is on sale" notifications...) you can probably pick it up for 90% off. and then on top of that there's the panoply of DLC, microtransactions, skins only available with the pre-order edition, pay-to-win items, etc etc. Alice and Bob could play what is functionally the exact same game, except Alice paid $200 and Bob paid $2.
Factorio rejects all that, and just says that the price is the price, and it never goes on sale. there's no DLC or microtransactions, ever. they sell a handful of shirts but AFAIK, their only other revenue is actual full game purchases. Alice bought the game 5 years ago and has several thousand hours, Bob bought the game a month ago. they both paid $30.
Wube also gave a week of advance notice about the price increase, something they're not at all obligated to do. so anyone who thought it was fairly priced at $30 but overpriced at $35 has a chance to buy it at the old price.
-
Comment on The lights have been on at a Massachusetts school for over a year because no one can turn them off in ~tech
spit-evil-olive-tips not just that, but also running on specialized, non-commodity hardware: you could conceivably do a centralized control system like this effectively - but with commodity hardware and open-source...not just that, but also running on specialized, non-commodity hardware:
the parts they need to replace the system at the school have finally arrived from the factory in China and they expect to do the installation over the February break.
Osborne said they had no choice but to go back to Reflex Lighting and, with the help of the company’s electrical engineers, they came up with what he described as a “piecemeal” approach to solving the problem by replacing the server, the lighting control boards and other hardware.
Osborne and Provost also reported that “the remaining equipment has been back ordered multiple times” and the district was given a new delivery date of Oct. 14, 2022.
you could conceivably do a centralized control system like this effectively - but with commodity hardware and open-source software. and even then, as you said, you would want it to fail into a degraded mode that relies on local sensors & switches.
-
Comment on Alec Baldwin and ‘Rust’ armorer to face involuntary manslaughter charges in shooting death in ~movies
spit-evil-olive-tips these are criminal charges, so Baldwin being the one who pulled the trigger will be the primary thing. they already settled a separate civil lawsuit with Halyna Hutchins' widow, and for those...I wonder if Baldwin is more likely to be prosecuted as the one that held the gun, or in his capacity as the producer that allowed such poor on-set safety.
these are criminal charges, so Baldwin being the one who pulled the trigger will be the primary thing. they already settled a separate civil lawsuit with Halyna Hutchins' widow, and for those civil torts (negligence and wrongful death) his role as producer would have been a much bigger thing, because it factors into determining degree of responsibility for damages.
this page has the legal definition of involuntary manslaughter under New Mexico law. emphasis added:
Manslaughter is the unlawful killing of a human being without malice.
...
Involuntary manslaughter consists of manslaughter committed in the commission of an unlawful act not amounting to felony, or in the commission of a lawful act which might produce death in an unlawful manner or without due caution and circumspection.
I'll be surprised if this goes to trial, because I suspect a jury would have no problem finding Baldwin meets those criteria. no malice required, just that he acted without due caution and it caused Halyna Hutchins' death. and he was charged with an enhancement which carries a mandatory minimum:
They also will be charged with an enhancement for use of a firearm which carries a mandatory minimum sentence of five years.
I think the most likely outcome will be for Baldwin and Reed (the 24 year old lead armorer) to plead guilty in exchange for reduced charges, including dropping the mandatory minimum0 enhancement. they'll do a few months in jail (if at all) and it'll be at one of the bare-minimum-security facilities reserved for wealthy and high-status people.
0: I hate how misleading "mandatory minimum" is. they are only mandatory in the sense that they take away discretion from the judge and the jury. the net effect is that all the discretion rests with the prosecuting attorney and what charges they file.
-
Comment on The lights have been on at a Massachusetts school for over a year because no one can turn them off in ~tech
spit-evil-olive-tips The lighting system was installed at Minnechaug Regional High School when it was built over a decade ago and was intended to save money and energy. But ever since the software that runs it failed on Aug. 24, 2021, the lights in the Springfield suburbs school have been on continuously, costing taxpayers a small fortune.
“The lighting system went into default,” said Osborne. “And the default position for the lighting system is for the lights to be on.”
Osborne said they immediately reached out to the original installer of the system only to discover that the company had changed hands several times since the high school was built. When they finally tracked down the current owner of the company, Reflex Lighting, several more weeks went by before the company was able to find somebody familiar with the high school’s lighting system, he said.
-
The lights have been on at a Massachusetts school for over a year because no one can turn them off
17 votes -
Comment on Donald Trump's company sentenced to pay $1.61 million penalty for tax fraud in ~finance
spit-evil-olive-tips if the tax fraud saved them more than $107k/year (which seems virtually certain when you consider their holdings include several buildings valued in the $100 million+ range) then this isn't an...a $1.61 million criminal penalty after it was convicted of scheming to defraud tax authorities for 15 years
if the tax fraud saved them more than $107k/year (which seems virtually certain when you consider their holdings include several buildings valued in the $100 million+ range) then this isn't an actual penalty, it's just a cost of doing business.
imagine robbing a bank, stealing $100k, and then getting caught and the only punishment is paying a $1000 fine. that's a minor inconvenience. as you're planning out your next bank robbery, maybe you budget $5k for the getaway driver and $1k for the "aw shucks, you caught me" fee.
Justice Juan Merchan of the Manhattan criminal court imposed the sentence, the maximum possible under state law
so that's cool. they didn't explicitly write "this doesn't apply to rich people" into state law, but it ends up having the same effect. if you're wealthy enough, and cheat on your taxes to a large enough extent, the law is powerless to punish you to a degree that actually matters or deters the behavior.
-
CES: We visit the tech industry's scary vision for the future
the It Could Happen Here podcast did a 3-part series on this year's Consumer Electronics Show in Vegas, and I thought it was some of the most nuanced and interesting coverage I've seen. 1: The...
the It Could Happen Here podcast did a 3-part series on this year's Consumer Electronics Show in Vegas, and I thought it was some of the most nuanced and interesting coverage I've seen.
1: The dead future of Big Tech - host Robert Evans got his start in journalism doing tech reporting more than a decade ago, including covering CES. he reflects on how the show, and the tech industry as a whole, has changed over that time.
2: The good parts of our future tech dystopia - Robert and co-host Garrison talk about the good / promising parts of what they saw at the show
3: We visit the tech industry's scary vision for the future - discussion of the creepy / less good stuff they saw at CES, including lots of surveillance cameras & robots
8 votes -
Comment on SourceHut will blocklist the Go module mirror in ~comp
spit-evil-olive-tips I like this part of his reasoning, and wish decision-makers (particularly ones who are less divisive / more diplomatic than Drew is) would take this approach more often. if there's a large-scale /...We often reject solutions which are offered to SourceHut and SourceHut alone.
I like this part of his reasoning, and wish decision-makers (particularly ones who are less divisive / more diplomatic than Drew is) would take this approach more often.
if there's a large-scale / ecosystem-wide problem, such as
The frequency of these requests can be as high as ~2,500 per hour, often batched with up to a dozen clones at once, and are generally highly redundant: a single git repository can be fetched over 100 times per hour.
then offering "here's how to opt out on a case-by-case basis" is a very incomplete solution. really much more of a temporary workaround than an actual solution.
if you set aside the usual problems with tone and combativeness that comes with Drew's posts, I think at the core of it, he has the better technical argument.
the "solution" he was offered, and declined, is that he or anyone else can file a GitHub issue with the Golang team (at least, I think that's the process...AFAIK this isn't explicitly spelled out anywhere, other than in one of the 38 comments on a 2-year-old GitHub issue) to request exclusion:
you know what would be cool? if there was some way that request for exclusion could be automated, rather than filing a Github issue and waiting on someone at Google (not exactly known for their dedication to customer support) to go through a manual process on their end.
oh right, there is an automated way of requesting that. and it's been in use for nearly 30 years.
and in Drew's original request, that's what he asked for:
If it's crawling, it should set an appropriate user-agent and respect robots.txt.
as he mentioned in this most recent blog post, there is an additional crawl-delay directive1 in robots.txt that would be perfectly suited for this.
following the 30-year-old web standard would also have a couple other concrete benefits. one is authentication - if I run
acme-git-hosting.com
someone might register "@acmegithosting" on GitHub and submit an exclusion request in my name, or ask that a previous exclusion be reverted. it's a fairly minor impersonation vulnerability, as these things go, but it would be completely solved by relying onhttps://acme-git-hosting.com/robots.txt
(rather than assuming / hoping that any such impersonation would be noticed by the person at Google who handles the exclusion request)using robots.txt would also let the Git hosting services update the crawl-delay on their own, without needing to coordinate with someone from the Golang team. if Acme Git Hosting allows once-an-hour scrapes from the Golang proxy, but then traffic increases and they want to dial back to every-X-hours, that's a one-line change that Acme can make, entirely on their own.
at the center of this onion, Google wrote a web crawler (albeit a specialized one, not their normal Googlebot), didn't implement support for
robots.txt
, and refused to implement it when asked. I can understand the frustration from someone such as Drew who subsequently got deluged by that crawler.1: as a side note, crawl-delay is a non-standard addition to
robots.txt
, but it seems to have been in use since at least 2012 (taken from the oldest of Wikipedia's references to it). meanwhile, in 2019 Google helped get robots.txt standardized as an official IETF RFC. they had ample opportunity to include something like crawl-delay and standardize on its meaning, but they didn't. Googlebot itself doesn't honor crawl-delay directives at all, and instead requires you to go through their webmaster console. -
On this flooded island of homeless people, climate change has never been more real
5 votes -
Comment on Inside Job creator confirms show has been cancelled by Netflix in ~tv
spit-evil-olive-tips evergreen, from season 2 of Barry: "the algorithm felt it wasn't hitting the right taste clusters" as they cancel a show 12 hours after its premiere, despite having a 98% on Rotten Tomatoes,...evergreen, from season 2 of Barry:
"the algorithm felt it wasn't hitting the right taste clusters"
as they cancel a show 12 hours after its premiere, despite having a 98% on Rotten Tomatoes, because "the algorithm" said so
-
Comment on Megathread for news/updates/discussion about Musk's takeover of Twitter – Part 1 in ~tech
spit-evil-olive-tips Streisand Effect powers, ACTIVATE! the data powering that Twitter account came from ADSB Exchange, which is a completely open and crowd-sourced database of aircraft positions. you can bookmark...Streisand Effect powers, ACTIVATE!
the data powering that Twitter account came from ADSB Exchange, which is a completely open and crowd-sourced database of aircraft positions.
you can bookmark this page to view the current position of his jet and its most recent flight: https://globe.adsbexchange.com/?icao=a835af
the data is inherently public, FAA and international regulations require civilian aircraft to broadcast their position, speed, altitude, etc. as they fly.
other flight-tracking sites (FlightRadar24 and FlightAware are the two most well-known) put some of that information behind a paywall. ADSB-X is very opinionatedly open and unpaywalled.
from their FAQ:
How is ADSBexchange different than “other” flight tracking sites?
ADSBexchange operates a bit differently from other flight tracking sites. As a group of aviation enthusiasts, our primary goal is to answer the question of “what’s up there” rather than “is grandma’s flight on-time”.
-
You’ll never see an aircraft censored or “blocked” from our site. If one of our feeders is receiving it, the data will be there. This includes military, and other aircraft that attempt to be “unlisted”. Hint: to see some of the planes not shown by other sites, from the map page, right-hand column, Filters -> LADD -> Filter. This primarily applies to US registered aircraft.
-
We don’t “estimate” or “interpolate” positions. Every time you see an aircraft move on ADSBexchange it is based on actual data received and not an estimate of where the aircraft “should” be. Look at the unnatural movement of aircraft on some of the other sites – you’ll see what we mean. If we are receiving the data, we’ll update positions as often as once per second… with real data.
-
We don’t put features behind a “paywall”, then hold them ransom to encourage people to feed. Features are the features. If you enjoy them, we’d appreciate you setting up a feeder or adding ADSBexchange to your existing feeder.
anyone with an always-on computer (even just a Raspberry Pi) can contribute data to their network. it requires $50-100 in parts (USB dongle, antenna, and cable to connect them). the radio frequency ADS-B uses is limited to line-of-sight, so more feeders in more locations is always useful, and you'll get better reception if you're able to mount your antenna high up with a clear view of the sky.
-
-
Comment on Day 9: Rope Bridge in ~comp.advent_of_code
spit-evil-olive-tips part 1 I started out with a big ugly complicated if statement for determining which direction to move the tail. that got me through part 1, then I ended up converting it to a lookup table which...part 1
I started out with a big ugly complicated if statement for determining which direction to move the tail. that got me through part 1, then I ended up converting it to a lookup table which worked much better, because I needed to expand it for the
(2, 2)
cases. I backported that to my part 1 solution because it was so much cleaner.# map of (diff_x, diff_y) (the difference between head and tail) # to (delta_x, delta_y) (the amount to move the tail) TAIL_MOVES = { (0, 0): (0, 0), # cardinal directions (0, 1): (0, 0), (0, -1): (0, 0), (1, 0): (0, 0), (-1, 0): (0, 0), (2, 0): (1, 0), (0, 2): (0, 1), (-2, 0): (-1, 0), (0, -2): (0, -1), # one step diagonal (1, 1): (0, 0), (-1, 1): (0, 0), (1, -1): (0, 0), (-1, -1): (0, 0), # knight's move (1, 2): (1, 1), (-1, 2): (-1, 1), (1, -2): (1, -1), (-1, -2): (-1, -1), (2, 1): (1, 1), (-2, 1): (-1, 1), (2, -1): (1, -1), (-2, -1): (-1, -1), } def move_tail(head, tail): head_x, head_y = head tail_x, tail_y = tail diff_x = head_x - tail_x diff_y = head_y - tail_y delta_x, delta_y = TAIL_MOVES[(diff_x, diff_y)] return tail_x + delta_x, tail_y + delta_y MOVES = { 'R': (1, 0), 'L': (-1, 0), 'U': (0, 1), 'D': (0, -1), } with open('09.txt') as input_file: lines = input_file.readlines() head = 0, 0 tail = 0, 0 tail_positions = set() tail_positions.add(tail) for line in lines: parts = line.strip().split(' ') direction, length = parts[0], int(parts[1]) delta_x, delta_y = MOVES[direction] for _ in range(length): head_x, head_y = head head = head_x + delta_x, head_y + delta_y tail = move_tail(head, tail) tail_positions.add(tail) print(len(tail_positions))
part 2
--- aoc09a.py 2022-12-08 22:20:16.292632216 -0800 +++ aoc09b.py 2022-12-08 22:15:52.521454415 -0800 @@ -20,6 +20,12 @@ (1, -1): (0, 0), (-1, -1): (0, 0), + # two steps diagonal + (2, 2): (1, 1), + (-2, 2): (-1, 1), + (2, -2): (1, -1), + (-2, -2): (-1, -1), + # knight's move (1, 2): (1, 1), (-1, 2): (-1, 1), @@ -45,6 +51,26 @@ return tail_x + delta_x, tail_y + delta_y +class Rope: + def __init__(self, length): + self.length = length + self.segments = [(0, 0)] * length + + def move(self, delta_x, delta_y): + head_x, head_y = self.segments[0] + self.segments[0] = head_x + delta_x, head_y + delta_y + + for i in range(1, self.length): + head = self.segments[i-1] + tail = self.segments[i] + new_tail = move_tail(head, tail) + self.segments[i] = new_tail + + @property + def tail(self): + return self.segments[-1] + + MOVES = { 'R': (1, 0), 'L': (-1, 0), @@ -55,11 +81,10 @@ with open('09.txt') as input_file: lines = input_file.readlines() -head = 0, 0 -tail = 0, 0 +rope = Rope(10) tail_positions = set() -tail_positions.add(tail) +tail_positions.add(rope.tail) for line in lines: parts = line.strip().split(' ') @@ -67,10 +92,7 @@ delta_x, delta_y = MOVES[direction] for _ in range(length): - head_x, head_y = head - head = head_x + delta_x, head_y + delta_y - - tail = move_tail(head, tail) - tail_positions.add(tail) + rope.move(delta_x, delta_y) + tail_positions.add(rope.tail) print(len(tail_positions))
-
Comment on Day 8: Treetop Tree House in ~comp.advent_of_code
spit-evil-olive-tips part 1 I use a dict of (x, y) coordinates as a multi-dimensional array. this is a trick I remember from last year - if I have a grid of something and I'm considering each cell's neighbors, Python...part 1
I use a dict of
(x, y)
coordinates as a multi-dimensional array. this is a trick I remember from last year - if I have a grid of something and I'm considering each cell's neighbors, Python makes it very easy to implement "wrap-around" behavior, becauseindex - 1
when index is 0 will give me the other end of the array. when I want "fall off the edge" behavior, such as here,-1
being a valid index is an annoyance. by using a dict with(row, col)
tuples as keys, I can get proper fall-off-the-edge behavior from the dict throwing aKeyError
when I try to access outside the grid.from there, I iterate each row and each column, backwards and forwards, and keep track of the maximum height in each of the 4 cardinal directions.
and then, the tree is visible from the outside if its height is larger than the view in any direction
from dataclasses import dataclass, field @dataclass class Tree: height: int views: dict = field(default_factory=dict) class Grid: def __init__(self, trees, height, width): self.trees = trees self.height = height self.width = width @staticmethod def parse(input_str): trees = dict() for row, line in enumerate(input_str.split('\n')): for col, char in enumerate(line): height = int(char) trees[(row, col)] = Tree(height=height) return Grid(trees, row + 1, col + 1) def calculate_views(self): # north delta_row, delta_col = (-1, 0) for row in range(self.height): for col in range(self.width): self.calculate_view(row, col, delta_row, delta_col) # south delta_row, delta_col = (1, 0) for row in reversed(range(self.height)): for col in range(self.width): self.calculate_view(row, col, delta_row, delta_col) # west delta_row, delta_col = (0, -1) for col in range(self.width): for row in range(self.height): self.calculate_view(row, col, delta_row, delta_col) # east delta_row, delta_col = (0, 1) for col in reversed(range(self.width)): for row in range(self.height): self.calculate_view(row, col, delta_row, delta_col) def calculate_view(self, row, col, delta_row, delta_col): tree = self.trees[(row, col)] try: neighbor = self.trees[(row + delta_row, col + delta_col)] view = max(neighbor.height, neighbor.views[(delta_row, delta_col)]) except KeyError: view = -1 tree.views[(delta_row, delta_col)] = view with open('08.txt') as input_file: contents = input_file.read() grid = Grid.parse(contents) grid.calculate_views() visible_trees = 0 for location, tree in grid.trees.items(): visible = any(tree.height > view for view in tree.views.values()) if visible: visible_trees += 1 print(visible_trees)
part 2
part 2 was frustrating. what I kept trying to do was keep track of the actual number of trees visible, as I go, but I couldn't make it work quite right.
what I settled on is that my
view
list contains the heights of all trees in that direction. then I feed that through thecount_visible_trees
function to extract only the trees that are visible in order to calculate the scenic score.eg, looking at the
9
in the bottom row of the example input, looking north its neighbors are[4, 3, 1, 7]
but the only visible trees are[4, 7]
.--- aoc08a.py 2022-12-08 14:38:16.232131655 -0800 +++ aoc08b.py 2022-12-08 14:39:17.393847716 -0800 @@ -53,12 +53,23 @@ tree = self.trees[(row, col)] try: neighbor = self.trees[(row + delta_row, col + delta_col)] - view = max(neighbor.height, neighbor.views[(delta_row, delta_col)]) + view = [neighbor.height] + neighbor.views[(delta_row, delta_col)] except KeyError: - view = -1 + view = [] tree.views[(delta_row, delta_col)] = view +def count_visible_trees(height, view): + count = 0 + current = height + for tree in view: + count += 1 + if tree >= current: + return count + + current = max(current, tree) + + return count with open('08.txt') as input_file: contents = input_file.read() @@ -66,10 +77,19 @@ grid = Grid.parse(contents) grid.calculate_views() -visible_trees = 0 +most_scenic_location = None +most_scenic_score = 0 + for location, tree in grid.trees.items(): - visible = any(tree.height > view for view in tree.views.values()) - if visible: - visible_trees += 1 + scenic_score = 1 + for view in tree.views.values(): + scenic_score *= count_visible_trees(tree.height, view) + + if scenic_score > most_scenic_score: + most_scenic_score = scenic_score + most_scenic_location = location + -print(visible_trees) +print(most_scenic_location) +print(most_scenic_score)
-
Comment on Day 7: No Space Left On Device in ~comp.advent_of_code
spit-evil-olive-tips part 1 really fun, first one that felt like a legit challenge. tricky part of parsing is that you have two "modes" of consuming lines, one looking for cd and ls commands and one for consuming ls...part 1
really fun, first one that felt like a legit challenge.
tricky part of parsing is that you have two "modes" of consuming lines, one looking for
cd
andls
commands and one for consumingls
output. I solved that by treating the lines as a queue rather than just iterating them in aforeach
loop, and having a 2nd nested loop to consumels
output from the line queue. important subtlety here is when you encounter the first non-matching line, to make sure to put it back into its place at the front of the queue (theappendleft
call).for computing the total sizes I implemented a "normal" recursive algorithm; for finding directories matching what the problem wanted I did the "recursive algorithm as an iterative loop" trick. if you're not very comfortable with recursion it can be useful to study both approaches, because every recursive algorithm can be implemented non-recursively, and often the iterative implementation is easier to think about / debug.
from collections import deque import re CD_RE = re.compile(r'\$ cd (.+)') LS_RE = re.compile(r'\$ ls') DIR_RE = re.compile(r'dir (.+)') FILE_RE = re.compile(r'(\d+) (.+)') class Directory: def __init__(self, name, parent): self.name = name self.parent = parent self.entries = {} self.total_size = 0 def prettyprint(self, pad=''): print(f'{pad}- {self.name} (dir, total_size={self.total_size})') for name, entry in sorted(self.entries.items()): child_padding = pad + ' ' if isinstance(entry, int): print(f'{child_padding}- {name} (file, size={entry})') else: entry.prettyprint(child_padding) @staticmethod def parse(lines): root_dir = Directory(name='/', parent=None) current_dir = root_dir lines = deque(lines) while lines: line = lines.popleft() cd_match = CD_RE.match(line) ls_match = LS_RE.match(line) if cd_match: target = cd_match.group(1) if target == '/': current_dir = root_dir elif target == '..': current_dir = current_dir.parent else: current_dir = current_dir.entries[target] elif ls_match: while lines: line = lines.popleft() dir_match = DIR_RE.match(line) file_match = FILE_RE.match(line) if dir_match: name = dir_match.group(1) new_dir = Directory(name=name, parent=current_dir) current_dir.entries[name] = new_dir elif file_match: size, name = int(file_match.group(1)), file_match.group(2) current_dir.entries[name] = size else: lines.appendleft(line) break return root_dir def assign_total_sizes(self): total = 0 for name, entry in self.entries.items(): if isinstance(entry, int): total += entry else: entry.assign_total_sizes() total += entry.total_size self.total_size = total with open('07.txt') as input_file: lines = input_file.readlines() root_directory = Directory.parse(lines) root_directory.assign_total_sizes() root_directory.prettyprint() total = 0 pending = deque() pending.append(root_directory) while pending: item = pending.popleft() if isinstance(item, Directory): pending.extend(item.entries.values()) if item.total_size < 100000: total += item.total_size print(total)
for shits and giggles I also implemented a recursive prettyprint function that matches the output from the problem statement, and adds in the total computed size of each directory:
- / (dir, total_size=48381165) - a (dir, total_size=94853) - e (dir, total_size=584) - i (file, size=584) - f (file, size=29116) - g (file, size=2557) - h.lst (file, size=62596) - b.txt (file, size=14848514) - c.dat (file, size=8504156) - d (dir, total_size=24933642) - d.ext (file, size=5626152) - d.log (file, size=8033020) - j (file, size=4060174) - k (file, size=7214296)
part 2
--- aoc07a.py 2022-12-06 22:05:34.232327237 -0800 +++ aoc07b.py 2022-12-06 22:05:17.718218543 -0800 @@ -81,7 +81,13 @@ root_directory.assign_total_sizes() root_directory.prettyprint() -total = 0 +disk_size = 70_000_000 +required_free_space = 30_000_000 + +target_disk_size = disk_size - required_free_space +to_be_deleted = root_directory.total_size - target_disk_size + +best_directory = disk_size pending = deque() pending.append(root_directory) @@ -91,7 +97,7 @@ if isinstance(item, Directory): pending.extend(item.entries.values()) - if item.total_size < 100000: - total += item.total_size + if to_be_deleted < item.total_size < best_directory: + best_directory = item.total_size -print(total) +print(best_directory)
-
Comment on Day 6: Tuning Trouble in ~comp.advent_of_code
spit-evil-olive-tips part 1 from collections import deque with open('06.txt') as input_file: buffer = input_file.read().strip() def find_marker(buffer): window = deque(maxlen=4) for i, c in enumerate(buffer):...part 1
from collections import deque with open('06.txt') as input_file: buffer = input_file.read().strip() def find_marker(buffer): window = deque(maxlen=4) for i, c in enumerate(buffer): window.append(c) if len(window) == 4 and \ window[0] not in (window[1], window[2], window[3]) and \ window[1] not in (window[2], window[3]) and \ window[2] != window[3]: return i + 1, ''.join(window) index, marker = find_marker(buffer) print(marker) print(index)
part 2
generalized it so that if I change the
size
parameter back to 4, it solves part 1 againrather less efficient than it could be, because it constructs a
set
over and over again just to check for uniqueness. that could be improved by keeping track of characters as they both enter and leave the window, using that to maintain a dictionary of character counts, and checking those counts to see if we found the marker.but, execution time on my desktop is ~20ms for both the example input and my real input, which is both fast enough and suggests that the long pole is the Python startup time and not my algorithm, so meh.
--- aoc06a.py 2022-12-05 23:55:26.426887947 -0800 +++ aoc06b.py 2022-12-05 23:53:52.512286534 -0800 @@ -3,18 +3,16 @@ with open('06.txt') as input_file: buffer = input_file.read().strip() -def find_marker(buffer): - window = deque(maxlen=4) +def find_marker(buffer, size): + window = deque(maxlen=size) for i, c in enumerate(buffer): window.append(c) + + charset = set(window) + if len(charset) == size: + return i + 1, ''.join(window) - if len(window) == 4 and \ - window[0] not in (window[1], window[2], window[3]) and \ - window[1] not in (window[2], window[3]) and \ - window[2] != window[3]: - return i + 1, ''.join(window) - -index, marker = find_marker(buffer) +index, marker = find_marker(buffer, 14)
-
Comment on Day 5: Supply Stacks in ~comp.advent_of_code
spit-evil-olive-tips part 1 from collections import defaultdict, deque import re INSTRUCTION_RE = re.compile(r'move (\d+) from (\d+) to (\d+)') def parse_layout(layout): lines = layout.split('\n') lines.reverse()...part 1
from collections import defaultdict, deque import re INSTRUCTION_RE = re.compile(r'move (\d+) from (\d+) to (\d+)') def parse_layout(layout): lines = layout.split('\n') lines.reverse() stacks = defaultdict(deque) # " 1 2 3 4 5 6 7 8 9 " for i in range(1, len(lines[0]), 4): stack_idx = int(lines[0][i]) for line in lines[1:]: item = line[i] if item != ' ': stacks[stack_idx].append(item) return stacks with open('05.txt') as input_file: contents = input_file.read() starting_layout, instructions = contents.rstrip().split('\n\n') stacks = parse_layout(starting_layout) for line in instructions.split('\n'): match = INSTRUCTION_RE.match(line) count, source, target = [int(group) for group in match.groups()] for _ in range(count): item = stacks[source].pop() stacks[target].append(item) for i in range(len(stacks)): print(stacks[i + 1].pop(), end='')
part 2
--- aoc05a.py 2022-12-04 21:24:51.376412925 -0800 +++ aoc05b.py 2022-12-04 21:28:27.267831102 -0800 @@ -31,8 +31,13 @@ match = INSTRUCTION_RE.match(line) count, source, target = [int(group) for group in match.groups()] + temp = deque() for _ in range(count): item = stacks[source].pop() + temp.append(item) + + for _ in range(count): + item = temp.pop() stacks[target].append(item) for i in range(len(stacks)):
-
Comment on Day 4: Camp Cleanup in ~comp.advent_of_code
spit-evil-olive-tips part 1 import re LINE_RE = re.compile(r'(\d+)-(\d+),(\d+)-(\d+)') with open('04.txt') as input_file: lines = input_file.readlines() count = 0 for line in lines: match = LINE_RE.match(line) start1,...part 1
import re LINE_RE = re.compile(r'(\d+)-(\d+),(\d+)-(\d+)') with open('04.txt') as input_file: lines = input_file.readlines() count = 0 for line in lines: match = LINE_RE.match(line) start1, end1, start2, end2 = [int(group) for group in match.groups()] # start1 start2 end2 end1 # start2 start1 end1 end2 if (start1 <= start2 and end2 <= end1) or \ (start2 <= start1 and end1 <= end2): count += 1 print(count)
part 2
--- aoc04a.py 2022-12-03 21:21:32.559129397 -0800 +++ aoc04b.py 2022-12-03 21:21:26.839086405 -0800 @@ -11,10 +11,10 @@ match = LINE_RE.match(line) start1, end1, start2, end2 = [int(group) for group in match.groups()] - # start1 start2 end2 end1 - # start2 start1 end1 end2 - if (start1 <= start2 and end2 <= end1) or \ - (start2 <= start1 and end1 <= end2): + # start1 start2 end1 + # start2 start1 end2 + if (start1 <= start2 <= end1) or \ + (start2 <= start1 <= end2): count += 1 print(count)
-
Comment on Day 3: Rucksack Reorganization in ~comp.advent_of_code
spit-evil-olive-tips part 1 import string def get_priority(letter): return string.ascii_letters.index(letter) + 1 with open('03.txt') as input_file: lines = [line.strip() for line in input_file.readlines()] total = 0...part 1
import string def get_priority(letter): return string.ascii_letters.index(letter) + 1 with open('03.txt') as input_file: lines = [line.strip() for line in input_file.readlines()] total = 0 for line in lines: split = len(line) // 2 items1, items2 = line[:split], line[split:] ruck1, ruck2 = set(items1), set(items2) overlap = ruck1.intersection(ruck2) total += get_priority(overlap.pop()) print(total)
part 2
--- aoc03a.py 2022-12-03 18:55:45.593771254 -0800 +++ aoc03b.py 2022-12-03 18:54:52.443395112 -0800 @@ -8,11 +8,10 @@ total = 0 -for line in lines: - split = len(line) // 2 - items1, items2 = line[:split], line[split:] - ruck1, ruck2 = set(items1), set(items2) - overlap = ruck1.intersection(ruck2) +for i in range(0, len(lines), 3): + items1, items2, items3 = lines[i], lines[i+1], lines[i+2] + ruck1, ruck2, ruck3 = set(items1), set(items2), set(items3) + overlap = set.intersection(ruck1, ruck2, ruck3) total += get_priority(overlap.pop()) print(total)
I have Anker Q30s and have been happy with them. they tick all your boxes except for being over-ear rather than earbuds.
they're wireless, USB-C chargeable, and the active noise cancelling on them works very well.