Day 5: Cafeteria

Megathread guidelines

  • Keep top level comments as only solutions, if you want to say something other than a solution put it in a new post. (replies to comments can be whatever)
  • You can send code in code blocks by using three backticks, the code, and then three backticks or use something such as https://topaz.github.io/paste/ if you prefer sending it through a URL

FAQ

  • VegOwOtenks
    link
    fedilink
    English
    arrow-up
    2
    ·
    7 months ago

    Futhark

    As always with futhark, first the script that converts the input into futhark-format:

    sed script and example output
    1i [
    s/\([0-9]\+\)-\([0-9]\+\)/\[\1, \2],/g
    s/^\([0-9]\+\)$/\1,/
    s/^$/] [/
    $i ]
    $d
    

    this converts the example to this:

    [
    [3, 5],
    [10, 14],
    [16, 20],
    [12, 18],
    ] [
    1,
    5,
    8,
    11,
    17,
    32,
    ]
    
    

    I ended up writing a RangeSet implementation, currently considering publishing this as a library later. The RangeSet-Implementation makes it very large, but futhark makes it run blazingly fast 🚀 . /s

    Some fiddling with existential size types made me learn the type system even better.

    I discovered that futhark has a multicore-backend, which makes it run faster. :]

    Solution with RangeSet implementation
    def fst 'a 'b ((a, _b): (a, b)): a = a
    def snd 'a 'b ((_a, b): (a, b)): b = b
    def count 'a (p: a -> bool) (xs: []a): i64 = xs |> map (p >-> i64.bool) |> reduce (+) 0
    def array2Pair 'a (pair: [2]a): (a, a) = (pair[0], pair[1])
    def pair2Array 'a (p: (a, a)): [2]a = [fst p, snd p]
    
    def rangeContains (range: [2]i64) (x: i64): bool = x >= range[0] && x <= range[1]
    
    def isInRanges (ranges: [][2]i64) (x: i64): bool = any (`rangeContains` x) ranges
    
    def part1 (ranges: [][2]i64) (ids: []i64): i64 = count (isInRanges ranges) ids
    
    def insertAt [n] 'a (xs: [n]a) (insertionIndex: i64) (x: a): [n+1]a
      = let lookup = \ i -> 
          if i == insertionIndex
          then x
          else 
            if i < insertionIndex
            then xs[i]
            else xs[i-1] in
        tabulate (n + 1) lookup
    
    def merge [n] 'a (xs: [n]a) (f: a -> a -> a) (ia: i64) (ib: i64): [n-1]a
      = let imin = i64.min ia ib in
        let imax = i64.max ia ib in
        tabulate (n - 1) \ i ->
          if i < imin
          then xs[i]
          else 
            if i == imin
            then f xs[imin] xs[imax]
            else if i < imax
              then xs[i]
              else xs[i+1]
    
    
    -- invariant: fst <= snd
    -- both ends are inclusive
    type Range = (i64, i64) 
    
    type~ RangeSet = ?[n]. #RangeSet [n]Range
    
    def Range_Start = fst
    def Range_End = snd
    def Range_Overlaps (a: Range) (b: Range): bool 
      = Range_Start a <= Range_End b && Range_Start b <= Range_End a
    
    -- Only works with overlapping ranges
    
    def Range_Merge ((a_start, a_end): Range) ((b_start, b_end): Range): Range
      = (i64.min a_start b_start, i64.max a_end b_end)
    
    def Range_Size ((start, end): Range): i64 = end - start + 1
    
    def RangeSet_Empty: RangeSet = #RangeSet []
    def RangeSet_Singleton (r: Range): RangeSet = #RangeSet [r]
    def RangeSet_Unpack ((#RangeSet rs): RangeSet): ?[n].[n]Range = rs
    
    def RangeSet_InsertionIndex (r: Range) ((#RangeSet rs): RangeSet): i64
      = fst
        ( loop (low, high) = (0, length rs)
          while low != high
          do 
            let middle = low + ((high - low) / 2) in
            let middleRange = rs[middle] in
            if Range_Start middleRange < Range_Start r
              then (middle + 1, high)
              else (low, middle)
        )
    
    
    def RangeSet_IndicesMergeable (a: i64) (b: i64) ((#RangeSet rs): RangeSet): bool
      = let ra = rs[a] in
        let rb = rs[b] in
        ra `Range_Overlaps` rb
    
    def RangeSet_NormalizeAtIndex (i: i64) ((#RangeSet rs): RangeSet): RangeSet
      = ( loop (current, (position, continue)) = (rs, (i, true))
          while continue
          do
            if position != 0 && RangeSet_IndicesMergeable (position-1) position (#RangeSet current)
            then (merge current Range_Merge (position-1) position, (position - 1, true))
            else 
              if position + 1 != length current && RangeSet_IndicesMergeable (position+1) position (#RangeSet current)
              then (merge current Range_Merge (position+1) position, (position, true))
              else (current, (position, false))
        )
        |> fst
        |> \ rs -> #RangeSet rs
    
    def RangeSet_Add (r: Range) ((#RangeSet rs): RangeSet): RangeSet
      = let insertionIndex = RangeSet_InsertionIndex r (#RangeSet rs) in
        let rs' = (rs `insertAt` insertionIndex) r in
      RangeSet_NormalizeAtIndex insertionIndex (#RangeSet rs')
    
    def buildRangeSet [n] (rangeArrays: [n][2]i64): RangeSet
      = let ranges = map array2Pair rangeArrays in
        loop set = copy RangeSet_Empty
        for range in ranges
        do RangeSet_Add range set
    
    def part2 (rangeSet: RangeSet): i64
      = rangeSet
      |> RangeSet_Unpack
      |> map Range_Size
      |> reduce (+) 0
    
    def main (ranges: [][2]i64) (ids: []i64) 
      = let rangeSet = buildRangeSet ranges in
      (part1 ranges ids, part2 rangeSet)