import * as O from "fp-ts/Option"; import * as N from "fp-ts/number"; import { chunksOf, concat, every, exists, filter, findFirst, isOutOfBound, map, mapWithIndex, reduce, replicate, tail } from "fp-ts/lib/Array"; import { pipe } from "fp-ts/lib/function"; import { concatAll } from "fp-ts/lib/Monoid"; import { isEmpty } from "fp-ts/lib/string"; import { draws as testDraws, RawTable, tables as testTables } from "./input.test"; import { draws as inputDraws, tables as inputTables } from "./input"; type Value = { value: number, marked: boolean }; type Table = { id: number; lastMarked?: number, won?: number; table: Value[][] } const parseTables = (tables: string) => pipe( tables.split("\n"), filter(s => !isEmpty(s)), chunksOf(5), map(table => pipe(table, map(row => row.split(" ").filter(n => !isEmpty(n)).map(s => parseInt(s))))) ); const tables: Table[] = pipe( // testTables, parseTables(inputTables), mapWithIndex((i, t) => ({ id: i, table: pipe(t, map(r => pipe(r, map(v => ({ value: v, marked: false }))))) })) ); const draws: number[] = // testDraws; inputDraws; const product = (a: number) => (b: number) => N.MonoidProduct.concat(a, b); const result = (table: Table): number => pipe( [...table.table], reduce([] as Value[], (all, curr) => pipe(all, concat(curr))), filter(v => !v.marked), map(v => v.value), concatAll(N.MonoidSum), product(table.lastMarked ?? 1) ); const evalRows = (table: Table): O.Option => pipe( [...table.table], map(every(v => v.marked)), reduce(false, (acc, r) => acc || r), O.guard, O.map(() => result(table)) ); const evalColumns = (table: Table): O.Option => pipe( [...table.table], reduce( replicate(table.table[0].length, true), (marks, row) => pipe(row, map(v => v.marked), mapWithIndex((i, m) => marks[i] && m)) ), exists(m => m), O.guard, O.map(() => result(table)) ); const evalTable = (table: Table): Table => pipe( table, evalRows, O.alt(() => evalColumns(table)), O.fold( () => ({...table}), n => ({...table, won: n}) ) ); const findValue = (draw: number) => (table: Table): O.Option => pipe( [...table.table], map(findFirst((v: Value) => v.value === draw)), reduce(O.none as O.Option, (v, o) => pipe(v, O.alt(() => o))) ); const calc = (tables: Table[]) => (draw: number): Table[] => pipe( tables, filter(t => t.won === undefined), map(t => pipe( t, findValue(draw), O.fold( () => ({ ...t, lastMarked: undefined }), v => { v.marked = true; return { ...t, lastMarked: draw }; } ), evalTable )) ); const play = (ts: Table[], ds: number[]): Table[] => { if(isOutOfBound(0, ds)) { return ts; } const newTables = calc(ts)(ds[0]); console.log("new winners:", pipe(newTables, filter(t => t.won !== undefined))); return play(newTables, pipe(ds, tail, O.getOrElse(() => [] as number[]))); } play(tables, draws);