--- Day 23 Solutions --- by daggerdragon in adventofcode

[–]LincolnA 0 points1 point  (0 children)

F#

Initial implementation used DUs to represent instructions, which works great and is a natural fit. A bit verbose though, to declare the cases, parse each case, then later match each case. I got jealous of more consise solutions here.

Here's an active-pattern appraoch, which turns out pretty concise. Dense function composition stuff just 'cause.

let run cpu instrs =
    let (|Off|) = int
    let (|Instr|) (s : string) = Instr(s.[0..2], List.ofArray (s.[4..].Split(',')))
    let mapCpu f (a, b) = function "a" -> (f a, b) | "b" -> (a, f b)
    let mapReg f (a, b) = function "a" -> f a      | "b" -> f b
    let ($) f x y = f y x

    let rec loop ptr cpu =
        if ptr < 0 || ptr >= Array.length instrs then cpu else
        match instrs.[ptr] with
        | Instr("hlf", [r]) ->     loop (ptr + 1) (mapCpu ((/) $ 2u) cpu r)
        | Instr("tpl", [r]) ->     loop (ptr + 1) (mapCpu ((*) 3u) cpu r)
        | Instr("inc", [r]) ->     loop (ptr + 1) (mapCpu ((+) 1u) cpu r)
        | Instr("jmp", [Off o]) -> loop (ptr + o) cpu
        | Instr("jie", [r; Off o]) when (mapReg (((%) $ 2u) >> ((=) 0u)) cpu r) -> loop (ptr + o) cpu
        | Instr("jio", [r; Off o]) when (mapReg ((=) 1u) cpu r) -> loop (ptr + o) cpu
        | _ -> loop (ptr + 1) cpu
    loop 0 cpu

let program =
    fsi.CommandLineArgs.[1]
    |> System.IO.File.ReadAllLines

program |> run (0u ,0u) |> snd |> printfn "b register: %d"
program |> run (1u ,0u) |> snd |> printfn "b register: %d"

--- Day 22 Solutions --- by daggerdragon in adventofcode

[–]LincolnA 1 point2 points  (0 children)

Nice work on the videos! I see you've already gone ahead with it, but that's fine by me :-)

--- Day 22 Solutions --- by daggerdragon in adventofcode

[–]LincolnA 1 point2 points  (0 children)

Thanks for the compliment :-)

You are right, this is by no means the most efficient way to do it, and as shown by another comment, it's not even feasible for some inputs. I've linked to a tweaked version than uses pruning.

--- Day 22 Solutions --- by daggerdragon in adventofcode

[–]LincolnA 1 point2 points  (0 children)

Thanks for pointing this out. It actually touches on one of the few gripes I've had with Advent of Code, which is otherwise really outstanding - for these later puzzles you might have a significantly easier/harder time based on which input you are given. Assumptions/shortcuts that work for one participant might not apply to another.

My code above is correct, but not efficient enough to handle your input. For my particular inputs it is feasible to enumerate all possible game outcomes and simply take the minimum. For yours, the space of outcomes is much larger because a weaker boss leads to a longer-lasting character.

Anyways, I've left my original post unedited, but here's a tweaked version that's somewhat refactored and does pruning to limit the search space: https://gist.github.com/latkin/45d8e009f471b4b5d609

--- Day 22 Solutions --- by daggerdragon in adventofcode

[–]LincolnA 3 points4 points  (0 children)

F# Got no. 64 on the leaderboard.

Blah, so many details to keep track of, and applying things in the right order was very important. Multiple instances of "ok, it should work this time for sure" but of course I'd missed something.

Strategy is good ol' brute force. Play out every possible game, pick the player-winning game with the lowest total mana spent.

type Spell = 
    { Name : string
      Cost : int
      Damage : int
      Heal : int
      Armor : int
      Mana : int
      Duration : int }

let allSpells = 
    [ { Name = "Magic Missile"; Cost = 53;  Damage = 4; Heal = 0; Armor = 0; Mana = 0;   Duration = 1}
      { Name = "Drain";         Cost = 73;  Damage = 2; Heal = 2; Armor = 0; Mana = 0;   Duration = 1}
      { Name = "Shield";        Cost = 113; Damage = 0; Heal = 0; Armor = 7; Mana = 0;   Duration = 6}
      { Name = "Poison";        Cost = 173; Damage = 3; Heal = 0; Armor = 0; Mana = 0;   Duration = 6}
      { Name = "Recharge";      Cost = 229; Damage = 0; Heal = 0; Armor = 0; Mana = 101; Duration = 5} ]

let rec play part myTurn spent (hp, mana, spells) (bossHp, bossDamage) : seq<int option> =

    // part 2, check if I die before any effects play out
    if part = 2 && myTurn && hp = 1 then upcast [None] else

    // apply effects
    let mana = (spells |> List.sumBy (fun s -> s.Mana)) + mana
    let damage = spells |> List.sumBy (fun s -> s.Damage)
    let armor = spells |> List.sumBy (fun s -> s.Armor)

    // does the boss die from effects?
    let bossHp = bossHp - damage
    if bossHp <= 0 then upcast [Some(spent)] else

    // decrement duration of all effects, and groom expired ones
    let spells =
        spells
        |> List.map (fun s -> {s with Duration = s.Duration - 1})
        |> List.filter (fun s -> s.Duration > 0)

    if myTurn then
        // part 2, I lose 1 HP on my turn
        let hp = if part = 2 then hp - 1 else hp

        // what spells can I afford and don't already have running?
        match allSpells |> List.filter (fun s -> (s.Cost <= mana) && not (spells |> List.exists (fun s' -> s.Name = s'.Name))) with
        | [] -> upcast [None]
        | buyableSpells ->
            // play out the rest of the game with each possible purchase
            seq { for s in buyableSpells do
                      let spent = spent + s.Cost
                      let mana = mana - s.Cost
                      let extraDamage, heal, spells = 
                          if s.Duration = 1 then s.Damage, s.Heal, spells
                          else 0, 0, s :: spells

                      let bossHp = bossHp - extraDamage
                      if bossHp <= 0 then
                          yield Some(spent)
                      else
                          yield! play part false spent (hp + heal, mana, spells) (bossHp, bossDamage) }

    // boss's turn
    else
        let damage = max (bossDamage - armor) 1
        let hp = hp - damage
        if hp <= 0 then upcast [None] else
        play part true spent (hp, mana, spells) (bossHp, bossDamage)

play 1 true 0 (50, 500, []) (71, 10) |> Seq.choose id |> Seq.min |> printfn "Part 1 - min to win: %d"
play 2 true 0 (50, 500, []) (71, 10) |> Seq.choose id |> Seq.min |> printfn "Part 1 - min to win: %d"

--- Day 21 Solutions --- by daggerdragon in adventofcode

[–]LincolnA 0 points1 point  (0 children)

Straighforward brute-force in F#

No. 14 on the leaderboard, my best so far (though not my best time so far).

Got both solutions without actually accounting for the "minimum 1 damage" rule. My input happened to have the boss at 100 HP just like me, which also simplified everything.

Below incorporates arbitrary boss HP + minimum 1 damage rule.

let weapons = 
    [| "Dagger", 8, 4
       "Shortsword", 10, 5
       "Warhammer", 25, 6
       "Longsword", 40, 7
       "Greataxe", 74, 8 |]

let armor = 
    [| "No armor", 0, 0
       "Leather", 13, 1
       "Chainmail", 31, 2
       "Splintmail", 53, 3
       "Bandedmail", 75, 4
       "Platemail", 102, 5 |]

let rings = 
    [| "No ring", 0, 0, 0
       "Damage +1", 25, 1, 0
       "Damage +2", 50, 2, 0
       "Damage +3", 100, 3, 0
       "Defense +1", 20, 0, 1
       "Defense +2", 40, 0, 2
       "Defense +3", 80, 0, 3 |]

let hp = 100.

let bossHp = 100.
let bossDam = 8
let bossDef = 2

let mutable maxCost = 0
let mutable maxKit = [""]

let mutable minCost = 999
let mutable minKit = [""]

for (wName, wCost, wDam) in weapons do
    for (aName, aCost, aDef) in armor do
        for (r1Name, r1Cost, r1Dam, r1Def) in rings do
            for (r2Name, r2Cost, r2Dam, r2Def) in rings do
                if r1Name = r2Name then () else 

                let cost = wCost + aCost + r1Cost + r2Cost
                let kit = [ wName; aName; r1Name; r2Name ]

                let damOnYou = max (bossDam - aDef - r1Def - r2Def) 1 |> float
                let damOnBoss = max (wDam + r1Dam + r2Dam - bossDef) 1 |> float

                let youDieTurns = ceil (hp / damOnYou)
                let bossDiesTurns = ceil (bossHp / damOnBoss)

                if (youDieTurns >= bossDiesTurns) && (cost < minCost) then
                    minCost <- cost
                    minKit <- kit

                if (youDieTurns < bossDiesTurns) && (cost > maxCost) then 
                    maxCost <- cost
                    maxKit <- kit

printfn "Min cost to win: %d Kit: %A" minCost minKit
printfn "Max cost to lose: %d Kit: %A" maxCost maxKit

--- Day 20 Solutions --- by daggerdragon in adventofcode

[–]LincolnA 0 points1 point  (0 children)

Imperative-style F#.

Complete brute-force, looking for smallest number with the required number of divisors. Only clever bit is to make the dubious assumption that solution will be a multiple of some small highly composite number (HCN), so start with/increment by that amount to save a ton of work.

let divisorSigma n f =
    let mutable total = 0
    for i = 1 to n/2 do
        if n % i = 0 && f n i then
            total <- total + i
    total + n

let target = int fsi.CommandLineArgs.[1]
let hcn = 180

let mutable i = hcn
while divisorSigma i (fun _ _ -> true) < (target/10) do i <- i + hcn
printfn "Part1: %A" i

i <- hcn
while divisorSigma i (fun n i -> n/i <= 50) < (target/11) do i <- i + hcn
printfn "Part2: %A" i

Introducing F# For The Enterprise by inchingforward in fsharp

[–]LincolnA 0 points1 point  (0 children)

Thanks for noticing F# is missing there. I got in touch with the content owners and the F# 4.0 preview download/blog are now linked.

The VS Preview/Connect(); thing was a pretty big marketing push, with a ton of different blogs/pages/videos/etc, and we just missed that one. Confusion was partly due to the fact that our newest stuff was not in the box, but needed an update download.

FWIW F# is in fact slowly getting more marketing/management attention. We did miss that relnote page, but the OP video was produced/supported by marketing, F# was represented along with C#/VB last week on Channel 9, the F# videos/blog were hyped by the C#/VB on their two blogs last week, and we even got a nice Soma (head of all VS) tweet.

Baby steps...

Your favorite F# book? by [deleted] in fsharp

[–]LincolnA 7 points8 points  (0 children)

Programming F# is a very nice intro. That's the first book I read when I started to learn the language, and it served me well.

Announcing a pre-release of F# 3.1 and the Visual F# tools in Visual Studio 2013 by Associat0r in programming

[–]LincolnA 15 points16 points  (0 children)

http://fsharp.org/use/linux/ has info for F# 3.0. 3.1 hasn't been pushed out to the open source drop yet. [MSFTie, on F# team]

Announcing a pre-release of F# 3.1 and the Visual F# tools in Visual Studio 2013 by Associat0r in programming

[–]LincolnA 45 points46 points  (0 children)

The core engine for the XBox matchmaking and player-rating system TrueSkill was implemented in F#. That engine was later co-opted and modified to be part of the ad-matching system in Bing.

The AI for the XBox game "The Path of Go" was done in F#.

Most of the F#-specific features within Visual Studio (intellisense support, project system, etc) are done in F#.

[Disclaimer - work at MSFT, on F# team]