:(){ :|:& };:
đđ»ââïž Â đŽđ»ââïž Â đđ»ââïž Â â
Dag: 6
SprÄk: Python (numpy)
import numpy as np
offset = np.array([[-1, 0],[0, 1],[1, 0],[0, -1]])
d = "0123"
original = np.array([[c for c in l.strip()]
for l in open("input06.txt", "r").readlines()])
start = tuple(zip(*np.where(original == '^')))[0]
def find_loop(a):
dir = 0
pos = next = start
while (0 <= next[0] < a.shape[0] and 0 <= next[1] < a.shape[1]):
if a[next] == '#':
dir = (dir + 1) % 4
else:
pos = next
if a[pos] == d[dir]:
return True
a[pos] = d[dir]
next = tuple(pos + offset[dir])
return False
def relabel_visited(a):
for i in range(4):
a[a == d[i]] = 'X'
def part1(a):
find_loop(a) # We will not find a loop but it walks the path
relabel_visited(a)
return a[a == 'X'].size + (a[start] == '^')
def test_obstacle(pos):
a = original.copy()
a[pos] = '#'
return find_loop(a)
def part2(indices):
return sum(test_obstacle(pos) for pos in indices)
a = original.copy()
print(part1(a))
print(part2(zip(*np.where(a == 'X'))))
Dag: 6
SprÄk: Python (numpy)
import numpy as np
offset = np.array([[-1, 0],[0, 1],[1, 0],[0, -1]])
d = "0123"
original = np.array([[c for c in l.strip()]
for l in open("input06.txt", "r").readlines()])
start = tuple(zip(*np.where(original == '^')))[0]
def find_loop(a):
dir = 0
pos = next = start
while (0 <= next[0] < a.shape[0] and 0 <= next[1] < a.shape[1]):
if a[next] == '#':
dir = (dir + 1) % 4
else:
pos = next
if a[pos] == d[dir]:
return True
a[pos] = d[dir]
next = tuple(pos + offset[dir])
return False
def relabel_visited(a):
for i in range(4):
a[a == d[i]] = 'X'
def part1(a):
find_loop(a) # We will not find a loop but it walks the path
relabel_visited(a)
return a[a == 'X'].size + (a[start] == '^')
def test_obstacle(pos):
a = original.copy()
a[pos] = '#'
return find_loop(a)
def part2(indices):
return sum(test_obstacle(pos) for pos in indices)
a = original.copy()
print(part1(a))
print(part2(zip(*np.where(a == 'X'))))
Har du testat att jÀmföra exekveringstid om du implementerar samma lösning med och utan numpy? Min naiva lösning (prova att sÀtta ett block pÄ varje plats i vÄr path och kolla om det loopar) kör pÄ typ 20s pÄ en M1 Pro.
:(){ :|:& };:
đđ»ââïž Â đŽđ»ââïž Â đđ»ââïž Â â
Dag 6:
Sprak: Golang
Del 2 gÄr segare Àn jag önskar ~2s
package main
import (
"bufio"
"fmt"
"os"
)
type Position struct {
y int
x int
}
func isOutOfBounds(rows []string, pos Position) bool {
if pos.y < 0 || pos.y >= len(rows) {
return true
} else if pos.x < 0 || pos.x >= len(rows[pos.y]) {
return true
}
return false
}
func getNewDirection(dir Position) Position {
if dir.y == -1 && dir.x == 0 { // north
return Position{0, 1} // east
} else if dir.y == 0 && dir.x == 1 { // east
return Position{1, 0} // south
} else if dir.y == 1 && dir.x == 0 { // south
return Position{0, -1} // west
} else if dir.y == 0 && dir.x == -1 { // west
return Position{-1, 0} // north
}
panic("no supported direction")
}
func moveDirection(rows []string, pos, dir Position, visitMap map[int]map[int]int) (Position, Position, bool, int) {
pos.y += dir.y
pos.x += dir.x
if isOutOfBounds(rows, pos) {
return pos, dir, false, 0
} else if rows[pos.y][pos.x] == '#' || rows[pos.y][pos.x] == 'O' {
pos.y -= dir.y
pos.x -= dir.x
return pos, getNewDirection(dir), true, 0
}
// fmt.Println("Visiting", pos.y, pos.x)
visitCount := visit(rows, pos, visitMap)
return pos, dir, true, visitCount
}
func visit(rows []string, pos Position, visitMap map[int]map[int]int) int {
if _, exist := visitMap[pos.y]; !exist {
visitMap[pos.y] = make(map[int]int, len(rows[pos.y]))
}
visitMap[pos.y][pos.x]++
return visitMap[pos.y][pos.x]
}
func findStart(rows []string) (start, direction Position) {
for y, row := range rows {
for x, char := range row {
if char == '^' { // direction up
start.y, start.x = y, x
direction.y, direction.x = -1, 0
return start, direction
}
}
}
panic("no start position")
}
func moveUntilDone(rows []string, pos, dir Position, visitMap map[int]map[int]int) bool {
var moving bool = true
posCount := 0
for moving {
pos, dir, moving, posCount = moveDirection(rows, pos, dir, visitMap)
if posCount > 4 {
return true
}
}
return false
}
func getPartOne(rows []string) (count int) {
startPos, startDirection := findStart(rows)
visitMap := make(map[int]map[int]int, len(rows))
visit(rows, startPos, visitMap)
moveUntilDone(rows, startPos, startDirection, visitMap)
for _, v := range visitMap {
count += len(v)
}
return count
}
func getPartTwo(rows []string) (count int) {
startPos, startDirection := findStart(rows)
visitMap := make(map[int]map[int]int, len(rows))
visit(rows, startPos, visitMap)
moveUntilDone(rows, startPos, startDirection, visitMap)
for y, v := range visitMap {
for x := range v {
if startPos.y == y && startPos.x == x {
continue
}
rows[y] = rows[y][:x] + "O" + rows[y][x+1:]
newVisitMap := make(map[int]map[int]int, len(rows))
if moveUntilDone(rows, startPos, startDirection, newVisitMap) {
count++
}
rows[y] = rows[y][:x] + "." + rows[y][x+1:]
}
}
return count
}
func getRows(filename string) []string {
file, err := os.Open(filename)
if err != nil {
panic(err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
var rows []string
for scanner.Scan() {
row := scanner.Text()
rows = append(rows, row)
}
return rows
}
func main() {
fmt.Println("Part one:", getPartOne(getRows("../input.txt")))
fmt.Println("Part two:", getPartTwo(getRows("../input.txt")))
}
Har du testat att jÀmföra exekveringstid om du implementerar samma lösning med och utan numpy? Min naiva lösning (prova att sÀtta ett block pÄ varje plats i vÄr path och kolla om det loopar) kör pÄ typ 20s pÄ en M1 Pro.
Tajmade just. Tar ca 40s pÄ min 12900K.
Detta Àr dock inte en uppgift dÀr numpy glÀnser, manipulering av enskilda element Àr inte dess starkaste sida. Vid varje access mÄste numpy testa vad du indexerar med, Àr det heltal, en slice, ett villkor, konstiga listor eller en tupel? Det kostar i prestanda nÀr man gör lite per access. NÀr det blir operationer pÄ stora arrayer dÀremot...
Men att kunna adressera arrayen med tupler och villkor Àr en programmeringsmodell att jag inte kan motstÄ
Dag: 6
SprÄk: Rust
use rayon::prelude::*;
use std::fs;
pub const INPUT_FILE: &str = "input.txt";
const DIRECTIONS: &[&Vec2D; 4] = &[
&Vec2D { x: 0, y: -1 },
&Vec2D { x: 1, y: 0 },
&Vec2D { x: 0, y: 1 },
&Vec2D { x: -1, y: 0 },
];
#[derive(Default, Clone, Copy)]
pub struct Vec2D {
x: i32,
y: i32,
}
pub type Map = Vec<Vec<bool>>;
pub fn read_map_and_start_pos(path: &str) -> (Map, Vec2D) {
let mut start_pos = Vec2D::default();
let mut map = Map::default();
for (y, line) in fs::read_to_string(path)
.expect(&format!("File {} should be present", path))
.lines()
.enumerate()
{
let mut row = Vec::new();
for (x, ch) in line.chars().enumerate() {
if '^' == ch {
start_pos = Vec2D {
x: x as i32,
y: y as i32,
}
}
row.push('#' == ch);
}
map.push(row);
}
(map, start_pos)
}
fn calc_num_distinct_tiles(map: &Map, start_pos: &Vec2D, added_block_pos: &Vec2D) -> (u32, bool) {
let width = map[0].len() as i32;
let height = map.len() as i32;
let mut direction = 0;
let mut visited = vec![vec![0u8; width as usize]; height as usize];
let mut guard_pos = *start_pos;
let mut next_forward_pos = *start_pos;
let mut num_distinct_tiles = 0;
while next_forward_pos.x >= 0
&& next_forward_pos.x < width
&& next_forward_pos.y >= 0
&& next_forward_pos.y < height
{
if (added_block_pos.x == next_forward_pos.x && added_block_pos.y == next_forward_pos.y)
|| map[next_forward_pos.y as usize][next_forward_pos.x as usize]
{
direction = (direction + 1) % 4;
} else {
guard_pos = next_forward_pos;
let visited_tile = &mut visited[guard_pos.y as usize][guard_pos.x as usize];
if *visited_tile == 0 {
num_distinct_tiles += 1;
} else if (*visited_tile & (1 << direction)) != 0 {
return (num_distinct_tiles, true);
}
*visited_tile |= 1 << direction;
}
let forward = DIRECTIONS[direction];
next_forward_pos = Vec2D {
x: guard_pos.x + forward.x,
y: guard_pos.y + forward.y,
};
}
(num_distinct_tiles, false)
}
pub fn calc_num_distinct_tiles_before_leaving(map: &Map, start_pos: &Vec2D) -> u32 {
let dummy_pos = Vec2D { x: -1, y: -1 };
let (cnt, _) = calc_num_distinct_tiles(map, start_pos, &dummy_pos);
cnt
}
pub fn calc_num_block_position_causing_loop(map: &Map, start_pos: &Vec2D) -> u32 {
(0..map.len())
.into_par_iter()
.map(|y| {
(0..map[0].len())
.filter_map(|x| {
let block_pos = Vec2D {
x: x as i32,
y: y as i32,
};
if block_pos.x == start_pos.x && block_pos.y == start_pos.y
|| map[block_pos.y as usize][block_pos.x as usize]
{
return None;
}
let (_, is_loop) = calc_num_distinct_tiles(map, start_pos, &block_pos);
if is_loop {
Some(1)
} else {
None
}
})
.sum::<u32>()
})
.sum()
}
fn main() {
let (map, start_pos) = read_map_and_start_pos(INPUT_FILE);
println!(
"part 1: {}",
calc_num_distinct_tiles_before_leaving(&map, &start_pos)
);
println!(
"part 2: {}",
calc_num_block_position_causing_loop(&map, &start_pos)
);
}
Del 1: 16”s (M3) / 51”s (Orange Pi 5)
Del 2: 20ms (M3) / 147ms (Orange Pi 5)
Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer
Dag: 6
SprÄk: F#
Inte direkt snabb, del tvÄ tar sÀkert 6 sekunder pÄ en M2.
type Direction = UP | DOWN | LEFT | RIGHT
type Spot = EMPTY | OBSTACLE | START
type Position = {X: int; Y: int}
type Movement = { position: Position; direction: Direction }
let data = System.IO.File.ReadAllLines("input.txt")
|> Seq.mapi (fun x ->
Seq.mapi (fun y c ->
let spot = match c with
| '.' -> EMPTY
| '#' -> OBSTACLE
| '^' -> START
| _ -> failwith "Invalid input"
({X= x; Y=y}, spot)
))
|> Seq.concat
let startPosition = data |> Seq.find (fun (_, s) -> s = START) |> fst
let obstacles = data |> Seq.filter (fun (_, s) -> s = OBSTACLE) |> Seq.map fst |> Set.ofSeq
let bounds =
let maxX = data |> Seq.map fst |> Seq.maxBy _.X |> _.X
let maxY = data |> Seq.map fst |> Seq.maxBy _.Y |> _.Y
(maxX, maxY)
let move obstacles movement =
let potentialNewMove = match movement.direction with
| UP -> { movement with position.X = movement.position.X - 1 }
| DOWN -> { movement with position.X = movement.position.X + 1 }
| LEFT -> { movement with position.Y = movement.position.Y - 1 }
| RIGHT -> { movement with position.Y = movement.position.Y + 1 }
match Set.contains potentialNewMove.position obstacles with
| false -> potentialNewMove
| true -> match movement.direction with
| UP -> { movement with direction = RIGHT; }
| DOWN -> { movement with direction = LEFT;}
| LEFT -> { movement with direction = UP;}
| RIGHT -> { movement with direction = DOWN;}
let MoveUntilOutOfBounds obstacles movement =
let rec MoveUntilOutOfBounds' pos cnt =
let newMovement = move obstacles pos
match newMovement.position.X < 0 || newMovement.position.Y < 0 || newMovement.position.X > fst bounds || newMovement.position.Y > snd bounds with
| true -> cnt
| false -> MoveUntilOutOfBounds' newMovement (cnt |> Set.add newMovement.position)
MoveUntilOutOfBounds' movement Set.empty
let visitedPositions = MoveUntilOutOfBounds obstacles { position = startPosition; direction = UP }
printfn $"Task 1: %A{visitedPositions |> Set.count}"
let createsLoop movement obstacles =
let rec MoveUntilOutOfBounds' pos cnt =
let newMovement = move obstacles pos
if newMovement.position.X < 0 || newMovement.position.Y < 0 || newMovement.position.X > fst bounds || newMovement.position.Y > snd bounds then false
elif Set.contains newMovement cnt && newMovement <> pos then true
else MoveUntilOutOfBounds' newMovement (cnt |> Set.add newMovement)
MoveUntilOutOfBounds' movement Set.empty
let obstaclesThatCreatesLoop = visitedPositions |> Set.filter (fun position -> createsLoop { position=startPosition; direction = UP } (Set.add position obstacles))
printfn $"Task 2: %A{obstaclesThatCreatesLoop |> Set.count}"
Jag Àr en optimist; det Àr aldrig sÄ dÄligt sÄ att det inte kan bli sÀmre.
Dag 6:
Sprak: Golang
Del 2 gÄr segare Àn jag önskar ~2s
package main
import (
"bufio"
"fmt"
"os"
)
type Position struct {
y int
x int
}
func isOutOfBounds(rows []string, pos Position) bool {
if pos.y < 0 || pos.y >= len(rows) {
return true
} else if pos.x < 0 || pos.x >= len(rows[pos.y]) {
return true
}
return false
}
func getNewDirection(dir Position) Position {
if dir.y == -1 && dir.x == 0 { // north
return Position{0, 1} // east
} else if dir.y == 0 && dir.x == 1 { // east
return Position{1, 0} // south
} else if dir.y == 1 && dir.x == 0 { // south
return Position{0, -1} // west
} else if dir.y == 0 && dir.x == -1 { // west
return Position{-1, 0} // north
}
panic("no supported direction")
}
func moveDirection(rows []string, pos, dir Position, visitMap map[int]map[int]int) (Position, Position, bool, int) {
pos.y += dir.y
pos.x += dir.x
if isOutOfBounds(rows, pos) {
return pos, dir, false, 0
} else if rows[pos.y][pos.x] == '#' || rows[pos.y][pos.x] == 'O' {
pos.y -= dir.y
pos.x -= dir.x
return pos, getNewDirection(dir), true, 0
}
// fmt.Println("Visiting", pos.y, pos.x)
visitCount := visit(rows, pos, visitMap)
return pos, dir, true, visitCount
}
func visit(rows []string, pos Position, visitMap map[int]map[int]int) int {
if _, exist := visitMap[pos.y]; !exist {
visitMap[pos.y] = make(map[int]int, len(rows[pos.y]))
}
visitMap[pos.y][pos.x]++
return visitMap[pos.y][pos.x]
}
func findStart(rows []string) (start, direction Position) {
for y, row := range rows {
for x, char := range row {
if char == '^' { // direction up
start.y, start.x = y, x
direction.y, direction.x = -1, 0
return start, direction
}
}
}
panic("no start position")
}
func moveUntilDone(rows []string, pos, dir Position, visitMap map[int]map[int]int) bool {
var moving bool = true
posCount := 0
for moving {
pos, dir, moving, posCount = moveDirection(rows, pos, dir, visitMap)
if posCount > 4 {
return true
}
}
return false
}
func getPartOne(rows []string) (count int) {
startPos, startDirection := findStart(rows)
visitMap := make(map[int]map[int]int, len(rows))
visit(rows, startPos, visitMap)
moveUntilDone(rows, startPos, startDirection, visitMap)
for _, v := range visitMap {
count += len(v)
}
return count
}
func getPartTwo(rows []string) (count int) {
startPos, startDirection := findStart(rows)
visitMap := make(map[int]map[int]int, len(rows))
visit(rows, startPos, visitMap)
moveUntilDone(rows, startPos, startDirection, visitMap)
for y, v := range visitMap {
for x := range v {
if startPos.y == y && startPos.x == x {
continue
}
rows[y] = rows[y][:x] + "O" + rows[y][x+1:]
newVisitMap := make(map[int]map[int]int, len(rows))
if moveUntilDone(rows, startPos, startDirection, newVisitMap) {
count++
}
rows[y] = rows[y][:x] + "." + rows[y][x+1:]
}
}
return count
}
func getRows(filename string) []string {
file, err := os.Open(filename)
if err != nil {
panic(err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
var rows []string
for scanner.Scan() {
row := scanner.Text()
rows = append(rows, row)
}
return rows
}
func main() {
fmt.Println("Part one:", getPartOne(getRows("../input.txt")))
fmt.Println("Part two:", getPartTwo(getRows("../input.txt")))
}
Liten justering av din lösning, Àr dÄ nere pÄ 0,6s pÄ M3
Go stora styrka Àr inte parallellism, det Àr concurrency och syns hÀr...
Dag: 6
SprÄk: Go
package main
import (
"bufio"
"fmt"
"os"
)
type Position struct {
y int
x int
}
func isOutOfBounds(rows []string, pos Position) bool {
if pos.y < 0 || pos.y >= len(rows) {
return true
} else if pos.x < 0 || pos.x >= len(rows[pos.y]) {
return true
}
return false
}
func getNewDirection(dir Position) Position {
if dir.y == -1 && dir.x == 0 { // north
return Position{0, 1} // east
} else if dir.y == 0 && dir.x == 1 { // east
return Position{1, 0} // south
} else if dir.y == 1 && dir.x == 0 { // south
return Position{0, -1} // west
} else if dir.y == 0 && dir.x == -1 { // west
return Position{-1, 0} // north
}
panic("no supported direction")
}
func moveDirection(rows []string, pos, dir Position, visitMap map[int]map[int]int) (Position, Position, bool, int) {
pos.y += dir.y
pos.x += dir.x
if isOutOfBounds(rows, pos) {
return pos, dir, false, 0
} else if rows[pos.y][pos.x] == '#' || rows[pos.y][pos.x] == 'O' {
pos.y -= dir.y
pos.x -= dir.x
return pos, getNewDirection(dir), true, 0
}
// fmt.Println("Visiting", pos.y, pos.x)
visitCount := visit(rows, pos, visitMap)
return pos, dir, true, visitCount
}
func visit(rows []string, pos Position, visitMap map[int]map[int]int) int {
if _, exist := visitMap[pos.y]; !exist {
visitMap[pos.y] = make(map[int]int, len(rows[pos.y]))
}
visitMap[pos.y][pos.x]++
return visitMap[pos.y][pos.x]
}
func findStart(rows []string) (start, direction Position) {
for y, row := range rows {
for x, char := range row {
if char == '^' { // direction up
start.y, start.x = y, x
direction.y, direction.x = -1, 0
return start, direction
}
}
}
panic("no start position")
}
func moveUntilDone(rows []string, pos, dir Position, visitMap map[int]map[int]int) bool {
var moving bool = true
posCount := 0
for moving {
pos, dir, moving, posCount = moveDirection(rows, pos, dir, visitMap)
if posCount > 4 {
return true
}
}
return false
}
func getPartOne(rows []string) (count int) {
startPos, startDirection := findStart(rows)
visitMap := make(map[int]map[int]int, len(rows))
visit(rows, startPos, visitMap)
moveUntilDone(rows, startPos, startDirection, visitMap)
for _, v := range visitMap {
count += len(v)
}
return count
}
func deepCopySlice(original []string) []string {
copy := make([]string, len(original))
for i, str := range original {
copy[i] = str
}
return copy
}
func getPartTwo(_rows []string) (count int) {
startPos, startDirection := findStart(_rows)
visitMap := make(map[int]map[int]int, len(_rows))
visit(_rows, startPos, visitMap)
moveUntilDone(_rows, startPos, startDirection, visitMap)
cntChan := make(chan int)
// CHANGE MADE HERE!
for _y, _v := range visitMap {
go func(y int, v map[int]int, rows []string) {
rowCnt := 0
for x := range v {
if startPos.y == y && startPos.x == x {
continue
}
rows[y] = rows[y][:x] + "O" + rows[y][x+1:]
newVisitMap := make(map[int]map[int]int, len(rows))
if moveUntilDone(rows, startPos, startDirection, newVisitMap) {
rowCnt++
}
rows[y] = rows[y][:x] + "." + rows[y][x+1:]
}
cntChan <- rowCnt
}(_y, _v, deepCopySlice(_rows))
}
for i := len(visitMap); i > 0; i-- {
count += <-cntChan
}
return count
}
func getRows(filename string) []string {
file, err := os.Open(filename)
if err != nil {
panic(err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
var rows []string
for scanner.Scan() {
row := scanner.Text()
rows = append(rows, row)
}
return rows
}
func main() {
fmt.Println("Part one:", getPartOne(getRows("input.txt")))
fmt.Println("Part two:", getPartTwo(getRows("input.txt")))
}
Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer
Dag 7 Del 1 skriker vid första anblick "Permutated Bruteforce" men det kan helt omöjligt vara vad skaparen bakom pusslet vill att man ska nyttja? SÄdana lösningar Àr ju frÀmst till för den tveksamma aktiviteten lösenordshackning.
Jag lider konstant av medveten inkompetens och den paradox det innebÀr. Jag vet att det Àr nÄgot jag inte vet Ànnu men jag vet inte vad det kan vara för jag vet inte ens vad som finns Ànnu!
Ăr det nĂ„got inom Numbers Theory, nĂ„got inom Graph Theory, nĂ„got inom nĂ„gon annan Matematisk Theory-disciplin vars teori(er) lett till numera kĂ€nda algoritmer sĂ€rskilt framtagna för att lösa just sĂ„dana hĂ€r slags problem mer effektivt Ă€n klassisk "Permutated Bruteforce"?
Om nÄgon vill ge en ledtrÄd för Dag 7 Del 1 till vad för "kÀnda" algoritm(er) inom vilken Matematisk Theory-disciplin jag borde ta en titt pÄ sÄ dela gÀrna det i en spoiler-tagg. Jag har ju redan lÀrt mig nyttja koordinatsystem nu tack vare tidigare dagar och snart lÀr jag inom tid fÄ lÀra mig nyttja Graph Theory med noder och kanter i praktiken.
Tills dess sÄ provar jag "Permutated Bruteforce" (vilket lÀr ej funka vid runtime pÄ grund av för hög !n dÀrmed minnesbrist i PHP)!
EDIT: Jag upptÀckte redan innan jag försökte mig pÄ "Permutated Bruteforce" att det högsta !n jag kommer att fÄ Àr 11 vilket dÄ blir 39916800 vilket innebÀr följande nÀr en sÄdan stor array försöks fyllas:
$test = array_fill(0, 39916800, "999");
var_dump($test);
[Running] php "c:\xampp\htdocs\skoj\7.php"
PHP Fatal error: Allowed memory size of 943718400
bytes exhausted (tried to allocate 1073741832 bytes)
in C:\xampp\htdocs\skoj\7.php on line 865
Fatal error: Allowed memory size of 943718400
bytes exhausted (tried to allocate 1073741832 bytes)
in C:\xampp\htdocs\skoj\7.php on line 865
Eller ja, det fungerar om jag har: ini_set('memory_limit', '-1'); men dÄ arbetar VSCode ihjÀl sig och försöker ÀndÄ allokera flera GB trots att det egentligen bara Àr ~1074 MB som behövs för den största möjliga arrayen.
Mvh,
WKF.
(V)ulnerabilities
(I)n
(B)asically
(E)verything
Programming
Möjliga kombinationer för dag 7 Àr vÀl 3^(n-1) för varje rad. Det blir sÄklart relativt snabbt otrevligt, men borde inte vara nÄgra problem för den hÀr uppgiften.
Dag 7 Del 1 skriker vid första anblick "Permutated Bruteforce" men det kan helt omöjligt vara vad skaparen bakom pusslet vill att man ska nyttja? SÄdana lösningar Àr ju frÀmst till för den tveksamma aktiviteten lösenordshackning.
Jag lider konstant av medveten inkompetens och den paradox det innebÀr. Jag vet att det Àr nÄgot jag inte vet Ànnu men jag vet inte vad det kan vara för jag vet inte ens vad som finns Ànnu!
Ăr det nĂ„got inom Numbers Theory, nĂ„got inom Graph Theory, nĂ„got inom nĂ„gon annan Matematisk Theory-disciplin vars teori(er) lett till numera kĂ€nda algoritmer sĂ€rskilt framtagna för att lösa just sĂ„dana hĂ€r slags problem mer effektivt Ă€n klassisk "Permutated Bruteforce"?
Om nÄgon vill ge en ledtrÄd för Dag 7 Del 1 till vad för "kÀnda" algoritm(er) inom vilken Matematisk Theory-disciplin jag borde ta en titt pÄ sÄ dela gÀrna det i en spoiler-tagg. Jag har ju redan lÀrt mig nyttja koordinatsystem nu tack vare tidigare dagar och snart lÀr jag inom tid fÄ lÀra mig nyttja Graph Theory med noder och kanter i praktiken.
Tills dess sÄ provar jag "Permutated Bruteforce" (vilket lÀr ej funka vid runtime pÄ grund av för hög !n dÀrmed minnesbrist i PHP)!
EDIT: Jag upptÀckte redan innan jag försökte mig pÄ "Permutated Bruteforce" att det högsta !n jag kommer att fÄ Àr 11 vilket dÄ blir 39916800 vilket innebÀr följande nÀr en sÄdan stor array försöks fyllas:
$test = array_fill(0, 39916800, "999");
var_dump($test);
[Running] php "c:\xampp\htdocs\skoj\7.php"
PHP Fatal error: Allowed memory size of 943718400
bytes exhausted (tried to allocate 1073741832 bytes)
in C:\xampp\htdocs\skoj\7.php on line 865
Fatal error: Allowed memory size of 943718400
bytes exhausted (tried to allocate 1073741832 bytes)
in C:\xampp\htdocs\skoj\7.php on line 865
Eller ja, det fungerar om jag har: ini_set('memory_limit', '-1'); men dÄ arbetar VSCode ihjÀl sig och försöker ÀndÄ allokera flera GB trots att det egentligen bara Àr ~1074 MB som behövs för den största möjliga arrayen.
Mvh,
WKF.
Skriver frÄn mobil sÄ ursÀkta formatering...
Min lösning med ca 60ms Del 1 och ~ 4000ms Del 2(string concatenation Àr inte billigt ...) handlar helt enkelt om att:
För varje rad, generera alla möjliga kombinationer av dina operatorer. Varje rad har "AntalSiffror - 1" operatorer.
Sedan itererar du varje kombination av dessa.
Iterera sedan över varje operator i varje kombination och hantera dina siffror likt följande (möjlig lösning nedan sÄ lÀs bara om du vill)
var before = numbersForLine[operatorIndex]
var after = numbersForLine[operatorIndex + 1]
if operatorIndex == 1 {
if operator==add { value = before + after }
else if operator == mult { value = before * after}
}
else {
if operator==add { value += after }
else if operator == mult { value *= after}
}
NZXT H510 Flow MSI B450 Tomahawk MAX
AMD Ryzen 5800X3D RX 7900XTX Kingston Fury 64GB
LG C2 42" 4K@120Hz AOC Q27G2U 1440P@144Hz
Dag 7
SprÄk: Java
GitHub
Konkatenerade genom att konvertera till en strÀng och sen tillbaka först, tiden minskade till ~en tredjedel nÀr jag gjorde den matematiskt istÀllet. Inget jag tÀnkt pÄ tidigare
Starka Äsikter om onödiga saker.
Dag: ?
SprÄk: Python
from operator import add, mul
def test_operators(expect, terms, operators, computed = 0):
if not terms:
return expect if expect == computed else 0
for o in operators:
if test_operators(expect, terms[1:], operators, o(computed, terms[0])):
return expect
return 0
s1 = s2 = 0
for l in open("input07.txt", "r").readlines():
expect, terms = l.split(": ")
s1 += test_operators(int(expect), list(map(int, terms.split(" "))), [add, mul])
s2 += test_operators(int(expect), list(map(int, terms.split(" "))), [add, mul, lambda x,y: int(str(x) + str(y))])
print(s1, s2)
Eller i lite mer one-liner-ish style. Man kommer inte ifrÄn att den rekursiva funktionen mÄste ha ett namn.
from operator import add, mul
def test_operators(expect, terms, operators, computed = 0):
if not terms:
return expect if expect == computed else 0
return any(test_operators(expect, terms[1:], operators, o(computed, terms[0])) for o in operators) and expect
print(*[sum(test_operators(expect, terms, os)
for expect, terms in [(int(expect), list(map(int, terms.split()))) for expect, terms in
(line.split(": ") for line in open("input07.txt", "r").readlines())])
for os in [[add, mul], [add, mul, lambda x,y: int(str(x) + str(y))]]])
Dag: 7
SprÄk: C#
Dag 7
Del 1: ~27ms
Del 2: ~3700ms
Kan lÀgga in parallellism och bÀttre generering av antalet kombinationer, men nu Àr PoE2 ute sÄ jag nöjer mig med detta
namespace Day_7;
public class Solver
{
private enum Operator
{
Add,
Mult,
Concat
}
private static List<Operator[]> GetOperatorCombinations(int count, List<Operator> operators)
{
var result = new List<Operator[]>();
GenerateCombinations(operators.ToArray(), count, new Operator[count], result, 0);
return result;
}
private static void GenerateCombinations(Operator[] operators, int count, Operator[] current, List<Operator[]> result, int index)
{
if (index == count)
{
result.Add((Operator[])current.Clone());
return;
}
foreach (var op in operators)
{
current[index] = op;
GenerateCombinations(operators, count, current, result, index + 1);
}
}
private static List<(long Value, List<long> Numbers)> ParseInput(List<string> data)
{
List<(long val, List<long> numbers)> result = new();
foreach (var line in data)
{
var value = Int64.Parse(line.Substring(0, line.IndexOf(':')));
var numbers = line[(line.IndexOf(':') + 1)..].Split(' ', StringSplitOptions.RemoveEmptyEntries).Select(s => Int64.Parse(s.Trim())).ToList();
result.Add((val: value, numbers: numbers));
}
return result;
}
public static long Run_PartOne(List<string> input)
{
var data = ParseInput(input);
long totalValue = 0;
foreach (var line in data)
{
long expectedValue = line.Value;
var numbers = line.Numbers;
var numberOfOperators = numbers.Count - 1;
var combinations = GetOperatorCombinations(numberOfOperators, [Operator.Add, Operator.Mult]);
for (int i = 0; i < combinations.Count; i++)
{
long val = 0;
var operators = combinations.ElementAt(i);
for (int j = 0; j < operators.Length; j++)
{
var numberBefore = numbers[j];
var numberAfter = numbers[j + 1];
var currentOp = operators[j];
if (j == 0)
{
if (currentOp == Operator.Add)
val = numberBefore + numberAfter;
else if (currentOp == Operator.Mult)
val = numberBefore * numberAfter;
}
else
{
if (currentOp == Operator.Add)
val += numberAfter;
else if (currentOp == Operator.Mult)
val *= numberAfter;
}
}
if (val == expectedValue)
{
totalValue += val;
break;
}
}
}
return totalValue;
}
public static long Run_PartTwo(List<string> input)
{
var data = ParseInput(input);
long totalValue = 0;
foreach (var line in data)
{
long expectedValue = line.Value;
var numbers = line.Numbers;
var numberOfOperators = numbers.Count - 1;
var combinations = GetOperatorCombinations(numberOfOperators,
[Operator.Add, Operator.Mult, Operator.Concat]);
for (int i = 0; i < combinations.Count; i++)
{
long val = 0;
var operators = combinations.ElementAt(i);
for (int j = 0; j < operators.Length; j++)
{
var numberBefore = numbers[j];
var numberAfter = numbers[j + 1];
var currentOp = operators[j];
if (j == 0)
{
if (currentOp == Operator.Add)
val = numberBefore + numberAfter;
else if (currentOp == Operator.Mult)
val = numberBefore * numberAfter;
else if (currentOp == Operator.Concat)
val = long.Parse($"{numberBefore}{numberAfter}");
}
else
{
if (currentOp == Operator.Add)
val += numberAfter;
else if (currentOp == Operator.Mult)
val *= numberAfter;
else if (currentOp == Operator.Concat)
val = long.Parse($"{val}{numberAfter}");
}
}
if (val == expectedValue)
{
totalValue += val;
break;
}
}
}
return totalValue;
}
}
NZXT H510 Flow MSI B450 Tomahawk MAX
AMD Ryzen 5800X3D RX 7900XTX Kingston Fury 64GB
LG C2 42" 4K@120Hz AOC Q27G2U 1440P@144Hz
Skriver frÄn mobil sÄ ursÀkta formatering...
Min lösning med ca 60ms Del 1 och ~ 4000ms Del 2(string concatenation Àr inte billigt ...) handlar helt enkelt om att:
För varje rad, generera alla möjliga kombinationer av dina operatorer. Varje rad har "AntalSiffror - 1" operatorer.
Sedan itererar du varje kombination av dessa.
Iterera sedan över varje operator i varje kombination och hantera dina siffror likt följande (möjlig lösning nedan sÄ lÀs bara om du vill)
var before = numbersForLine[operatorIndex]
var after = numbersForLine[operatorIndex + 1]
if operatorIndex == 1 {
if operator==add { value = before + after }
else if operator == mult { value = before * after}
}
else {
if operator==add { value += after }
else if operator == mult { value *= after}
}
Dag 7 Del 1 Diskussion
SÄ det Àr bruteforce av permutationer av operatorerna + och * trots allt? Det var det första jag tÀnkte pÄ sÄ fort jag gjorde min första tolkning av pusslet idag. Jag förstÄr inte varför PHP verkar vara sÄ minnesuselt sÄ fort en array vill lagra cirka 4 miljoner element? (antalet kombinationer av + och * i den rad med flest nummer, dvs., 12 nummer, dÀrmed 12-1 operatorer och dÀrmed !11 antal kombinationer).
Problemet jag verkar ha Àr inte lösningen i sig utan sjÀlva minneshanteringen (hÀr kommer verkligheten i kapp en nÀr man sluppit minneshantering i snart tre Ärs tid som Webbutvecklare) för att kunna köra lösningen.
Ăven i förrförra gĂ„rdagens Del 2 fick jag problemet med att jag skulle köra en foreach av reglerna pĂ„ varje kombination av numren, dvs., en foreach i en foreach och funktionen som genererade nummerkombinationer körde rekursivt och fick minnesbrist av samma skĂ€l: för mĂ„nga element i en array.
Btw, tror jag VSCode-minnet pÄ flera GB berodde mest pÄ att jag försökte var_dumpa (skriva ut) i terminalen ~4 miljoner element snarare Àn den faktiska minneslagringen pÄ ~1024 MB för hela arrayen.
Mvh,
WKF.
(V)ulnerabilities
(I)n
(B)asically
(E)verything
Programming
Dag 7, C#
Ser att integers blivit för smÄ, det bÄdar inte sÄ gott för framtiden. à tminstone trevligt nÀr jag lyckades hjÀlpa mig sjÀlv pÄ lösningen av del 1 sÄ pass att del 2 blev trivial.
Nu i efterhand gick jag in för att optimera det, nu kör det pÄ 5 sekunder, var vÀl runt 2 minuter nÀr jag skickade in svaret...
using System.Collections.Concurrent;
using System.Diagnostics;
var lines = File.ReadAllLines("input.txt").Select(x => x.Split(':'));
(long sumpart1, long sumpart2) = (0, 0);
long a(long x, long y) => x + y;
long m(long x, long y) => x * y;
long c(long x, long y) => long.Parse($"{x}{y}");
Stopwatch sw = Stopwatch.StartNew();
ConcurrentBag<long> p1 = [];
ConcurrentBag<long> p2 = [];
Parallel.ForEach(lines, line =>
{
var expected = long.Parse(line[0]);
var numbers = line[1].Trim().Split(' ').Select(long.Parse).Reverse().ToArray();
if (Generatesolutions(numbers, [a, m]).Any(x => x == expected))
p1.Add(expected);
else if (Generatesolutions(numbers, [a, m, c]).Any(x => x == expected))
p2.Add(expected);
});
sumpart1 = p1.Sum();
sumpart2 = sumpart1 + p2.Sum();
Console.WriteLine("Part 1: " + sumpart1);
Console.WriteLine("Part 2: " + sumpart2);
Console.WriteLine("Time: " + sw.Elapsed);
static IEnumerable<long> Generatesolutions(long[] numbers, IEnumerable<Func<long, long, long>> funcs)
{
if (numbers.Length == 1)
{
yield return numbers[0];
yield break;
}
foreach (var op in funcs)
foreach (var num in Generatesolutions(numbers[1..], funcs))
yield return op(num, numbers[0]);
}
The power of GNU compiles you!
"Often statistics are used as a drunken man uses lampposts -- for support rather than illumination."
Dag: 7
SprÄk: Scala
SjĂ€lva berĂ€kningarna för del1/2 ligger pĂ„ 35/80ÎŒs. Parse funktionen tar mer Ă€n dubbelt sĂ„ lĂ„ng tid.
object Day07:
val input = Using.resource(Source.fromFile("7.txt"))(_.getLines().toArray)
def parse(input: Array[String]): Array[(Long, List[Int])] =
input.map: line =>
val Array(fst, snd) = line.split(':')
fst.toLong -> snd.trim.split(" ").map(_.toInt).reverse.toList
def part1(data: Array[(Long, List[Int])]): Long =
def canSum(target: Long, values: List[Int]): Boolean =
values.nonEmpty &&
(values.head == target ||
target % values.head == 0 && canSum(target / values.head, values.tail) ||
target >= values.head && canSum(target - values.head, values.tail))
data
.collect:
case (sum, values) if canSum(sum, values) => sum
.sum
private val table1: Array[Int] = Array(0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 3)
private val table2: Array[Int] = Array(1, 10, 100, 1000)
def part2(data: Array[(Long, List[Int])]): Long =
def multiplier(x: Int): Int =
var y = table1(31 - Integer.numberOfLeadingZeros(x))
y = y - ((x - table2(y)) >>> 31)
table2(y + 1)
def canSum(target: Long, values: List[Int]): Boolean =
values.nonEmpty &&
(values.head == target ||
target % values.head == 0 && canSum(target / values.head, values.tail) ||
target >= values.head && canSum(target - values.head, values.tail) || {
val m = multiplier(values.head)
(target - values.head) % m == 0 && canSum(target / m, values.tail)
})
data
.collect:
case (sum, values) if canSum(sum, values) => sum
.sum
def main(args: Array[String]): Unit =
println(part1(parse(input)))
println(part2(parse(input)))
Liten justering av din lösning, Àr dÄ nere pÄ 0,6s pÄ M3
Go stora styrka Àr inte parallellism, det Àr concurrency och syns hÀr...
Dag: 6
SprÄk: Go
package main
import (
"bufio"
"fmt"
"os"
)
type Position struct {
y int
x int
}
func isOutOfBounds(rows []string, pos Position) bool {
if pos.y < 0 || pos.y >= len(rows) {
return true
} else if pos.x < 0 || pos.x >= len(rows[pos.y]) {
return true
}
return false
}
func getNewDirection(dir Position) Position {
if dir.y == -1 && dir.x == 0 { // north
return Position{0, 1} // east
} else if dir.y == 0 && dir.x == 1 { // east
return Position{1, 0} // south
} else if dir.y == 1 && dir.x == 0 { // south
return Position{0, -1} // west
} else if dir.y == 0 && dir.x == -1 { // west
return Position{-1, 0} // north
}
panic("no supported direction")
}
func moveDirection(rows []string, pos, dir Position, visitMap map[int]map[int]int) (Position, Position, bool, int) {
pos.y += dir.y
pos.x += dir.x
if isOutOfBounds(rows, pos) {
return pos, dir, false, 0
} else if rows[pos.y][pos.x] == '#' || rows[pos.y][pos.x] == 'O' {
pos.y -= dir.y
pos.x -= dir.x
return pos, getNewDirection(dir), true, 0
}
// fmt.Println("Visiting", pos.y, pos.x)
visitCount := visit(rows, pos, visitMap)
return pos, dir, true, visitCount
}
func visit(rows []string, pos Position, visitMap map[int]map[int]int) int {
if _, exist := visitMap[pos.y]; !exist {
visitMap[pos.y] = make(map[int]int, len(rows[pos.y]))
}
visitMap[pos.y][pos.x]++
return visitMap[pos.y][pos.x]
}
func findStart(rows []string) (start, direction Position) {
for y, row := range rows {
for x, char := range row {
if char == '^' { // direction up
start.y, start.x = y, x
direction.y, direction.x = -1, 0
return start, direction
}
}
}
panic("no start position")
}
func moveUntilDone(rows []string, pos, dir Position, visitMap map[int]map[int]int) bool {
var moving bool = true
posCount := 0
for moving {
pos, dir, moving, posCount = moveDirection(rows, pos, dir, visitMap)
if posCount > 4 {
return true
}
}
return false
}
func getPartOne(rows []string) (count int) {
startPos, startDirection := findStart(rows)
visitMap := make(map[int]map[int]int, len(rows))
visit(rows, startPos, visitMap)
moveUntilDone(rows, startPos, startDirection, visitMap)
for _, v := range visitMap {
count += len(v)
}
return count
}
func deepCopySlice(original []string) []string {
copy := make([]string, len(original))
for i, str := range original {
copy[i] = str
}
return copy
}
func getPartTwo(_rows []string) (count int) {
startPos, startDirection := findStart(_rows)
visitMap := make(map[int]map[int]int, len(_rows))
visit(_rows, startPos, visitMap)
moveUntilDone(_rows, startPos, startDirection, visitMap)
cntChan := make(chan int)
// CHANGE MADE HERE!
for _y, _v := range visitMap {
go func(y int, v map[int]int, rows []string) {
rowCnt := 0
for x := range v {
if startPos.y == y && startPos.x == x {
continue
}
rows[y] = rows[y][:x] + "O" + rows[y][x+1:]
newVisitMap := make(map[int]map[int]int, len(rows))
if moveUntilDone(rows, startPos, startDirection, newVisitMap) {
rowCnt++
}
rows[y] = rows[y][:x] + "." + rows[y][x+1:]
}
cntChan <- rowCnt
}(_y, _v, deepCopySlice(_rows))
}
for i := len(visitMap); i > 0; i-- {
count += <-cntChan
}
return count
}
func getRows(filename string) []string {
file, err := os.Open(filename)
if err != nil {
panic(err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
var rows []string
for scanner.Scan() {
row := scanner.Text()
rows = append(rows, row)
}
return rows
}
func main() {
fmt.Println("Part one:", getPartOne(getRows("input.txt")))
fmt.Println("Part two:", getPartTwo(getRows("input.txt")))
}
Snyggt, det sparar mig en hel sekund, trots att alla rader mÄste kopieras varje gÄng
Dag: 7
SprÄk: C#
Hade problem med gÄrdagens problem (dag 6) - missade att hinder inte fÄr placeras flera gÄnger pÄ samma koordinat. Tog ett par timmar innan jag upptÀckte mitt misstag...
Idag gick det lÀttare. Jag var lite orolig medan jag implementerade part 1 att part 2 skulle vara stökig men det var bara att lÀgga till ett par rader i det hÀr fallet.
Lösningstid för part 2 ~200ms. Tog cirka sekunden med string concat som jag först anvÀnde.
internal static class December7
{
public static readonly string _dataLocation = @data\input7dec.txt;
public static long Question2()
{
long sum = 0;
StreamReader reader = new StreamReader(_dataLocation);
while (reader.ReadLine() is string line)
{
var first = line.Split(':');
long answer = long.Parse(first[0]);
long[] numbers = first[1].Trim().Split(' ').Select(long.Parse).ToArray();
sum += addAndMultiply(numbers, 0, 0, answer);
}
return sum;
}
private static long addAndMultiply(long[] numbers, long i, long currentSum, long targetSum)
{
if (i == numbers.Length)
{
//Check if target sum was reached
return currentSum == targetSum ? currentSum : 0;
}
long sum = currentSum + numbers[i];
long product = currentSum * numbers[i]; ;
long concatenated = currentSum;
long multiplier = 1;
while (numbers[i] >= multiplier)
{
multiplier *= 10;
}
concatenated = concatenated * multiplier + numbers[i];
i++;
long sum1 = 0;
long sum2 = 0;
long sum3 = 0;
if (sum != 0 && sum <= targetSum)
{
sum1 = addAndMultiply(numbers, i, sum, targetSum);
}
if (product != 0 && product <= targetSum)
{
sum2 = addAndMultiply(numbers, i, product, targetSum);
}
if (concatenated != 0 && concatenated <= targetSum)
{
sum3 = addAndMultiply(numbers, i, concatenated, targetSum);
}
return (sum1 == targetSum || sum2 == targetSum || sum3 == targetSum) ? targetSum : 0;
}
}
Just dag 2 Àr ju vÀldigt enkel att hantera med CUDA, Metal Compute eller motsvarande. DÄ man hÄller sig under 1024 rader Àr det trivialt att skriva en kernel som hanterar en rad per "CUDA-kÀrna" och sedan gör en enkel reduce pÄ slutet (det Àr vÀldigt enkelt sÄ lÀnge man hÄller sig pÄ ett block). Fungerar bÄde för del 1 och 2.
Men Àr rÀtt poÀnglöst dÄ det kommer ta flera tusentals gÄnger lÀngre att starta CUDA-kerneln och fÄ tillbaka resultatet jÀmfört med tiden sjÀlva berÀkningen tar. Totala tiden blir betydligt lÀgre om man bara hÄller sig pÄ CPU...
Hade antalet rader varit en miljon gÄnger fler eller sÄ hade det lönat sig att köra GPGPU.
Problemet Àr inte bara att det tar tid att flytta data och starta en CUDA-kernel utan att det Àr vÀldigt fÄ berÀkningar per enhet data. Det Àndras inte om man har nÄgra miljoner gÄnger fler rader. Gör man sÄ fÄ berÀkningar Àr det bÀttre att stanna kvar i CPU om man ÀndÄ lÀst in alla data dÀr och har den nÀra i cache. Det gÄr ju att dela upp io, parsning och berÀkningar i lagom stora block och köra flera trÄdar. Med 16 kÀrnor testar man 1k element samtidigt.
Diskussionen handlade om datalayout. En rapport per register eller 1 element frÄn 64 olika rapporter per register. Jag trodde att skillnaden i hastighet skulle bli större eftersom jag inte kom pÄ nÄt bra sÀtt att undvika branching i inre loopen för del tvÄ med 64 olika rapporter per register. Skillnaden blev inte sÄ stor som jag trodde, 5us / 8us. SÄ jag hade fel, bÄda varianterna Àr effektiva. Det viktiga Àr hoppet frÄn 80us ner till 5us/8us. Men jag tycker nog att metoden att sprida ut en rapport Àr enklare och dessutom nÄgot snabbare.
Dag 7 Del 1 Diskussion
SÄ det Àr bruteforce av permutationer av operatorerna + och * trots allt? Det var det första jag tÀnkte pÄ sÄ fort jag gjorde min första tolkning av pusslet idag. Jag förstÄr inte varför PHP verkar vara sÄ minnesuselt sÄ fort en array vill lagra cirka 4 miljoner element? (antalet kombinationer av + och * i den rad med flest nummer, dvs., 12 nummer, dÀrmed 12-1 operatorer och dÀrmed !11 antal kombinationer).
Problemet jag verkar ha Àr inte lösningen i sig utan sjÀlva minneshanteringen (hÀr kommer verkligheten i kapp en nÀr man sluppit minneshantering i snart tre Ärs tid som Webbutvecklare) för att kunna köra lösningen.
Ăven i förrförra gĂ„rdagens Del 2 fick jag problemet med att jag skulle köra en foreach av reglerna pĂ„ varje kombination av numren, dvs., en foreach i en foreach och funktionen som genererade nummerkombinationer körde rekursivt och fick minnesbrist av samma skĂ€l: för mĂ„nga element i en array.
Btw, tror jag VSCode-minnet pÄ flera GB berodde mest pÄ att jag försökte var_dumpa (skriva ut) i terminalen ~4 miljoner element snarare Àn den faktiska minneslagringen pÄ ~1024 MB för hela arrayen.
Mvh,
WKF.
Ett tips kan vara att anvĂ€nda papper och penna. Börja med enklast möjliga problem. Ett mĂ„l och ett nummer. Hur löser du det problemet? LĂ€gg till ett nummer till sĂ„ du har tvĂ„ nummer och en tom plats mellan de som kan vara + eller Ă. Hur löser du problemet dĂ„? Vad hĂ€nder nĂ€r du har tre nummer? Rita upp problemet som ett trĂ€d. Ett nummer per nivĂ„ med tvĂ„ förgreningar, en för + och en för Ă. Finns det situationer som kan uppstĂ„ som gör det meningslöst att fortsĂ€tta ner i en viss förgrening? I del tvĂ„ Ă€r det tre förgreningar per nummer.
Dag: 4
SprÄk: C
#include <stdio.h>
static inline int check_xmas(const char a[140][144], const int i, const int j)
{
int count = 0;
// check right
if ((a[i][j] == 'X' && a[i][j+1] == 'M' && a[i][j+2] == 'A' && a[i][j+3] =='S') ||
(a[i][j] == 'S' && a[i][j+1] == 'A' && a[i][j+2] == 'M' && a[i][j+3] =='X')) {
count++;
}
// check down
if ((a[i][j] == 'X' && a[i+1][j] == 'M' && a[i+2][j] == 'A' && a[i+3][j] =='S') ||
(a[i][j] == 'S' && a[i+1][j] == 'A' && a[i+2][j] == 'M' && a[i+3][j] =='X')) {
count++;
}
// diag - down right
if ((a[i][j] == 'X' && a[i+1][j+1] == 'M' && a[i+2][j+2] == 'A' && a[i+3][j+3] =='S') ||
(a[i][j] == 'S' && a[i+1][j+1] == 'A' && a[i+2][j+2] == 'M' && a[i+3][j+3] =='X')) {
count++;
}
// diag - up left
if ((a[i+3][j] == 'X' && a[i+2][j+1] == 'M' && a[i+1][j+2] == 'A' && a[i][j+3] =='S') ||
(a[i+3][j] == 'S' && a[i+2][j+1] == 'A' && a[i+1][j+2] == 'M' && a[i][j+3] =='X')) {
count++;
}
return count;
}
static inline int check_mas(const char a[140][144], const int i, const int j)
{
if (((a[i][j] == 'M' && a[i+1][j+1] == 'A' && a[i+2][j+2] == 'S') ||
(a[i][j] == 'S' && a[i+1][j+1] == 'A' && a[i+2][j+2] == 'M')) &&
((a[i+2][j] == 'M' && a[i+1][j+1] == 'A' && a[i][j+2] == 'S') ||
(a[i+2][j] == 'S' && a[i+1][j+1] == 'A' && a[i][j+2] == 'M'))) {
return 1;
}
return 0;
}
int main(int argc, char *argv[])
{
FILE *fp;
int i = 0, j = 0, part1 = 0, part2 = 0;
char a[140][144] = {0}; // Add 4 bytes extra per row, avoids loop range
// checks at the cost of some minor extra compute...
fp = fopen("input.txt", "r");
while (fgets(a[i], 142, fp) != NULL) { // last 2 bytes are don't care: '\n\0'
i++;
}
fclose(fp);
for (i = 0; i < 140; i++) {
for (j = 0; j < 140; j++) {
part1 += check_xmas(a, i, j);
part2 += check_mas(a, i, j);
}
}
printf("part 1: %d\n", part1);
printf("part 2: %d\n", part2);
return 0;
}
Plain old simpel C igen. Valde att lÀgga till nÄgra onödiga bytes (och dÀrmed checks) per rad för att slippa range-checks i for-loopen.
Citera mig för svar.
Arch Linux
Dag: 8
SprÄk: Java
Lösning: GitHub
Största problemet idag var att förstÄ vad han menade, men koden var ganska lÀtt.
InstÀmmer.
VÀldigt rörig beskrivning (för mig) dÀr jag först trodde det handlade om att rÀkna ut vilka antenner som kunde bilda ett par - utan att nÄgon annan antenn blockerade "line of sight" sÄ att sÀga.
InsÄg sedan att det inte alls handlade om detta och allt blev genast mycket enklare
Dag: 8
SprÄk: C#
Dag 8
Del 1: ~0.4ms
Del 2: ~0.6ms
KÀnner mig dÀremot smÄtt besegrad sÄ jag behövde googla pÄ formler för första gÄngen. Nackdelen med att jobba med "vanlig" utveckling
public class Solver
{
private struct Vector() : IEquatable<Vector>
{
public Vector(int x, int y) : this()
{
X = x;
Y = y;
}
public Vector(Vector v) : this()
{
X = v.X;
Y = v.Y;
}
public int X { get; private set; }
public int Y { get; private set; }
public static Vector operator - (Vector a, Vector b)
{
return new Vector(a.X - b.X, a.Y - b.Y);
}
public static Vector operator + (Vector a, Vector b)
{
return new Vector(a.X + b.X, a.Y + b.Y);
}
public static bool operator ==(Vector a, Vector b)
{
return a.X == b.X && a.Y == b.Y;
}
public static bool operator !=(Vector a, Vector b)
{
return a == b == false;
}
public bool Equals(Vector other)
{
return X == other.X && Y == other.Y;
}
public override bool Equals(object? obj)
{
return obj is Vector other && Equals(other);
}
public override int GetHashCode()
{
return HashCode.Combine(X, Y);
}
}
private struct Node(Vector position, string value)
{
public Vector Position { get; set; } = position;
public string Value { get; set; } = value;
}
private static Node[,] GetGrid(string input)
{
string[] lines = input.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries);
int height = lines.Length;
int width = lines[0].Length;
var grid = new Node[width, height];
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
grid[y, x] = new Node(new Vector(x, y), lines[y][x].ToString());
}
}
return grid;
}
private static Vector GetDistanceBetweenNodes(Node a, Node b)
{
return new Vector(a.Position - b.Position);
}
private static Node? GetNodeAtDistance(Vector distance, Node otherNode, Node[,] grid)
{
var pos = otherNode.Position + distance;
if (pos.X < 0 || pos.X >= grid.GetLength(1))
{
return null;
}
if (pos.Y < 0 || pos.Y >= grid.GetLength(0))
{
return null;
}
return grid[pos.Y, pos.X];
}
private static List<Node> GetAllNodesInDirectionTowardsB(Node a, Node b, Node[,] grid)
{
var delta = b.Position - a.Position;
var gcd = GCD(Math.Abs(delta.X), Math.Abs(delta.Y));
int stepX = delta.X / gcd;
int stepY = delta.Y / gcd;
int x = a.Position.X;
int y = a.Position.Y;
var currentNode = a;
var nodes = new List<Node>();
while (true)
{
nodes.Add(currentNode);
x += stepX;
y += stepY;
if ((x < 0 || x >= grid.GetLength(1)) ||
(y < 0 || y >= grid.GetLength(0)))
break;
currentNode = grid[y, x];
}
return nodes;
}
private static int GCD(int a, int b)
{
while (b != 0)
{
int temp = b;
b = a % b;
a = temp;
}
return a;
}
public static int Run_PartOne(string input)
{
var grid = GetGrid(input);
var distinctNodes = new Dictionary<string, List<Node>>();
var antiNodes = new List<Node>();
for (int y = 0; y < grid.GetLength(0); y++)
{
for (int x = 0; x < grid.GetLength(1); x++)
{
var node = grid[y, x];
if (node.Value == ".") continue;
if (!distinctNodes.TryAdd(node.Value, [node]))
{
distinctNodes[node.Value].Add(node);
}
}
}
foreach (var kvp in distinctNodes)
{
foreach (var node in kvp.Value)
{
foreach (var node2 in kvp.Value)
{
if (node.Position == node2.Position)
continue;
var distance = GetDistanceBetweenNodes(node, node2);
var node3 = GetNodeAtDistance(distance, node, grid);
if (node3.HasValue)
{
antiNodes.Add(node3.Value);
}
}
}
}
return antiNodes.DistinctBy(x => x.Position).Count();
}
public static int Run_PartTwo(string input)
{
var grid = GetGrid(input);
var distinctNodes = new Dictionary<string, List<Node>>();
var antiNodes = new List<Node>();
for (int y = 0; y < grid.GetLength(0); y++)
{
for (int x = 0; x < grid.GetLength(1); x++)
{
var node = grid[y, x];
if (node.Value == ".") continue;
if (!distinctNodes.TryAdd(node.Value, [node]))
{
distinctNodes[node.Value].Add(node);
}
}
}
foreach (var kvp in distinctNodes)
{
foreach (var node in kvp.Value)
{
foreach (var node2 in kvp.Value)
{
if (node.Position == node2.Position)
continue;
var nodes = GetAllNodesInDirectionTowardsB(node, node2, grid);
foreach (var node3 in nodes)
{
antiNodes.Add(node3);
}
}
}
}
return antiNodes.DistinctBy(x => x.Position).Count();
}
}
NZXT H510 Flow MSI B450 Tomahawk MAX
AMD Ryzen 5800X3D RX 7900XTX Kingston Fury 64GB
LG C2 42" 4K@120Hz AOC Q27G2U 1440P@144Hz
Dag: 7
SprÄk: Rust
part 1: 31 ”s (M3), 141 ”s (Orange Pi 5)
part 2: 41 ”s (M3), 235 ”s (Orange Pi 5)
use std::fs;
pub const INPUT_FILE: &str = "input.txt";
type Number = i64;
pub fn read_result_and_numbers(path: &str) -> Vec<(Number, Vec<Number>)> {
fs::read_to_string(path)
.expect("Failed to read input file")
.lines()
.map(|line| {
let parts: Vec<&str> = line.split(':').collect();
let test_value = parts[0].parse().expect("Failed to parse number");
let numbers = parts[1]
.split_whitespace()
.map(|num| num.parse().expect("Failed to parse number"))
.rev()
.collect();
(test_value, numbers)
})
.collect()
}
pub fn part1(equations: &[(Number, Vec<Number>)]) -> Number {
total_calibration_result(equations, |result, numbers| {
is_equation_valid(result, numbers, false)
})
}
pub fn part2(equations: &[(Number, Vec<Number>)]) -> Number {
total_calibration_result(equations, |result, numbers| {
is_equation_valid(result, numbers, true)
})
}
pub fn total_calibration_result(
equations: &[(Number, Vec<Number>)],
pred: impl Fn(Number, &[Number]) -> bool,
) -> Number {
equations
.iter()
.filter(|(result, numbers)| pred(*result, &numbers))
.map(|(result, _)| result)
.sum()
}
fn is_equation_valid(result: Number, numbers: &[Number], use_concat: bool) -> bool {
if numbers.is_empty() {
return false;
}
let rhs = numbers[0];
let lhs = &numbers[1..];
(lhs.len() == 0 && result == rhs)
|| (use_concat && {
let lhs_factor = next_larger_power_of_10(rhs); // lhs || rhs -> lhs * lhs_factor + rhs
is_factor(result - rhs, lhs_factor) && is_equation_valid(result / lhs_factor, lhs, true)
})
|| (is_factor(result, rhs) && is_equation_valid(result / rhs, lhs, use_concat))
|| (result >= rhs && is_equation_valid(result - rhs, lhs, use_concat))
}
fn is_factor(product: Number, number: Number) -> bool {
product % number == 0
}
fn next_larger_power_of_10(n: Number) -> Number {
let mut factor = 10;
while factor <= n {
factor *= 10;
}
factor
}
fn main() {
let equations = read_result_and_numbers(&INPUT_FILE);
println!("part 1: {}", part1(&equations));
println!("part 2: {}", part2(&equations));
}
Dag: 8
SprÄk: Rust
part 1: 7 ”s (M3), 16 ”s (Orange Pi 5)
part 2: 7 ”s (M3), 19 ”s (Orange Pi 5)
use std::fs;
use ahash::AHashMap;
use itertools::Itertools;
pub const INPUT_FILE: &str = "input.txt";
#[derive(Debug)]
pub struct Vec2D {
x: i32,
y: i32,
}
pub type AntennaPositions = Vec<Vec2D>;
pub fn read_map_dimensions_and_antenna_positions(
path: &str,
) -> ((i32, i32), Vec<AntennaPositions>) {
let content = fs::read_to_string(path).expect("Failed to read file");
let mut antenna_positions: AHashMap<char, AntennaPositions> = AHashMap::new();
let mut height = 0;
let mut width = 0;
for (y, line) in content.lines().enumerate() {
height += 1;
width = line.len() as i32;
for (x, ch) in line.chars().enumerate() {
if ch != '.' {
antenna_positions
.entry(ch)
.or_insert_with(Vec::new)
.push(Vec2D {
x: x as i32,
y: y as i32,
});
}
}
}
((width, height), antenna_positions.into_values().collect())
}
pub fn part1(width: i32, height: i32, antenna_positions: &[AntennaPositions]) -> u32 {
let mut antinodes = vec![0u64; height as usize];
let mut mark_antinode = |a: &Vec2D, b: &Vec2D| {
let x = 2 * b.x - a.x;
let y = 2 * b.y - a.y;
if x >= 0 && x < width && y >= 0 && y < height {
antinodes[y as usize] |= 1 << x;
}
};
for positions in antenna_positions {
for pair in positions.iter().combinations(2) {
mark_antinode(pair[0], pair[1]);
mark_antinode(pair[1], pair[0]);
}
}
antinodes.into_iter().map(|row| row.count_ones()).sum()
}
pub fn part2(width: i32, height: i32, antenna_positions: &[AntennaPositions]) -> u32 {
let mut antinodes = vec![0u64; height as usize];
let mut mark_antinode_and_harmonics = |a: &Vec2D, b: &Vec2D| {
let dx = b.x - a.x;
let dy = b.y - a.y;
let mut x = b.x;
let mut y = b.y;
while x >= 0 && x < width && y >= 0 && y < height {
antinodes[y as usize] |= 1 << x;
x += dx;
y += dy;
}
};
for positions in antenna_positions {
for pair in positions.iter().combinations(2) {
mark_antinode_and_harmonics(pair[0], pair[1]);
mark_antinode_and_harmonics(pair[1], pair[0]);
}
}
antinodes.into_iter().map(|row| row.count_ones()).sum()
}
fn main() {
let ((width, height), antenna_positions) =
read_map_dimensions_and_antenna_positions(&INPUT_FILE);
println!("part 1: {}", part1(width, height, &antenna_positions));
println!("part 2: {}", part2(width, height, &antenna_positions));
}
AngÄende körtider. NÀr dessa kommer vÀl under 1ms Àr det lite "sanning med modifikation" att man fÄ de tider som listas ovan. Dessa Àr vad som rapporteras av Rust benchmark-system "criterion" dÀr det kan se ut t.ex. sÄ hÀr
fn part1_benchmark(c: &mut Criterion) {
let equations = read_result_and_numbers(&INPUT_FILE);
c.bench_function("part 1", |b| b.iter(|| part1(black_box(&equations))));
}
Tiden man fÄ dÀr och tiden man fÄr om man istÀllet gör sÄ hÀr
let equations = read_result_and_numbers(&INPUT_FILE);
let start_part1 = Instant::now();
let result_part1 = part1(&equations);
let duration_part1 = start_part1.elapsed();
println!("part 1: {}", result_part1);
println!(
"Execution time for part 1: {:.3} ”s",
duration_part1.as_secs_f64() * 1_000_000.0
);
skiljer sig en hel del för vÀldigt korta körtider. Gör man ovan i en loop blir det samma sak, men första körningen Àr alltid klart lÄngsammare (och lÀr skilja Ànnu mer nÀr man kör t.ex. med JVM som har "warmup" time).
Tar det 10-tals ms ger dock bÄda metoderna vÀldigt snarlika resultat.
Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer
Problemet Àr inte bara att det tar tid att flytta data och starta en CUDA-kernel utan att det Àr vÀldigt fÄ berÀkningar per enhet data. Det Àndras inte om man har nÄgra miljoner gÄnger fler rader. Gör man sÄ fÄ berÀkningar Àr det bÀttre att stanna kvar i CPU om man ÀndÄ lÀst in alla data dÀr och har den nÀra i cache. Det gÄr ju att dela upp io, parsning och berÀkningar i lagom stora block och köra flera trÄdar. Med 16 kÀrnor testar man 1k element samtidigt.
Diskussionen handlade om datalayout. En rapport per register eller 1 element frÄn 64 olika rapporter per register. Jag trodde att skillnaden i hastighet skulle bli större eftersom jag inte kom pÄ nÄt bra sÀtt att undvika branching i inre loopen för del tvÄ med 64 olika rapporter per register. Skillnaden blev inte sÄ stor som jag trodde, 5us / 8us. SÄ jag hade fel, bÄda varianterna Àr effektiva. Det viktiga Àr hoppet frÄn 80us ner till 5us/8us. Men jag tycker nog att metoden att sprida ut en rapport Àr enklare och dessutom nÄgot snabbare.
Var kanske otydligt vad jag svarade pÄ, egentligen mer ett svar pÄ frÄgan du initialt svarade pÄ om det finns "bÀttre sÀtt". GPGPU Àr i.o.f.s. i grunden ocksÄ bara en form av SIMD.
Eller Nvidia gillar inte riktigt att man kallar det SIMD, de vill kalla det SIMT (Single Instruction Multiple Threads) dÄ GPUer (sÄ hÀr lÄngt) typiskt har rÀtt mycket mer flexibilitet i hur de kan göra scatter/gather pÄ olika GPU-trÄdar jÀmfört med scatter/gather som moderna CPUer har.
Att det Àr fÄ berÀkningar per trÄd Àr inte ett problem. TÀnk pÄ vad en GPU gör med grafik, den kör typiskt vÀldigt enkla berÀkningar "per trÄd" i vertex-shaders / fragment-shaders. Och antalet gÄnger man kör en specifik fragment-shader-trÄd blir ju typiskt en bra bit över 10 miljoner gÄnger per frame i ett modernt spel i 4k upplösning.
OvanpÄ det Àr just AoC problemen ofta rÀtt lÀtt att "vÀxa" per trÄd. Fallet vi diskuterar hÀr kan ju vÀxas genom att varje trÄd tar, sÀg 10, rader var. Statistiskt skulle de rimligen ocksÄ göra sÄ att genomsnittslÀngden pÄ antalet saker att hantera kommer fÄ mindre relativ spridning Àn nÀr man lÄter en trÄd ta en rad.
Ăr dĂ€rför frĂ€mst kostanden att starta kernel och fĂ„ tillbaka svaret som Ă€r flaskhals nĂ€r man gĂ„r en bra bit under 1ms i totaltid, framförallt nĂ€r man anvĂ€nder dGPU (iGPUer har rejĂ€lt mindre overhead).
Huvudvinsten med att köra med GPGPU jÀmfört med handknackad SIMD-kod Àr att den förra Àr vÀldigt lik "vanlig" kod, fast skriven för en invokering av en SIMD-lane. Den senare blir ofta "nÀra nog magi...".
FÄr testa att skiva nÄgon av problemen med Metal Compute eller CUDA
Men man fÄr nog vÀxa problemet för att fÄ ett vettig körtid som inte till >99 % bestÄr att launch/host-sync delen...
Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer
Dag: 8
SprÄk: Python
import numpy as np
from itertools import combinations
a = np.array([[c for c in l.strip()] for l in open("input08.txt", "r").readlines()])
p1 = set()
p2 = set()
def part1(x1, y1, x2, y2):
dx, dy = x2 - x1, y2 - y1
x1, y1 = x1 - dx, y1 - dy
x2, y2 = x2 + dx, y2 + dy
if 0 <= x1 < a.shape[0] and 0 <= y1 < a.shape[1]:
p1.add((x1, y1))
if 0 <= x2 < a.shape[0] and 0 <= y2 < a.shape[1]:
p1.add((x2, y2))
def part2(x1, y1, x2, y2):
dx, dy = x2 - x1, y2 - y1
while 0 <= x1 < a.shape[0] and 0 <= y1 < a.shape[1]:
p2.add((x1, y1))
x1, y1 = x1 - dx, y1 - dy
while 0 <= x2 < a.shape[0] and 0 <= y2 < a.shape[1]:
p2.add((x2, y2) )
x2, y2 = x2 + dx, y2 + dy
for c in np.unique(a[a != '.']):
for (x1, y1), (x2, y2) in combinations(zip(*np.where(a == c)), 2):
part1(x1, y1, x2, y2)
part2(x1, y1, x2, y2)
print(len(p1), len(p2))
InstÀmmer.
VÀldigt rörig beskrivning (för mig) dÀr jag först trodde det handlade om att rÀkna ut vilka antenner som kunde bilda ett par - utan att nÄgon annan antenn blockerade "line of sight" sÄ att sÀga.
InsÄg sedan att det inte alls handlade om detta och allt blev genast mycket enklare
Dag: 8
SprÄk: C#
Dag 8
Del 1: ~9ms
Del 2: ~1ms
KÀnner mig dÀremot smÄtt besegrad sÄ jag behövde googla pÄ formler för första gÄngen. Nackdelen med att jobba med "vanlig" utveckling
public class Solver
{
private struct Vector() : IEquatable<Vector>
{
public Vector(int x, int y) : this()
{
X = x;
Y = y;
}
public Vector(Vector v) : this()
{
X = v.X;
Y = v.Y;
}
public int X { get; private set; }
public int Y { get; private set; }
public static Vector operator - (Vector a, Vector b)
{
return new Vector(a.X - b.X, a.Y - b.Y);
}
public static Vector operator + (Vector a, Vector b)
{
return new Vector(a.X + b.X, a.Y + b.Y);
}
public static bool operator ==(Vector a, Vector b)
{
return a.X == b.X && a.Y == b.Y;
}
public static bool operator !=(Vector a, Vector b)
{
return a == b == false;
}
public bool Equals(Vector other)
{
return X == other.X && Y == other.Y;
}
public override bool Equals(object? obj)
{
return obj is Vector other && Equals(other);
}
public override int GetHashCode()
{
return HashCode.Combine(X, Y);
}
}
private struct Node(Vector position, string value)
{
public Vector Position { get; set; } = position;
public string Value { get; set; } = value;
}
private static Node[,] GetGrid(string input)
{
string[] lines = input.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries);
int height = lines.Length;
int width = lines[0].Length;
var grid = new Node[width, height];
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
grid[y, x] = new Node(new Vector(x, y), lines[y][x].ToString());
}
}
return grid;
}
private static Vector GetDistanceBetweenNodes(Node a, Node b)
{
return new Vector(a.Position - b.Position);
}
private static Node? GetNodeAtDistance(Vector distance, Node otherNode, Node[,] grid)
{
var pos = otherNode.Position + distance;
if (pos.X < 0 || pos.X >= grid.GetLength(1))
{
return null;
}
if (pos.Y < 0 || pos.Y >= grid.GetLength(0))
{
return null;
}
return grid[pos.Y, pos.X];
}
private static List<Node> GetAllNodesInDirectionTowardsB(Node a, Node b, Node[,] grid)
{
var delta = b.Position - a.Position;
var gcd = GCD(Math.Abs(delta.X), Math.Abs(delta.Y));
int stepX = delta.X / gcd;
int stepY = delta.Y / gcd;
int x = a.Position.X;
int y = a.Position.Y;
var currentNode = a;
var nodes = new List<Node>();
while (true)
{
nodes.Add(currentNode);
x += stepX;
y += stepY;
if ((x < 0 || x >= grid.GetLength(1)) ||
(y < 0 || y >= grid.GetLength(0)))
break;
currentNode = grid[y, x];
}
return nodes;
}
private static int GCD(int a, int b)
{
while (b != 0)
{
int temp = b;
b = a % b;
a = temp;
}
return a;
}
public static int Run_PartOne(string input)
{
var grid = GetGrid(input);
var distinctNodes = new Dictionary<string, List<Node>>();
var antiNodes = new List<Node>();
for (int y = 0; y < grid.GetLength(0); y++)
{
for (int x = 0; x < grid.GetLength(1); x++)
{
var node = grid[y, x];
if (node.Value == ".") continue;
if (!distinctNodes.TryAdd(node.Value, [node]))
{
distinctNodes[node.Value].Add(node);
}
}
}
foreach (var kvp in distinctNodes)
{
foreach (var node in kvp.Value)
{
foreach (var node2 in kvp.Value)
{
if (node.Position == node2.Position)
continue;
var distance = GetDistanceBetweenNodes(node, node2);
var node3 = GetNodeAtDistance(distance, node, grid);
if (node3.HasValue)
{
antiNodes.Add(node3.Value);
}
}
}
}
return antiNodes.DistinctBy(x => x.Position).Count();
}
public static int Run_PartTwo(string input)
{
var grid = GetGrid(input);
var distinctNodes = new Dictionary<string, List<Node>>();
var antiNodes = new List<Node>();
for (int y = 0; y < grid.GetLength(0); y++)
{
for (int x = 0; x < grid.GetLength(1); x++)
{
var node = grid[y, x];
if (node.Value == ".") continue;
if (!distinctNodes.TryAdd(node.Value, [node]))
{
distinctNodes[node.Value].Add(node);
}
}
}
foreach (var kvp in distinctNodes)
{
foreach (var node in kvp.Value)
{
foreach (var node2 in kvp.Value)
{
if (node.Position == node2.Position)
continue;
var nodes = GetAllNodesInDirectionTowardsB(node, node2, grid);
foreach (var node3 in nodes)
{
antiNodes.Add(node3);
}
}
}
}
return antiNodes.DistinctBy(x => x.Position).Count();
}
}
HÀr Àr ett exempel nÀr körtiden, som jag gissar du mÀtte med "Stopwatch"?, inte alls blir rÀtt. Det Àr betydligt snabbare om man tar bort "warmup". .NET verkar ha lÄngt kortare warmup jÀmfört med typiska JVM:er, redan körningen av part 2 verkar vara "ljummen" dÄ det rimligen borde ta lÀngre tid Àn part 1.
Kör man i en loop fÄr jag ca 90”s i del 1 och ca 310”s i del 2 med din lösning pÄ en M3 och .NET core 8.
I första varvet fÄr man ~5ms och 1ms pÄ samma dator...
Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer
Dag 8, C#
Ganska rÀttframt idag, helgproblemen brukar ju i regel vara lite mer att bita i.
using System.Diagnostics;
Stopwatch sw = Stopwatch.StartNew();
var lines = File.ReadAllLines("input.txt");
(HashSet<(int x, int y)> part1, HashSet<(int x, int y)> part2, int maxpos) = ([], [], lines.Length);
Dictionary<(int x, int y), char> grid = [];
for (int x = 0; x < lines.Length; x++)
for (int y = 0; y < lines[x].Length; y++)
if (lines[x][y] != '.')
grid[(x, y)] = lines[x][y];
foreach (var pos in grid)
{
foreach (var antenna in grid.Where(x => x.Key != pos.Key && x.Value == pos.Value))
{
if (getAnte(out (int x, int y) ante, pos.Key, antenna.Key, maxpos))
part1.Add(ante);
part2.UnionWith(getResonance(pos.Key, antenna.Key, maxpos));
}
}
sw.Stop();
Console.WriteLine("Part 1: " + part1.Count);
Console.WriteLine("Part 2: " + part2.Count);
Console.WriteLine("Total time: " + sw.Elapsed);
static bool inRange((int x, int y) pos, int max) => (pos.x < max && pos.y < max && pos.x >= 0 && pos.y >= 0);
static (int x, int y) calculateDiff((int x, int y) me, (int x, int y) them)
{
(int x, int y) diff = (Math.Abs(me.x - them.x), Math.Abs(me.y - them.y));
if (them.x > me.x)
diff.x = -1 * diff.x;
if (them.y > me.y)
diff.y = -1 * diff.y;
return diff;
}
static bool getAnte(out (int x, int y) val, (int x, int y) me, (int x, int y) them, int max)
{
var diff = calculateDiff(me, them);
val = (me.x + diff.x, me.y + diff.y);
return inRange(val, max);
}
static IEnumerable<(int x, int y)> getResonance((int x, int y) me, (int x, int y) them, int max)
{
HashSet<(int x, int y)> retval = [];
var diff = calculateDiff(me, them);
while (inRange(me, max))
{
retval.Add(me);
me = (me.x + diff.x, me.y + diff.y);
}
return retval;
}
The power of GNU compiles you!
"Often statistics are used as a drunken man uses lampposts -- for support rather than illumination."
Spelnyheter frÄn FZ
Copyright © 1999â2025 Geeks AB. Allt innehĂ„ll tillhör Geeks AB.
Citering Àr tillÄten om kÀllan anges.