Adventures in Advent of Code
davedelong.com
davedelong.com
Except
> "It turns out, there was a bug in Set.intersection(_:), but it had only been discovered this past June, and the fix hasn’t made it into a public version of Swift yet. "
It turns out the bug fix has been released publicly, but only on newer OS versions.
I ran into the same bug, but since I'd included a `precondition` to verify that each elf-group had only one common item type, I discovered the bug immediately. I then needed about 5-10 minutes to convince myself that it really was a Swift bug and not a mayoff bug.
One of the guys in my team handled a frantic support request from a Formula 1 team because they required reproducible builds or else they got some fine from the FIA, and they somehow hit a situation where our compiler was outputting different code depending on time of day. Wild.
The compiler itself felt well-written and there were some very smart people involved in developing it. And now that I think about it the bug wasn't different code being output but some value elsewhere in the generated binary itself - maybe some ELF header? I wish I could remember
I thought you meant TI. Their compiler would compile in the date of compilation as a string by default (it wasn't deadcode eliminated in the linker because it was sucked in their panic libraries, if memory serves).
In any case, yeah, ADI.
I used to have a methodical way of investigating most bugs: (1) here's what I know, (2) here's what I don't, (3) here's the thing I would like to know to most restrict the remaining search space. It was fine enough for awhile, but when you don't know things like "I wrote a 0 here, therefore a 0 exists here", or "my code explicitly uses aligned 32-bit reads, so I issued an aligned 32-bit read here", the errors in your assumptions quickly get amplified into nonsense. Nowadays I explicitly includle miscompilation (interpretation), bit-flips, and other such garbage as I write down what I do or don't know about the system, and I throw in ballpark probabilities to figure out where to look next. Most debugging isn't noticably slower that way, but the hairy problems are easily 10x easier when you haven't conditioned yourself to ignore the real solution.
I also like it since I don't do any work or even side projects in C these days, and so it's nice to keep my C chops up to scratch.
What kinds of optimisations have you had to do? Are you hosting your code anywhere?
I don't necessarily want to share my github as it contains my real name, but I'd be willing to send you a tarball of my 2020 code tomorrow if you send an email to mtlmtlmtlmtl at pm.me
I've just now started on day 1 for this year, so no code there yet worth showing off.
If it's just a My First Hash table with closed addressing, buckets etc. you needn't care too much. If you're hashing integers, just use the integer itself, that's a terrible "hash" but with this like 1980s data structure it's good enough.
Worth noting that for AOC I got by just fine with the simplest possible bucket based implementation.
You can use the same hash function for any arbitrary struct as long as there's no pointers in it. If there are pointers, you'd have to implement one combining the hashes of all the members through something like xor. I used fnv1a_64 for my hash function in AOC 2020 which is a pretty good general choice for strings and other contiguous objects and is just 10 lines of code. Pseudocode on Wikipedia or I'm sure you can find C code on Stackoverflow. Hope that helps :)
https://github.com/forrestthewoods/aoc2021/blob/master/rust/...
My 2021 (Rust) solutions all meet that, but some only do so when optimized, and day 22 was one of those where without the optimizer I'm waiting 2-3 seconds for an answer.
IIRC the trick was to compact the indices, so e.g instead of thinking about X values of 120203 494302 and 109383 you call those 1, 2, 0. Do all the working out of which cells within the resulting tiny structure are on or off, and then expand the 1x1 cells you're talking about to their true size based on the real values instead of indices.
The trick I used was to represent signed volumes, compute the intersection and then store the 'negative cube' instead of splitting up cubes.
$ swift -v
Apple Swift version 5.7.1 (swiftlang-5.7.1.135.3 clang-1400.0.29.51)
Target: arm64-apple-macosx12.0
I love Swift as a language, but the lack of release notes for 5.7.1 (aside from a blog post for 5.7 as a whole), OS-specific releases (especially for SwiftUI), Xcode removing old toolchains when it upgrades (making me re-download tvOS 15.4 runtime for no reason), and all these other little things are starting to weigh on me.Requires thinking if you want to implement set difference or intersection. I just figured depending on needs using existing built-in types may be fun.
For the problem we don't care how many instances of each letter occurs. We just need to know if there is 0 or 1 instance of a given letter. A set is a pretty good way to do this. Store a set of each "compartment", intersect the two sets, and you should have one value.
We can do the same thing except instead of a set we use an integer and set bits. a-z are bits 0-26 and A_Z are bits 27-52. We can set a bit with bitwise-or. We can intersect two masks with bitwise-and.
My code is probably a little too verbose. I'm not super familiar with python so I wasn't sure the best way to deal with characters and integers. But it's functional.
for key, _ := range first {
if _, found2 := second[key]; found2 {
if _, found3 := third[key]; found3 {
// This is the intersection of 3 sets
}
}
}Most years I choose a language I've never worked with commercially (e.g. Fennel in 2022) and rather than reach for prebuilt libs for timesaving fancy array or set or string ops, I'll invent those wheels myself without dependencies and I find a ton of satisfaction in it. Unwise commercially but ideal for learning and play. And, yeah, bugs!
IMO, when it's not a customer paying for my learning curve, there's a lot to be said for rewalking old paths with new boots (or no boots and funky coloured glasses). Also, a terrible way to get onto the leaderboard IME!
In addition, unlike in C, in many other languages indexing into array will involve bounds check if the compiler cannot prove that the access is always in bounds. Obviously most branches will be predicted but branch retire throughput is always lower than math operations throughput.
I guess the bug is apparently in the Swift runtime itself, and since Swift 5.0 and ABT stability, the runtime version is tied to the OS version.
https://www.swift.org/blog/abi-stability-and-apple/
But I think that only applies to deployments via the App Store. I think for local development you can run against the current runtime by setting the TOOLCHAINS environment variable (you may also need to set SDKROOT).
https://www.swift.org/getting-started/#on-macos
ETA. Apple does not make it easy to replace the runtime, but I got it to work like this:
$ sw_vers
ProductName: macOS
ProductVersion: 12.6
BuildVersion: 21G115
$ cat foo.swift
let a = "gnmCjzwnmCPTPhBwPjzBgqPjllJJSWlhfhQDSrpJRhDSlfJl"
let b = "rLHNHrLHVNbVHMMctZFHsbcsDSDWpSDSGfSRsRWSRllfGSSG"
let c = "NNtdMVrLNdZNvLvLZrzCndqBgwwPmwgjggBn"
var s1 = Set(a)
s1.formIntersection(b)
s1.formIntersection(c)
print(s1)
var s2 = Set(a)
s2.formIntersection(Set(b))
s2.formIntersection(Set(c))
print(s2)
$ swiftc -version
swift-driver version: 1.62.15 Apple Swift version 5.7.1 (swiftlang-5.7.1.135.3 clang-1400.0.29.51)
Target: arm64-apple-macosx12.0
$ swiftc foo.swift
$ ./foo
["P", "r", "C", "z", "w", "m", "B", "q", "j", "g", "n"]
["r"]
$ otool -L foo
foo:
/usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1319.0.0)
/usr/lib/swift/libswiftCore.dylib (compatibility version 1.0.0, current version
$ DYLD_LIBRARY_PATH=/Library/Developer/Toolchains/swift-5.7.1-RELEASE.xctoolchain/usr/lib/swift/macosx ./foo
["r"]
["r"]
Note that I had to download and install Swift 5.7.1 from swift.org. Trying to use libswiftCore.dylib from Xcode-14.1 is no bueno: $ /Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild -version
Xcode 14.1
Build version 14B47b
$ DYLD_LIBRARY_PATH=/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx ./foo
["B", "P", "q", "C", "r", "n", "j", "w", "m", "z", "g"]
["r"]
Oh, it's because /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.0/macosx/libswiftCore.dylib is x86_64 only for some reason and I'm testing on an MBA M1.The dylibs under /Library/Developer/Toolchains/swift-5.7.1-RELEASE.xctoolchain/usr/lib/swift/macosx are arm64 and x86_64.
WTF Apple?
$ otool -L AdventOfCode2022
AdventOfCode2022:
/usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1319.0.0)
/usr/lib/swift/libswiftCore.dylib (compatibility version 1.0.0, current version 5.7.1)
/usr/lib/swift/libswiftCoreFoundation.dylib (compatibility version 1.0.0, current version 120.100.0, weak)
/usr/lib/swift/libswiftDarwin.dylib (compatibility version 1.0.0, current version 0.0.0, weak)
/usr/lib/swift/libswiftDispatch.dylib (compatibility version 1.0.0, current version 17.0.0, weak)
/usr/lib/swift/libswiftIOKit.dylib (compatibility version 1.0.0, current version 1.0.0, weak)
/usr/lib/swift/libswiftObjectiveC.dylib (compatibility version 1.0.0, current version 6.0.0, weak)
/usr/lib/swift/libswiftXPC.dylib (compatibility version 1.0.0, current version 6.0.0, weak)
/usr/lib/swift/libswiftFoundation.dylib (compatibility version 1.0.0, current version 1.0.0, weak)
Not only does it say that libswiftcore is 5.7.1, but that /usr/lib/swift folder doesn't have any of those files there. Very very confusing. I don't like any of this.I appreciate your write-up and research into all of this though! I tried googling for what runtime version of Swift is installed in Monterey and couldn't find anything. This is incredibly opaque.
I wasn't able to get `swift repl` to use anything but the system runtime, maybe because it's signed. Maybe it can be made to work by stripping the out the signing but to be quite honest, I'm done with this at this point.
I've been happily using Python to solve AoC. Y'all gluttons for punishment doing this in Swift:
def part2():
total = 0
group = []
for line in INPUT.splitlines():
group.append(line.strip())
if len(group) < 3:
continue
common = list(set(group[0]) & set(group[1]) & set(group[2]))
group = []
assert len(common) == 1
c = common[0]
p = ascii_letters.index(c) + 1
# print(c, p)
total += p
print(total)Wait, that would actually be a good thing...
The highlights of my holiday season are stepping away from the keyboard and spending time with friends and family. The highlights of my holiday season do not include doing leetcode exercises, much as a I realize the need for leetcode exercises.
So, no. I will not be participating in your Advent of Code.