#AoC 2022 | Day 8 | Solutions & Spoilers

1888 messages ยท Page 2 of 2 (latest)

surreal tusk
#

a little chonky

orchid adder
#

cubic cost ๐Ÿ˜”

midnight vortex
#

๐Ÿฅน

surreal tusk
#

from PIL import Image
import numpy as np


def count_and_spin(view_list, tree_matrix, tree_counter):
    for y, row in enumerate(tree_matrix):
        height = -1
        for x, tree in enumerate(row):
            if tree[1]:
                if tree[0] > height:
                    height = tree[0]
            elif not tree[1] and tree[0] > height:
                tree_counter += 1
                height = tree[0]
                tree_matrix[y][x][1] = True
        for y2, row2 in enumerate(tree_matrix):
            for x2, tree2 in enumerate(row2):
                view_count = 1
                height2 = tree2[0]
                view_line = (j for i, j in enumerate(row2) if i > x2)
                for t in view_line:
                    if t[0] < height2:
                        view_count += 1
                    else:
                        break
                view_list.append(view_count)
                tree_matrix[y2][x2][2] *= view_count
    tree_matrix = [list(r) for r in zip(*tree_matrix[::-1])]
    return view_list, tree_matrix, tree_counter


def visualiser(tree_matrix, height_matrix):
    new_matrix = [[[0, 255, 0] if y[1] else [255, 0, 0] for y in row] for row in tree_matrix]
    image_arr = np.array(new_matrix)
    result = Image.fromarray(image_arr.astype(np.uint8))
    result.save("visual.png", "png")


with open("input_8.txt", "r+")as input_file:
    tree_counter = 0
    tree_matrix = [[[int(y), False, 1] for y in line.strip('\n')] for line in input_file]
    view_list = []
    for i in range(4):
        view_list, tree_matrix, tree_counter = count_and_spin(view_list, tree_matrix, tree_counter)
    print(tree_counter)
    height_matrix = [[x[2] for x in row] for row in tree_matrix]
    visualiser(tree_matrix, height_matrix)
    print(max([height for row in height_matrix for height in row]))```
midnight vortex
#

did i do this

#

i am feeling so bad

surreal tusk
#

It's somethin very inconsequential and silly. I've added that view_list to find what the maximum view along any axis is and it's only 61, and yet the final number is absolutely insane. It shouldn't be larger than eight digits at the absolute outside

orchid adder
#

which is suspicious

#

seems you multiply the same thing a bunch of times

swift yoke
midnight vortex
#

oh

#

lmao

frail charm
#

Day 8 had me using numpy

#

probably badly

midnight vortex
#

day 8 gave me the opportunity to use numpy

midnight vortex
#

i refused

#

maybe because i am not versatile with numpy

#

i would have to sit one day and experiment with every thing it has

surreal tusk
#

like, working through what's happening logically it seems to be right. It sets the view_count to 1 (the minimum), gets the height of the tree that it's looking at, then constructs a generator that contains the sight_line in one direction. Then it iterates through that generator and adds 1 to the view_count for every tree shorter than the tree, breaking when it reaches an equal or taller tree. then it takes that counter and stores the product of it and the current score in the variable each tree has which tracks its scenic score. the maximum value of the counter on any loop is 61.

#

what should happen is that each tree should come out with a number somewhere between 1 and 61*61*61*61

#

but somewhere it goes funny

swift yoke
pseudo mirage
#

did anyone get a super round looking answer. like with 4 trailing zeros

pseudo mirage
#

i thought i got something wrong because it was so round looking so i didn't submit it immediately lol

midnight vortex
#

everyone's puzzle input length was 99 right

midnight vortex
#

hm

drowsy forum
#

Definitely remember seeing a length of 99

#

Just checked, can confirm it was 99x99

midnight vortex
#

okay

#

and the score for one direction cant be >= 100

midnight vortex
pseudo mirage
#

ya

swift yoke
surreal tusk
#

ok yeah, that's definitely the problem. It's adding the view_count in way more times than it should and I am not sure why

midnight vortex
#

for an answer with 4 trailing zeroes there should be 4 factors of 10

#

we are multiplying 4 scores

#

is it possible ๐Ÿค”

#

should be , i am just wondering lol

#

nvm

#

i figured ot

swift yoke
midnight vortex
#

its possible

#

ye

#

i dumb

swift yoke
#

lol

#

p1

z=()=>document.body.innerText.split(/\n/).map(r=>[...r]);
w=(m)=>m[0].map((_,i)=>m.map(r=>r[98-i]));s=z();f=z();
for(i=0;i++<4;){
    for(y in f)for(x in f[y]) if(f[y].slice(0,x).every(v=>+v<+f[y][x])) s[y][x]='x';
    f=w(f);s=w(s);
}(""+s).split('x').length-1;

p2

f=document.body.innerText.split(/\n/).map(r=>[...r]);
w=(m)=>m[0].map((_,i)=>m.map(r=>r[98-i]));s=f.map(r=>r.map(c=>1));
for(i=0;i++<4;){
    for(y in f)for(x in f[y])s[y][x]*=(1+f[y].slice(+x+1).findIndex(v=>v>=f[y][x]))||98-x
    f=w(f);s=w(s);
}Math.max(...(""+s).split(','));
#

see if that helps the python golf

#

Source : reddit

surreal tusk
#

Ah dang. I tried just making them sets and crossing my fingers but obviously that's not going to work

#

hmmmmmmmmmm

midnight vortex
pseudo mirage
#

not necessarily

orchid adder
pseudo mirage
midnight vortex
#

oh right

#

2x5 factor split

#

iwas just overthinking lol

orchid adder
#

that would only give 2 trailing zeroes though, right?

#

not 4

pseudo mirage
#

oh and the max is 99. so yeah more had to have trailing zeros

drowsy forum
#

98

pseudo mirage
#

i could find the actual ๐Ÿค”

midnight vortex
#

2,50,50,8 types

midnight vortex
#

wait that would leak answer

#

dont dew it

orchid adder
#

50, 50, 25, 16 would be possible

pseudo mirage
#

there are multiple inputs though

orchid adder
#

and give 6 zeroes

midnight vortex
#

yea

surreal tusk
#

isn't everyone's puzzle input unique?

pseudo mirage
#

no, there are a few for each day

orchid adder
pseudo mirage
#

though i assume for something like today it'd be really easy to just generate more

orchid adder
#

I don't think 7 zeroes is possible pithink

surreal tusk
#

Yeah I assumed they generated them to a rule

pseudo mirage
#

anyways, it's 10 20 48 50

midnight vortex
#

dang

orchid adder
#

you would need 25, 25, 25, 5 and then multiply something with 2 7 times

#

which I'm pretty sure would violate the input size we have

surreal tusk
#

Ah, I am frustrate. I'm going to actually take a break today and not get obsessed like I did yesterday

fleet cloud
#

3 trailing

tribal brook
#

Ugh, today is making a big dent in my goal of doing each day in under 1ms...

drowsy forum
#

Wish I could code that fast

midnight vortex
#

rust ๐Ÿ‘€

tribal brook
#

Rust, I don't think I could get that fast without manual bytecode hacking in python

#

Would Cython count as python? grannylaugh

spare raven
#

Ah no. What went wrong?

spare raven
#

Oh right. Yeah it becomes a bit simpler when you use vectors.

midnight vortex
#

vectors ๐Ÿ‘€

#

numpy related?

tribal brook
#

Either way

spare raven
pseudo mirage
#

complex numbers ๐Ÿ‘€

spare raven
#

So if you're add position and you want to move one place to the right, you do ```py
position = add(position, (0, 1))

midnight vortex
#

what is the purpose of adding two coordinates ๐Ÿ‘€

spare raven
midnight vortex
#

oh so like math vectors

spare raven
swift yoke
pseudo mirage
misty peak
#

ahhhhh well i second guessed myself so hard on the second part

spare raven
tribal brook
#

I like APL for vectoring

pseudo mirage
misty peak
#

i was like "it can't be that big right?"

pseudo mirage
#

yeah it's only 99, so even n^3 is pretty ok

tired bramble
#

I have seen some pretty lovely APL solutions for today

spare raven
tired bramble
#

I may write my own

misty peak
#

what's APL solutions

tired bramble
#

this is pretty much the ideal problem for APL

spare raven
tired bramble
#

manipulate a 2d matrix

swift yoke
midnight vortex
misty peak
#

ah

#

numpy is a life-saver for today

midnight vortex
#

but still you will have like 4 loops for calculating the 4 scores right

swift yoke
#

why use numpy when you can use 7 loops ๐Ÿ˜Ž

midnight vortex
#

interesting

#

i need to try it out

pseudo mirage
midnight vortex
#

so you will be going in all 4 directions simultaneously or something

spare raven
midnight vortex
#

ah i see

#

can i see your add function

misty peak
#

09818
anyhow i still don't understand how 9 can see 1 but oh well

spare raven
#

Yeah, it's near the top of my solution here @midnight vortex

pseudo mirage
midnight vortex
#

yeah thats what i thought

spare raven
misty peak
#

had to re-read the prompt to realise it's intended

#

i thought i had a bug or something

pseudo mirage
#

because otherwise it's a sad trig problem ๐Ÿ˜”

#

oh, i see what you're saying, nvm

dull vine
#

walruses not allowed in the square brackets (unless it's a 3.11 thing?)

eternal spindle
surreal tusk
#

oh goodness, I'm such a numpty. The whole day 2 for loop was running inside the day 1 one react_why

dull vine
#

ah so it is 3.11

misty peak
#

works in 3.10 too?

surreal tideBOT
#

@eternal spindle :white_check_mark: Your 3.10 eval job has completed with return code 0.

b 1
misty peak
#

yeah

dull vine
#

wait a sec

eternal spindle
#

nope, ok in 3.10 as well

dull vine
#

what version is my python on then

misty peak
#

crack

eternal spindle
#

you're probablythinking it does not work in a comprehension. seems fine in an index.

dull vine
#

my vscode is showing red squiggles

#

i have my interpreter set to 3.11

#

must be a bug

hardy iron
#

for _ in range(9801) can be changed to for _ in[0]*9801

#

oh wait nvm you actually use _

cold lintel
#

there were found lots of false positives for walrus operator in pylance.

surreal tusk
#

That's not the right answer. Curiously, it's the right answer for someone else; you might be logged in to the wrong account or just unlucky. In any case, you need to be using your puzzle input.

#

=_=

rocky surge
dull vine
#

but this one is a false negative

#

did they overcompensate or something?

dull vine
#

ye i know now

rocky surge
dull vine
#

pylance initially convinced me otherwise

#

but apparently running the code actually works

surreal tusk
#

At least I know I must be in the right ballpark.

rocky surge
#

not a big fan of my scenic score calculator, but would appreciate feedback

def scenic_score_calculator(tree_grid, location):
    left, right, up, down = 1,1,1,1
    row, col = location[0], location[1]
    tree_val = tree_grid[row][col]
    while col-left>0:
        if tree_grid[row][col-left] >= tree_val:
            break
        left+=1
    while col+right<98:
        if tree_grid[row][col+right] >= tree_val:
            break
        right+=1
    while row-up>0:
        if tree_grid[row-up][col] >= tree_val:
            break
        up+=1
    while row+down<98:
        if tree_grid[row+down][col] >= tree_val:
            break
        down+=1
    if col == 0:
        left = 0
    if col == 98:
        right = 0
    if row == 0:
        up = 0
    if row == 98:
        down = 0
    return up*down*left*right
#

just feels very repetitive :/

proud fog
#

here's a hint

point = (x, y)
for direction in ((1,0), (0,1), (-1, 0), (0, -1)):
    # Add the direction to the point
    point = ...

Now do what you want

rocky surge
#

vector addition is pretty straightforward with numpy, but what's a one-liner in python?
new_p = [p[0]+d[0], p[1]+d[1]]?

proud fog
#

sure that works

#

or tuple(sum(n) for n in zip(point, direction))

#

!e

point = (1, 2)
dirn = (3,4)
print(tuple(sum(n) for n in zip(point, dirn)))
surreal tideBOT
#

@proud fog :white_check_mark: Your 3.11 eval job has completed with return code 0.

(4, 6)
pseudo mirage
#

you could also use complex numbers as 2d vectors

#

those can just be added

proud fog
rocky surge
#

I'm guessing that being unchanged by %98 is a check that would work in all 4 cases

proud fog
rocky surge
proud fog
#

now I am too lazy to rewrite my soln tho

#

I'll keep em in mind for future soln tho

surreal tusk
#

Woohoo, got it! It wasn't anything wrong with the code after I fixed that for loop, I just had slightly misread the question

proud fog
#

# these nested for loops *should* retrieve the answer for part 2
should ๐Ÿ˜

proud fog
surreal tusk
#

I am just working on doing a visualiser for the second part

orchid adder
proud fog
tribal brook
#

Yeah my first run was like 600mus

orchid adder
orchid adder
surreal tusk
orchid adder
#

Other than that I only do 4 passes over the matrix

#

amortized O(1) work per cell

tribal brook
#

I could make my solution faster by benchmarking on my desktop weSmart

surreal tusk
#

mine runs in 377 ms once I turn the visualiser off, which isn't bad at all as I didn't really optimise it

tribal brook
# surreal tusk

Oh I accidentally made something like that while debugging my code too! just with character output ๐Ÿ˜„

#

is that the shell time command?

alpine rock
#

yeah

#

time cargo run

orchid adder
alpine rock
#

Yep

tribal brook
#

yeah that's not very accurate

swift yoke
alpine rock
tribal brook
#

try something like criterion or hyperfine for benchmarking

swift yoke
alpine rock
swift yoke
#

why you posting this in d8?

alpine rock
#

Because I'm dumb

tribal brook
#

so you don't suffer time loss from startup or cold caches or system behavior... hehe

alpine rock
#

And it's the only one I've done in Rust

orchid adder
surreal tusk
orchid adder
#

(what even was d6?)

swift yoke
#

d6 for me

#

@alpine rock

alpine rock
#

oof

tribal brook
#

very helpful thank you

alpine rock
#

Get it so small it's all zeros

orchid adder
#

oh d6 was the boring moving window one

swift yoke
alpine rock
orchid adder
#

where I got disappointed by rust const generics

tribal brook
#

why, didn't want to use nightly?

swift yoke
orchid adder
quasi jasper
#

Just starting the problem today, is it wrong to brute force the perimeter or is there a "smarter" way?

peri_count = len(np_forest[0, 0:]) + len(np_forest[-1, 0:]) + len(np_forest[1:-1, -1]) + len(np_forest[1:-1, 0])
tribal brook
#

aha yeah

#

something about specialization or something

surreal tusk
quasi jasper
#

Yeah, since every tree along the perimeter is visible, I was just counting the length of the perimeter

surreal tusk
#

My code works by scanning each row, then rotating the matrix and doing it again (and again and again), and to get the perimeter I just set the initial height it checks against to -1 ๐Ÿคทโ€โ™€๏ธ

surreal tusk
#

every other way of doing it seemed excessively brain-achey to me

orchid adder
#

and even enabling it didn't help me so much

#

I have no idea what that error is saying

tribal brook
#

you need to constrain N so it doesn't overflow with the addition

#

the syntax is very funny

orchid adder
#

wait, this is how you constrain the value of N?

#

that's wild

tribal brook
#

yeah, if the type in the where clause is valid then the compiler knows N is not 255

#

which, lol

orchid adder
#

no it's worrying about an usize overflowing

#

when the value is really 4 or 14

#

it's great

tribal brook
#

anyways I did 2 solutions for day 6, one with normal window checking and another with a rolling hash

orchid adder
#

I sadly dropped the const generics completely

#

(and yes d6 tangent in d8 thread)

pseudo mirage
#

you could save a constant amount of memory by not making the array 256 long

swift yoke
#

rust seems like a pain to write. i hope transpilers get good enough to just auto convert what I write in python to rust to gimme blazing speeds

tribal brook
#

you should just pass your code to a GPT and tell it to make it faster
same level of commitment and similar results

orchid adder
#

(I write solutions on my phone even)

orchid adder
#

for an object that's created exactly two times ๐Ÿ˜›

#

back on topic, writing an O(n*m) solution for today was fun

pseudo mirage
#

wouldn't that just be like writing rust lol

orchid adder
#

transpilers from python to a statically typed compiled language is always lossy to some extent, unless maybe you do something like cython

#

but at that point you've already left python

surreal tusk
#

I like python, it's cosy blobcatcomfy

#

Don't want to go outside into the scary cold

drowsy forum
drowsy forum
#

lol I read it out of context and was like "wut?"

proud fog
#

lol

#

even out of context its not that bad of a statement ๐Ÿ˜›

drowsy forum
#

True, true. I probably read it at a funny angle ๐Ÿ˜›

proud fog
#

I wonder if they managed to shorten their code

rancid hearth
#

Used numpy today, but just to do easy vertical slices. Otherwise I just used iteration over the numpy arrays. Knowing little about numpy it feels wrong but idk

coarse nova
#

Who can see anyway to improve this? I already see a change for given a base value to tlt1

safe path
sacred star
#

is there any clever trick today or just looping over ?

def p1(day_input):

    for i in range(1, r-1):
        for j in range(1, c-1):
            tree = int(day_input[i][j])

            conditions = [
                int(max(day_input[i][:j], key=lambda x: int(x))) < tree,
                int(max(day_input[i][j+1:], key=lambda x: int(x))) < tree,
        ]

            up = True
            for ii in range(0, i):
                if int(day_input[ii][j]) >= tree:
                    up = False
                    break
            down = True
            for ii in range(i+1, r):
                if int(day_input[ii][j]) >= tree:
                    down = False
                    break
            conditions.append(up or down)
            if any(conditions):
                visible_trees +=1
    return visible_trees


safe path
pseudo mirage
rancid hearth
#

I meant the reason I went numpy and not Python list[list[int]] is for that sweet vertical slice

pseudo mirage
#

right

#

@orchid adder you wrote your own benchmarking thing right? how did you get the compiler to not optimize away your function calls?

pseudo mirage
rancid hearth
#

that's the part I feel like there might have been a way in numpy if I knew how

#

not a huge deal though

pseudo mirage
#

๐Ÿ˜” but it's the whole point of numpy

rancid hearth
#

i tried, I really did

#

but the API for numpy is so huge and Googling the precise function you need to do something can be really difficult

#

so I gave up and did it the scrub way :) will be cool to read better ways to do it after the fact

#

but I have to refactor my code to add classes, brb :)

eternal spindle
#

ok, I think this'll do for now. ready for the next day.

# create an empty class
class Tree(namedtuple('Tree', 'x y h')): v=0;s=1

#read the data into a dictionary
data = [*map(list,open(filename).read().splitlines())]
forest = {(x,y):Tree(x,y,h) for y,row in enumerate(data) for x,h in enumerate(row)}
H,W = max(forest)

# loop over each tree
for t in forest.values():
    # loop over the 4 directions
    for z,d in [
                (list(zip( range(0,t.x)[::-1], repeat(t.y))),         t.x),
                (list(zip(   range(t.x+1,W+1), repeat(t.y))),         W-t.x),
                (list(zip(        repeat(t.x), range(0,t.y)[::-1])),  t.y),
                (list(zip(        repeat(t.x), range(t.y+1,H+1))),    H-t.y)]:
        t.v |= all(t.h>forest[x,y].h for x,y in z)
        t.s *= next((c for c,e in enumerate(z,1) if t.h<=forest[e].h),d)

print('part1', sum(t.v for t in forest.values()))
print('part2', max(forest.values(),key=lambda t:t.s).s)
alpine rock
#

Tom! You're here!

alpine rock
viscid badger
#

interesting how the height of the trees is distributed. i had expected differently. Yet it makes sense when you think about it, higher trees, are more likely in the middle of the forest.
a white pixel = height 9; black = height 0

rancid hearth
rancid hearth
#

Btw Jupyter on iPhone kills battery life like no tomorrow - I have learned

alpine rock
rancid hearth
#

Could even pip install more-itertools and parse into it

alpine rock
#

Fancy fancy

#

Can it push to GitHub?

#

Or do you have to retype everything on the computer?

spare raven
rancid hearth
viscid badger
rancid hearth
#

I actually had to type first on my work computer, then again on my phone to actually run against my input

alpine rock
#

Wait no don't actually....

rancid hearth
#

And now I got to type again onto my actual computer

alpine rock
#

Just email it to yourself...

rancid hearth
#

Canโ€™t really do that

alpine rock
#

y

#

pastebin
gist

rancid hearth
#

Donโ€™t want to go into it too much, but work is strict about that stuff

alpine rock
#

Where there's a will lazy developer, there's a way

eternal spindle
rancid hearth
#

Oh yah Iโ€™m also adding classes so I can assert every detail about the test input on the description

eternal spindle
orchid adder
#

(? is the best verb)

lunar ore
#

std::intrinsics::black_box is also useful

red harness
#

and on a log scale:

wet badger
#

What testing code?

latent elbow
#

pandas it is

df = pd.read_csv("day_8.input", header=None, squeeze=True).apply(list).apply(pd.Series).astype(int)

print(pd.Series([-1]).pipe(lambda s: df.apply(lambda c: s.__setitem__(0, -1) or c.apply(lambda n: s.__setitem__(0, s[0] + 1) or pd.concat([df.iloc[s[0], :c.name], df.iloc[s[0], c.name+1:], df.iloc[:s[0], c.name], df.iloc[s[0]+1:, c.name]], axis=1).fillna(-1).lt(n).all().any()))).sum().sum())

print(pd.Series([-1, lambda s, n: len(s) if not s.ge(n).any() else s[s.ge(n).argmax():].diff().iloc[1:].le(0).argmax() + s.ge(n).argmax() + 1 if s[s.ge(n).argmax():].diff().iloc[1:].le(0).any() else len(s)]).pipe(lambda s: df.apply(lambda c: s.__setitem__(0, -1) or c.apply(lambda n: s.__setitem__(0, s[0] + 1) or pd.Series([s[1](df.iloc[s[0], :c.name][::-1], n), s[1](df.iloc[s[0], c.name+1:], n), s[1](df.iloc[:s[0], c.name][::-1], n), s[1](df.iloc[s[0]+1:, c.name], n)]).prod()))).max().max())
surreal tideBOT
#

:incoming_envelope: :ok_hand: applied mute to @faint helm until <t:1670538202:f> (10 minutes) (reason: newlines rule: sent 108 newlines in 10s).

The <@&831776746206265384> have been alerted for review.

topaz gull
#

!unmute 712372740509663294

inland wave
#

!unmute 712372740509663294

surreal tideBOT
#

:incoming_envelope: :ok_hand: pardoned infraction mute for @faint helm.

#

:incoming_envelope: :ok_hand: pardoned infraction mute for @faint helm.

inland wave
#

!paste

surreal tideBOT
#

Pasting large amounts of code

If your code is too long to fit in a codeblock in Discord, you can paste your code here:
https://paste.pythondiscord.com/

After pasting your code, save it by clicking the floppy disk icon in the top right, or by typing ctrl + S. After doing that, the URL should change. Copy the URL and post it here so others can see it.

jovial orbit
#

... I won't unmute....

inland wave
#

Do it.

#

@topaz gull I'll kick your behind to yesterday

jovial orbit
#

I've already gotten snucky'd once today >_>

topaz gull
#

y'all are getting beat by the worst typer on staff

#

embarassing tbh

jovial orbit
#

@faint helm Try using our pastebin above for big chunks of code like that~

faint helm
#

algorithm: Concert to hashset (dict) <Position (complex), height>.
For part1, Get the kets of all rows and columns, for k*4 rows. for each vec, only keep v[0] and rows where height > highest.
For part2: for each positon (key) count in each 4 directions until reach place outside of forest or blocking tree.

inland wave
pseudo mirage
pseudo mirage
orchid adder
#

I didn't really intentionally do it, but it should make sure the thing is not optimized away

pseudo mirage
#

how?

orchid adder
#

how could it optimize it away?

#

it re-reads the input as well

pseudo mirage
orchid adder
#

for all it knows the input is completely different and could cause something weird to happen

#

it's not a pure function

pseudo mirage
orchid adder
#

I just read from file

#

and time the read+basic parsing and the actual logic separately

pseudo mirage
#

I c I c

lunar ore
hazy fog
pseudo mirage
#

black box was made stable though I thought

hazy fog
#

right right

orchid adder
#

err, I found some rust weirdness

#

so this is fine

    let thing = 42;

    macro_rules! bleh {
        () => {
            thing
        };
    }

    let res = bleh!();
eternal spindle
orchid adder
eternal spindle
orchid adder
#

and that kinda breaks my mental model of macros

hazy fog
#

is that a macro hygiene issue? decl macros always confuse me.

latent elbow
orchid adder
latent elbow
#

checks

eternal spindle
# latent elbow no, did they find a vectorized way?
import numpy as np

grid = np.array([list(x.strip()) for x in open('in.txt')], int)
part1 = np.zeros_like(grid, int)
part2 = np.ones_like(grid, int)

for _ in range(4):
    for x,y in np.ndindex(grid.shape):   
            lower = [t < grid[x,y] for t in grid[x,y+1:]]
    
            part1[x,y] |= all(lower)
            part2[x,y] *= next((i+1 for i,t in enumerate(lower) if ~t), len(lower))

    grid, part1, part2 = map(np.rot90, [grid, part1, part2])

print(part1.sum(), part2.max())
latent elbow
#

oh nice, thanks

orchid adder
#

that's...cubic?

latent elbow
#

somewhat vectorized but not fully

eternal spindle
#

4HbQ posted some of my favorite solutions last year using numpy, and I swore I'd spend more time learning it, instead I picked up pyspark which is much less useful here.

pseudo mirage
eternal spindle
#

his solution is the opposite of mine, he did 4 rotations for each tree, I did for each tree, 4 ranges. but similar otherwise.

hazy fog
#

tlborm is great

#

i think that addresses the issue you're having

orchid adder
#

does it? pithink

#

why does the first one work?

hazy fog
#

i guess it's because the definition is part of the macro's syntax context?

orchid adder
#

so everything before the macro definition is included and everything after isn't?

#

I would have expected it to be like that but around the usage

hazy fog
#

me too. i'm not too versed in how declarative macros work. i'm somewhat more used to proc macros.

orchid adder
#

that's an interesting choice to say the least

#

15-20% speed increase from generating code with macros to remove if statements in loops

#

neat

pseudo mirage
#

huh. how does that work

orchid adder
#

I have a sweep I need to to 4 times

#

one from each direction

#

but re-using code across vertical and horizontal sweeps is kinda hard

#

I need to swap the i and j index at one point

#

the macro called ended up as this, which is kinda disturbing

pseudo mirage
#

oh I see, that's why you were messing around with identifiers

orchid adder
#

right

#

I could probably have a boolean whether to transpose, rather than this thing

#

a wrapper macro maybe ๐Ÿฅด

#

lol, apparently you can do this

#

oh, the @ isn't special?

pseudo mirage
#

no i think that's a feature

orchid adder
#

so this is a thing you can do

#

that's...intriguing

#

oh no...

#

I've discovered great powers

eternal spindle
# orchid adder the macro called ended up as this, which is kinda disturbing

I did something like this by defining the ranges first, and then looping over the 4 ranges

for t in forest.values():
    for z,d in [
            (list(zip( range(0,t.x)[::-1], repeat(t.y))),         t.x),
            (list(zip(   range(t.x+1,W+1), repeat(t.y))),         W-t.x),
            (list(zip(        repeat(t.x), range(0,t.y)[::-1])),  t.y),
            (list(zip(        repeat(t.x), range(t.y+1,H+1))),    H-t.y)]:
        t.v |= all(t.h>forest[x,y].h for x,y in z)
        t.s *= next((c for c,e in enumerate(z,1) if t.h<=forest[e].h),d)
eternal spindle
orchid adder
#

it doesn't, but I had no idea you could do that with rust macros

eternal spindle
#

ah, you're doing rust. lol. nevermind.

#

๐Ÿฆ€

orchid adder
#

I would have guessed the screenshots would be obvious ๐Ÿ˜›

eternal spindle
#

in meetings so not carefully reading, not sure why I thought it would be python on this server. ๐Ÿ˜„

mellow breach
quick fiber
#
table_proj = lambda table, x: table[x[1]][x[0]]
line_forward = lambda x: (x[0] + 1, x[1])
line_backward = lambda x: (x[0] - 1, x[1])
col_forward = lambda x: (x[0], x[1] + 1)
col_backward = lambda x: (x[0], x[1] - 1)

class Part1Matcher():
    def __init__(self):
        self.max = -1

    def __call__(self, v):
        coord, value = v
        if self.max < value:
            self.max = value
            return coord

def generator(cont, from_it, to_it, proj_func, next_func):
    curr_it = from_it
    end_it = next_func(to_it)
    while curr_it != end_it:
        yield proj_func(cont, curr_it)
        curr_it = next_func(curr_it)
#


def part1(content):
    def update_visibilities(table, results):
        for coord in results:
            visibility_table[coord[1]][coord[0]] = True

    def find_visibles(gen):
        matcher = Part1Matcher()
        return [r for n in gen if (r := matcher(n)) is not None]

    lines = [list(map(int, [*line])) for line in content.strip().split("\n")]

    row_count = len(lines)
    col_count = len(lines[0])
    visibility_table = [[False]*col_count for _ in range(row_count)]]
    table_and_index_proj = lambda table, x: (x, table_proj(table, x))

    for i, _ in enumerate(lines):
        col_gen = generator(lines, (0, i), (col_count-1, i), table_and_index_proj, line_forward)
        update_visibilities(visibility_table, find_visibles(col_gen))

        r_col_gen = generator(lines, (col_count-1, i), (0, i), table_and_index_proj, line_backward)
        update_visibilities(visibility_table, find_visibles(r_col_gen))

    for i, _ in enumerate(lines[0]):
        col_gen = generator(lines, (i, 0), (i, row_count-1), table_and_index_proj, col_forward)
        update_visibilities(visibility_table, find_visibles(col_gen))

        r_col_gen = generator(lines, (i, row_count-1), (i, i), table_and_index_proj, col_backward)
        update_visibilities(visibility_table, find_visibles(r_col_gen))

    return sum([cell is True for row in visibility_table for cell in row])
orchid adder
#

the unsafe version of this line looks hilarious

//scenic[$ix_i][$ix_j] *= ($j as i32 - ix).abs();
*unsafe { scenic.get_unchecked_mut($ix_i).get_unchecked_mut($ix_j) } *= ($j as i32 - ix).abs();
quick fiber
#

thoughs ?

orchid adder
#

err

visibility_table = []*len(lines)
#

that doesn't do anything useful

quick fiber
#

oh right

orchid adder
#

!e

print([]*1000000000)
surreal tideBOT
#

@orchid adder :white_check_mark: Your 3.11 eval job has completed with return code 0.

[]
orchid adder
#

I would change

    visibility_table = []
    for i in range(len(lines)):
        visibility_table.append([False]*len(lines[0]))
```into
```py
    visibility_table = [[False]*col_count for _ in range(row_count)]]
quick fiber
#

tyvm that felt clumsy when I wrote it

orchid adder
#

what's the point of [*line] here?

    lines = [list(map(int, [*line])) for line in content.strip().split("\n")]
quick fiber
#

well none

#

got tired or refactoring mistakes

#

oh well no

eternal spindle
# quick fiber well none

actually for 2 reasons. (1) the string is iterable, (2) you don't need to convert them to ints.

#

'7'>'2' etc

quick fiber
#

to decompose the string into single char, and then cast them to int

orchid adder
#

you can just put line

quick fiber
#

ok

eternal spindle
#
lines = content.splitlines()
orchid adder
#

also these enumerate lines are weird

for i, _ in enumerate(lines):
#

enumerating and not using the value

#

this one could be just

for i in range(row_count):
quick fiber
orchid adder
#

similar for the one with lines[0]

eternal spindle
orchid adder
#

I used the byte values and kinda forgot I used the byte values

#

worked fine though

eternal spindle
#

Did anyone else store the trees in a dictionary, or is that more of me being me?

orchid adder
#

what does that buy you?

quick fiber
#

what for xelf ? to avoid wondering is the tree has already be deemed visible ?

orchid adder
#

granted, I suspect I didn't do the same approach as most people here

quick fiber
#

well my algo was already shit so I figured, why try to optimize

orchid adder
#

I got the idea for how to do it in O(n*m) pretty quickly, so I just implemented that

eternal spindle
#

I dunno, just a habit of converting 2D arrays into a dictionary, frequently makes boundary checks easier. in retrospect I could have just kept it as a 2d list.

quick fiber
#

n the w of the array or the number of elems ?

orchid adder
#

n=height m=width

#

so O(number_of_elements)

quick fiber
#

yeah mine is supposedly O(N) too

#

it doesn't say how many times N tho lol

orchid adder
#

oh, part 1 is pretty easy to do in O(n*m)

#

part 2 is the trickier one

quick fiber
#

didn't take a look yet

#

I got lost making a generator with projection and everything

#

couldn't be bothered by part2

eternal spindle
turbid wigeon
eternal spindle
#

Nah, looks fine. Not overly complicated. Especially compared to some of the stuff we've seen.

turbid wigeon
#

fair

alpine rock
quick fiber
#

I've sink some times with my generators and my matchers, and all I had to do is build a dict to make iteration by index easier

eternal spindle
orchid adder
#

I don't generally use a dictionary unless there is good reason for it

eternal spindle
# alpine rock Yeah, it's like half the size of mine

is there a better way to get "length of items iterated and stop at the first one"? I used next and enumerate eventually, my initail version was a for loop with break.

next((c for c,e in enumerate(z,1) if t.h<=forest[e].h),d)

(z is the range, d is the default for next)

orchid adder
#

e.g. fast member lookup

#

or something sparse

#
c = d
for c, e in enumerate(z,1):
  if t.h <= forest[e].h:
    break
#

or wait

quick fiber
#

I'm still with the loop/break. since I took the generator path, I've made a do_until just for that

orchid adder
#

the default would have to go in an else

eternal spindle
#

I thought about using itertools.takewhile

orchid adder
#

this is terrible

for c, e in enumerate(z,1):
  if t.h <= forest[e].h:
    break
else:
  c = d
rancid hearth
#

oof a for else

eternal spindle
#

(before I had the class, and just iterated over the rows)

quick fiber
#

isn't that ?

for z in range(y+1,len(row)):
    down+=1
    if t<=row[z]:
        break
rancid hearth
#

hate for-elses. I always just use fors in functions with an early return

eternal spindle
orchid adder
#

for else can be pretty nice

orchid adder
#

though while else is generally the more sensible one

#

it's closer to an if else

quick fiber
#

no for else

rancid hearth
#

again, better to have the for/while in a function

#

then just do an early return

turbid wigeon
#

You have no idea how close I was to using for...else before I realized I misinterpreted the problem

orchid adder
#

this pattern is fun

for thing in things:
  for x in thing:
    if cond(x):
      break
  else:
    continue
  break
#

to break out of both loops

rancid hearth
#

oof

quick fiber
#

that's more than 3 indentations level

#

-> jail

alpine rock
orchid adder
#

I use 2 space indents, that makes it ok :^)

quick fiber
#

jail^jail then

rancid hearth
#

it's just a function with an early return is almost always the better (and less fun) way to do it

orchid adder
#

and fun is the most important part!

#

fun without functions

rancid hearth
#

talking about fun, I'm up to 4 classes for my day 8

orchid adder
#

sometimes I really miss python's comprehensions

  let res = visible
    .into_iter()
    .map(|v| {
      v.into_iter()
        .map(|vis| if vis { 1 } else { 0 })
        .sum::<i32>()
    })
    .sum();
rancid hearth
#

just got to write all my test cases and type hints

orchid adder
#

vs

sum(sum(v) for v in visible)
#

I guess I could write the if as a cast

mellow breach
orchid adder
#

I guess there isn't something like .int_value() on bool in rust?

alpine rock
mellow breach
rancid hearth
#

only 2?! nowhere near enough :)

alpine rock
orchid adder
#

like the if thing will work regardless of int type

mellow breach
orchid adder
mellow breach
#

idk if that works

alpine rock
mellow breach
orchid adder
#

I do

alpine rock
#

Rust doesn't really do "agnostic"

mellow breach
alpine rock
#

It wants you to be specific

orchid adder
#

and let rust deduce the type

#

(at least one of the times)

mellow breach
#

does doing that and removing ::<i32> still work

orchid adder
#

it doesn't

mellow breach
#

ok

orchid adder
#

and it's not about char count, it's about the principle ๐Ÿ˜›

quasi jasper
#

Is the input a square? can we assume it always will be?

orchid adder
#

check

#

if your input is square, feel free to assume it's square

mellow breach
#

what if there's a square font

#

the monospace font makes squares look like rectangles

orchid adder
#

adding a space can do wonders

mellow breach
orchid adder
#

works for fonts that are close to 2:1

#

err

#

maybe my math is wrong pithink

#

if cells are 2:1

#

I guess spacing matters

mellow breach
orchid adder
#

the rect from upper right dot to lower left dot is 64x68

#

so less than 10% off

#

neat

alpine rock
eternal spindle
weak python
orchid adder
idle trail
#
from itertools import product
from functools import reduce
from operator import mul
import numpy as np

with open("08_input.txt") as file:
    data = file.read().splitlines()

visible_trees = 2 * (len(data) + len(data[0]) - 2)
data = np.array([list(map(int, list(line))) for line in data])
max_score = 0

for i, j in product(range(1, len(data) - 1), repeat=2):
    slices = (data[i, :j][::-1], data[i, j + 1 :], data[:, j][:i][::-1], data[:, j][i + 1 :])
    current_value = data[i, j]
    visible_trees += any(np.amax(slice) < current_value for slice in slices)
    max_score = max(max_score, reduce(mul, (np.argmax(slice >= current_value) + 1 if np.amax(slice) >= current_value else len(slice) for slice in slices)))
    
print(visible_trees)
print(max_score)
weak python
#

can use accumulate:

def part_one():
    visible = np.zeros_like(trees, bool)
    visible[[0, -1]] = visible[:, [0, -1]] = True

    for i in range(4):
        dilated = np.maximum.accumulate(np.rot90(trees, i))
        dilated[1:] -= dilated[:-1]
        np.rot90(visible, i)[dilated.nonzero()] = True

    return visible.sum()
rocky vault
#

Took my a while but overall happy with my solution for part1:

with open('day8\input.txt', 'r') as f:
    data = f.read().split('\n')


visible = len(data[0]) * 2

for num, line in enumerate(data[1:-1], 1): 
    for position, tree in enumerate(line):
        if position in (0, (len(data[0])-1)):
            visible += 1
            continue
        if all(line[position] < tree for line in data[num+1:]) or all(line[position] < tree for line in data[:num]) or all(trees < tree for trees in line[position+1:]) or all(trees < tree for trees in line[:position]):  
            visible += 1

print(visible)```

As always, lmk if you see any obvious/simple improvements
orchid adder
#

you know, we should have some "up the ante" style challenge here much like the subreddit has

#

Here is a data generator, change n as you see fit

import random
random.seed(42)

n = 1000
for _ in range(n):
  print(''.join(random.choice('0123456789') for _ in range(n)))

runtimes for my solution

    n     time  part1    part2
  100    386us    920   279357
  500   9.30ms   4390  2462400
 1000   37.5ms   9024  2330944
 5000   1.11 s  44723  7562880
10000   5.14 s  89316  4520880
rancid hearth
#

this is gonna be a big one

#
import dataclasses
import enum
import io
import math
from typing import Callable, Iterator

import numpy as np
import numpy.typing as npt


class Direction(enum.Enum):
    LEFT = enum.auto()
    RIGHT = enum.auto()
    TOP = enum.auto()
    BOTTOM = enum.auto()


@dataclasses.dataclass
class TreeDirectionDetails:
    view_distance: int
    is_visible: bool


@dataclasses.dataclass(frozen=True)
class Coords:
    x: int
    y: int


class Tree(dict[Direction, TreeDirectionDetails]):
    def __repr__(self) -> str:
        return f"Tree({super().__repr__})"

    @property
    def is_visible(self) -> bool:
        return any(
            [direction_details.is_visible for direction_details in self.values()]
        )

    @property
    def scenic_score(self) -> int:
        return math.prod(
            [direction_details.view_distance for direction_details in self.values()]
        )


class TreetopTreeHouse:
    def __init__(self, tree_height_map: str) -> None:
        self._tree_map = self._create_tree_map(tree_height_map)
        self.trees = self._create_trees()

    @classmethod
    def read_file(cls) -> "TreetopTreeHouse":
        with open("input.txt") as f:
            return cls(f.read())

    @staticmethod
    def _create_tree_map(tree_height_map: str) -> npt.NDArray[np.int32]:
        f = io.StringIO(tree_height_map)
        first_line = f.readline().strip()
        f.seek(0)
        return np.genfromtxt(f, dtype="i4", delimiter=[1] * len(first_line))

    @staticmethod
    def _create_tree_details(
        current_tree_height: int,
        direction: Direction,
        directional_trees: npt.NDArray[np.int32],
    ) -> TreeDirectionDetails:
        if direction in (Direction.LEFT, Direction.TOP):
            directional_trees = np.flip(directional_trees)
        view_distance = 0
        for view_distance, tree_height in enumerate(directional_trees, start=1):
            if tree_height >= current_tree_height:
                return TreeDirectionDetails(
                    view_distance=view_distance, is_visible=False
                )
        return TreeDirectionDetails(
            view_distance=view_distance, is_visible=True
        )  # no trees can be found

    def _create_tree(self, row: int, col: int) -> Tree:
        tree_height = self._tree_map[row, col]
        directions = {
            Direction.LEFT: self._tree_map[row, :col],
            Direction.RIGHT: self._tree_map[row, col + 1 :],
            Direction.TOP: self._tree_map[:row, col],
            Direction.BOTTOM: self._tree_map[row + 1 :, col],
        }
        tree = Tree(
            {
                direction: self._create_tree_details(
                    tree_height, direction, directional_trees
                )
                for direction, directional_trees in directions.items()
            }
        )
        return tree

    def _create_trees(self) -> dict[Coords, Tree]:
        trees = {}
        tree_map_iter = np.nditer(self._tree_map, flags=["multi_index"])
        for _ in tree_map_iter:
            row, col = tree_map_iter.multi_index
            tree = self._create_tree(row, col)
            trees[Coords(col, row)] = tree
        return trees

    def _treehouse_query(self, fn: Callable[[Iterator[int]], int], query: str) -> int:
        return fn(getattr(tree, query) for tree in self.trees.values())

    def sum_visible_trees(self) -> int:
        return self._treehouse_query(sum, "is_visible")

    def max_scenic_score(self) -> int:
        return self._treehouse_query(max, "scenic_score")
#
def main() -> None:
    tth = TreetopTreeHouse.read_file()
    print(
        "Sum of visible trees from outside the grid:",
        tth.sum_visible_trees(),
    )
    print(
        "Highest scenic score possible for any tree:",
        tth.max_scenic_score(),
    )


if __name__ == "__main__":
    import timeit

    print(timeit.timeit(main, number=1))
#

And if you thought that was crazy, here is the unit tests for it

orchid adder
#

๐Ÿฅด

alpine rock
orchid adder
#

congrats for being significantly longer than my rust solution

rancid hearth
#

thank you very much!

#

I did it like this so I could assert as much detail as I dare from the problem description

#

I have 29 test cases

alpine rock
#

So... when you said every... you meant every

rancid hearth
#

eh almost

#

So, I assert whether a tree can be seen from a particular direction or not

alpine rock
#

Hey, I have two test cases

rancid hearth
#

but not the actual tree that might block it if it can't be seen

#

that's the one piece of info that was too much even for me XD

orchid adder
#

no TreeFactory or TreetopTreeHouseFactoryProducer, smh

rancid hearth
#

but everything else I have

meager trail
#

anyone did a oneliner?

rancid hearth
meager trail
alpine rock
#

What if we made one?

#

How do you rank by language?

#

Extra points for using the creators favorite language?

orchid adder
#

you can compare against my rust solution for some random larger data ๐Ÿ˜›

rancid hearth
#

Also I specifically got Discord Nitro this month for the extra long comment length

rancid hearth
#

but it wasn't enough for today

#

I still needed to split my solution

meager trail
#

only python solutions get compared

#

to each other

alpine rock
pseudo mirage
#

so you get the same data

orchid adder
alpine rock
#

Will it guarantee the exact same output every single run?
I thought even withing the seed there was still some variance?
... I hope my bank changes their seed for passwords every once and a while

pseudo mirage
#

why would your bank need a seed for passwords ๐Ÿค”

alpine rock
pseudo mirage
#

python's random is pseudorandom. same initial seed -> same output

pseudo mirage
#

rust doesn't have one builtin

orchid adder
#

every computer rng is pseudorandom

#

you can sample real world stuff to get some actual randomness, but that's about it

rancid hearth
#

for all the OOP I do I don't have much inheritance

#

gotta get me some diamond hierarchies going

meager trail
#

im bad at programming

#

when will the aoc things start

pseudo mirage
meager trail
#

where algorithms that only people who attend university can solve?

#

rip english sentence ig

orchid adder
#

let your point class inherit from Tree

#

dumb stuff like that

alpine rock
orchid adder
#

oh right, python supports multiple inheritance

meager trail
rancid hearth
meager trail
#

maybe: how much ahrder will it get

alpine rock
#

lots

rancid hearth
#

that's why I said - diamond hierarchy :)

orchid adder
#

just to mess with everyone

class Tree(Trunk, Leaves, Point):
  ...
#

follows "is a" relationships, a Tree is a Trunk (among other parts)

rancid hearth
#

gotta have some of that

orchid adder
#

type hints should be added, but they don't need to be correct

#

!e

a: (a := 42) = -1
print(a)
surreal tideBOT
#

@orchid adder :white_check_mark: Your 3.11 eval job has completed with return code 0.

42
quasi jasper
#
import numpy as np

with open("day8/forest.txt", "r") as f:
    forest = f.read().split("\n")

int_forest = [list(map(int, row)) for row in forest]
np_forest = np.array(int_forest, dtype = "i")
sub_grid_end = len(np_forest) - 1

visible = 0
for coords, height in np.ndenumerate(np_forest):
    x,y = coords
    if (x in range(1, sub_grid_end) and y in range(1, sub_grid_end)):
        #look up
        if height > np.amax(np_forest[:x,y]):
            visible += 1
        #look down
        elif height > np.amax(np_forest[x+1:,y]):
            visible += 1
        #look right
        elif height > np.amax(np_forest[x, y+1:]):
            visible += 1
        #look left
        elif height > np.amax(np_forest[x, :y]):
            visible += 1
    else:
        #perimeter is visible
        visible += 1

print(visible)

Really happy with this after not getting yesterday

oblique vault
turbid wigeon
#

wish I had known that you could slice 2D arrays

quasi jasper
oblique vault
#

ahh, for day 7, i see

quasi jasper
#

yep

#

now im screwing up part 2

quasi jasper
#

So I found the issue, wondering if anyone knows how to fix it: my slice for look up is :x,y
the problem for part 2 is that goes from the beginning to x, when I need to look from x to beginning. I read I can use -1 to reverse a slice so I tried :x:-1,y but that didnt work

#

actually the same issue for left as well

#

right and down just happened to work because of their nature

quasi jasper
#

[x-1::-1]

#

now the left edges are wrapping or something

lunar ore
#

It's very annoying because it messes with the golf effort too ๐Ÿ™ƒ

rocky vault
#

I don't understand the phrasing for part 2, do they want to know the "scenic score" of the best tree?

lunar ore
#

Yes

#

What is the highest scenic score possible for any tree?

#

Literally the last sentence

rocky vault
#

I thought maybe it was given each tree, what is the best possible score for each one

lunar ore
rocky vault
#

Yeah ik

#

idk the word "Possible" kinda threw me off, so its just what is the score of the highest scoring tree

lunar ore
#

Yeah

#

I guess I can see how the word 'possible' might throw you off yeah

#

I think it's used as in 'if I were to pick any tree, what is the highest scenic score it's possible for me to pick'

rocky vault
#

aha

#

yeah

errant cradle
#

kek

#
with open('08_input.txt') as f:
    inputs = f.read().splitlines()

# inputs = """30373
# 25512
# 65332
# 33549
# 35390""".splitlines()

column = ["".join(i) for i in zip(*inputs)]
row = inputs

len_column = len(column)
len_row = len(row)

n = 0


for x_i, x in enumerate(row):  # int, "30373"
    for y_i, y in enumerate(x):  # int, ["3", "0", "3","7","3"]
        column_ = column[y_i]  # "32633"

        # The sides
        sides = [
            max(row[x_i][:y_i] or "0"),  # left side
            max(row[x_i][y_i+1:] or "0"),  # right side
            max(column[y_i][:x_i] or "0"),  # top side
            max(column[y_i][x_i+1:] or "0"),  # bottom side
        ]

        # This is already correct dummy
        if y >= min(sides):
            n += 1
        else:
            pass

print(n)
#

for some reason this works on the example input but not on my input

#

any idea why?

#

FUCK

#

The only one problem is the "0"

#

arsoitnaioersntioaernstioanrsietoanrsietnarostnarosietn

#

it is fine now

#
with open('08_input.txt') as f:
    inputs = f.read().splitlines()
column = ["".join(i) for i in zip(*inputs)]
row = inputs
len_column = len(column)
len_row = len(row)
n = 0
for x_i, x in enumerate(row):
    for y_i, y in enumerate(x):
        column_ = column[y_i]
        if y > min([
            max(x[:y_i] or "."),  # left side
            max(x[y_i+1:] or "."),  # right side
            max(column_[:x_i] or "."),  # top side
            max(column_[x_i+1:] or "."),  # bottom side
        ]):
            n += 1
print(n)```
#

took me 1 day to made this

#

actually below one hour

#

if only the "." didn't fucked me over

quasi jasper
#

I am back with part two

def part_two():
    with open("day8/forest.txt", "r") as f:
        forest = f.read().split("\n")

    int_forest = [list(map(int, row)) for row in forest]
    np_forest = np.array(int_forest, dtype = "i")
    best_score = 0

    for coords, height in np.ndenumerate(np_forest):
        x,y = coords
        vd = 0
        vds = np.empty(0, dtype = 'i')

        directions = [np_forest[:x,y][::-1], np_forest[x, :y][::-1], np_forest[x+1:,y], np_forest[x, y+1:]]
        for direction in directions:
            for tree in direction:
                if height > tree: vd += 1
                else:
                    vd += 1
                    break
            vds = np.append(vds, vd)
            vd = 0
        if np.prod(vds) > best_score: best_score = np.prod(vds)

    print(f"The highest scenic score is {best_score}")
errant cradle
#

why do people keep using numpy???

rugged fossil
#

it's easy to index columns and rows with numpy

rocky vault
#

My absolute mess of a solution:


with open('day8\input.txt', 'r') as f:
    data = f.read().split('\n')


score = []

for num, line in enumerate(data[1:-1], 1): 
    
    for position, tree in enumerate(line):
        a, b, c, d = 0, 0, 0, 0
        if position in (0, (len(data[0])-1)):
            continue
        for line2 in data[num+1:]:
            if line2[position] < tree:
                a += 1
            else:
                a += 1
                break
        for line3 in data[:num][::-1]:
            if line3[position] < tree:
                b += 1
            else:
                b += 1
                break
        for trees in line[:position][::-1]:
            if trees < tree:
                c += 1
            else:
                c += 1
                break
        for trees in line[position+1:]:
            if trees < tree:
                d += 1
            else:
                d += 1
                break
            
        score.append(a * b * c * d)

print(max(score))```

If you see any optimizations lmk!
lunar ore
#

(they look very much like line but are on transposed_line instead or something)

rocky vault
#

transpose it?

lunar ore
#

Transpose:
Take the grid

A B C D E
F G H I J
K L M N O

and get the grid

A F K
B G L
C H M
D I N
E J O
#

You can do it with [''.join(col) for col in zip(*data)]

rocky vault
#

like rotating it?

lunar ore
#

Kinda

rocky vault
#

I see... Whats the * do in this context?

lunar ore
#

It's a mirror along the diagonal

lunar ore
#

Basically the same as zip(data[0], data[1], data[2], ..., data[-1])

rocky vault
#

Ok, where can I find more info about all these things, like what is the * called?

lunar ore
#
  • is the 'unpack operator'
#

Or 'iterable unpacking'

#

(there's also ** to do it with a dict into keyword arguments)

rocky vault
lunar ore
#

So you know what zip does, right?

rocky vault
#

yeah it takes 2 iterables and puts them together as a tuple for each pair of elements

lunar ore
#

*it takes n iterables and iterates through them all

#

Not necessarily 2

#

If we take the grid from before,

A B C D E
F G H I J
K L M N O
#

That's represented as

['ABCDE',
 'FGHIJ',
 'KLMNO']
rocky vault
#

mhm

lunar ore
#

So zip(*data) (the same as zip(data[0], data[1], data[2])) will first give us ('A', 'F', 'K')

#

Right?

rocky vault
#

uh huh

#

question, why does * only do the first 3?

lunar ore
#

That example only had 3

#

I meant to make that more clear but apparently I forgot the word 'here' lmao

rocky vault
#

ah np

#

my brain is starting to understand this xD

errant cradle
#
with open('08_input.txt') as f:
    inputs = f.read().splitlines()

column = ["".join(i) for i in zip(*inputs)]
row = inputs

len_column = len(column)
len_row = len(row)

n = 0


for x_i, x in enumerate(row):
    for y_i, y in enumerate(x):
        column_ = column[y_i]

        # The sides
        sides = [
            max(row[x_i][:y_i] or "."),  # left side
            max(row[x_i][y_i+1:] or "."),  # right side
            max(column[y_i][:x_i] or "."),  # top side
            max(column[y_i][x_i+1:] or "."),  # bottom side
        ]

        # This is already correct dummy
        if y > min(sides):
            n += 1

print(n)
#

didn't even tried to code golf this

alpine rock
#

Finally!
We've missed you around

rocky vault
silent harness
#

can someone explain where the 5th visible tree is (excluding the 16 outer trees) in the given example? ```
30373
25512
65332
33549
35390

#

i marked them with an X but i only see 4```
30373
2XX12
653X2
33X49
35390

lunar ore
#

The first 5 is visible via the 0 (up)
The second 5 is visible via the 3 (up) or the 12 (right)
The third 5 is visible via the 332 (right) or the 33 (down)
The 2nd 3 is visible via the 2 (right)
The 4th 5 is visible via the 33 (left) or the 3 (down)

#

@silent harness

silent harness
#

how?

#

i thought a tree could not be seen if the one infront of it was taller

#

which are the outer ones*

lunar ore
#

Right, but I told you two paths of outer trees that aren't taller

meager trail
#

look here

silent harness
#

oops, true

upbeat gate
#

8-2 is really giving me a headache

#

might actually need a little help here

#
.split("\n").filter(e=>e).flatMap((e,y,a)=>[...e].map((F,x,_,f=+F)=>[Array(y).fill().map((_,Y)=>+a[Y][x]<f).reduce((a,c)=>[c&&a[0],a[1]+c*(c&&a[0])],[1,0])[1],Array(x).fill().map((_,X)=>+e[X]<f).reduce((a,c)=>[c&&a[0],a[1]+c*(c&&a[0])],[1,0])[1],Array(e.length-x-1).fill().map((_,X)=>+e[x+X+1]<f).reduce((a,c)=>[c&&a[0],a[1]+c*(c&&a[0])],[1,0])[1],Array(a.length-y+2).fill().map((_,Y)=>+(a[y+Y+1]||[Infinity])[x]<f).reduce((a,c)=>[c&&a[0],a[1]+c*(c&&a[0])],[1,0])[1]]))
#

basically I'm struggling with the "tree that blocks counts, but not the edge"

#

I know I'll have to replace the map with reduce so I can end early, or maybe insert an extra item after and redo the map?

upbeat gate
#

Okay, I've just had a horrible idea

#

I should make a helper that adds one unless it hits an edge

neon pilot
#

as it turns out this was because the environment variable didn't persist when I closed the terminal lol

lunar ore
neon pilot
#

idk

#

I'm using a file now

west vortex
#

Can the tree house see all the trees of the same height or just one or what is it really I'm confused

lunar ore
#

The first tree taller than, or the same height as, the tree you're in

#

I think

west vortex
#

If i was 5 and there were 6 6 6 in front I would only see the first one?
And if I was 5 and there were 3 3 3 in front I would see all of them?

lunar ore
#

Correct

west vortex
#

With those rules I found a tree with a smaller score

lunar ore
#

You're skipping valid trees in the middle of the path

#

Yeah I know you shouldn't be able to see them but they're counted

west vortex
#

Are they not blocked by the bigger trees

#

oh

lunar ore
#

You have superhuman eyes so you can see them anyway idk

west vortex
#

yeahh this is weird

lunar ore
#

My dad did that as well apparently lol

west vortex
#

This looks really sussy

lunar ore
#

That does appear to be the right answer though

#

Possibly OBOE with not counting the centre tree, don't remember

west vortex
#

It was correct

#

yussss

#

the instructions are confusing sometimes

woeful summit
#

yeah so this is my code so far

#
forest = """30373
25512
65332
33549
35390""".split(
    "\n"
)

for i, j in enumerate(inner:=forest.copy()):
    inner[i] = inner[i][1:-1]
inner = inner[1:-1]

count=0
for i in inner:
    for x,y in enumerate(map(int,i)):
        pass```
#

it works like

#

but apparently i shouldnt be enumerating i

mellow breach
woeful summit
#

idk someone in #python-discussion said that

green canopy
woeful summit
#

yeah I honestly have no idea

#

im going to rewrite some stuff

woeful summit
#

ok yeah ive kinda hit a wall

#

this is what I have ```py
forest = """30373
25512
65332
33549
35390""".split(
"\n"
)

listforest = [list(map(int, x)) for x in forest]

for i, j in enumerate(inner:=forest.copy()):
inner[i] = inner[i][1:-1]
inner = inner[1:-1]

count=0
for i,j in enumerate(inner):
row=listforest[i+1]
for x,y in enumerate(map(int,j)):
# print(i, x, y)
# check left
left = row[0:i+2]
if left.index(max(left)) == i+1:count+=1
# m = left.index(max(left))```

#

this is what listforest is

#

just a 2d list of the forest

#

inner is this which is just like the highlighted part

#

thats all fine and working but now I have no idea how to count visible trees

#

ill try and explain what I tried to do here

#

so say for the first tree which is row 2 column 2

#

I slice the list to get this

#

i check if the last item is the largest item in the list

#

and if it is i increase the count by 1

#

and it works for that tree

#

wait im an idiot

#

nvm im stuck

#

I have no idea what to do from here

#

i have no clue if it would even work

#

can anyone give me some hints

#

like point me in the right direction

#

ive tried to explain my code the best I can

rugged fossil
#

don't bother trying to remove the outer edges

#

just iterate over every element in the list

#

it's easier

woeful summit
#
listforest = [list(map(int, x)) for x in forest]

for i, j in enumerate(listforest):
    for x, y in enumerate(listforest[i]):
        if len(listforest) - 2 < i or i < 1:
            break
        if x == 0 or x == len(listforest[i]) - 1:
            continue
        ...```
#

is this a better start?

#

wait nvm

#

ive basically just done the exact same thing again ๐Ÿ’€

#

or have I

#

I actually have no idea

green canopy
#

tell us what you're trying to do

timid monolith
#

Hey

woeful summit
#

ok so rn this is what I have ```py
forest = """30373
25512
65332
33549
35390""".split(
"\n"
)

count = 0
listforest = [list(map(int, x)) for x in forest]
vert = list(map(list, list(zip(*listforest))))

for i, j in enumerate(listforest):
for x, y in enumerate(listforest[i]):
# left + right
left = listforest[i][:x+1]
right = listforest[i][x:]
if left.index(max(left)) is x: count+=1;print(1,end=" ");continue
elif right.index(max(right)) == 0: count+=1;print(1,end=" "); continue
else:print(0,end=" ")```

#

ignore the prints thats just testing

timid monolith
#

Which part are you having problems with?

woeful summit
#

second last line

#

so everything works fine except in this one case

timid monolith
#

Is this p1 or p2?

woeful summit
#

p1

#

for this line in the input 65332

#

it says all trees are visible from the right

#

when the middle tree shouldnt

timid monolith
#

Ah

#

You're only checking if the highest tree in the row/column is in the way

woeful summit
#

ik its because im checking from left to right but im not sure how to fix it

timid monolith
#

Not any tree that's at least as tall as the current tree

#

Wait no

woeful summit
#

like that line would work if it was flipped to 23356

#

but somethings wrong with my right hand check

timid monolith
#

Yeah I think you can do that

#

Just flip the right hand side list

#

Do right = listforest[i][x:][::-1] and you can use the same check as the left hand one

woeful summit
#
right = listforest[i][x::-1]
if left.index(max(left)) is x: count+=1;print(1,end=" ");continue
elif right.index(max(right)) is x: count+=1;print(1,end=" "); continue```
#

yeah i tried this and it didnt work

timid monolith
#

You'll have to change is x for that

#

And use == instead of is

woeful summit
timid monolith
#

You'll have to figure that out yourself ๐Ÿ˜‰

woeful summit
#

ok fair enough

#

ty for the help

timid monolith
#

Np

woeful summit
#

why == instead of is though

#

I think I changed it because == wasnt working

timid monolith
#

You're just comparing numbers

#

Using is might break for larger numbers because of how python deals with numbers (specifically >256 iirc)

woeful summit
#

oh

timid monolith
#

I gtg, gl on the puzzle

woeful summit
#

ty bye

woeful summit
#

finally got it

forest = """...""".split(
    "\n"
)

count = 0
listforest = [list(map(int, x)) for x in forest]
vert = list(map(list, list(zip(*listforest))))

for i, j in enumerate(listforest):
    for x, y in enumerate(listforest[i]):
        left = listforest[i][: x + 1]
        right = listforest[i][x:][::-1]
        top = vert[x][: i + 1]
        bottom = vert[x][i:][::-1]

        if left.index(max(left)) == x: count += 1
        elif right.index(max(right)) + 1 == len(right): count += 1
        elif top.index(max(top)) == i: count += 1
        elif bottom.index(max(bottom)) + 1 == len(bottom): count += 1
            
print(count)```
woeful summit
#

just a bit confused about how part 2 works

#

if Ive a row of trees [3, 3, 0, 0, 4]

#

can the the first tree see the last tree?

rugged fossil
#

yes

woeful summit
#

and it can see everything between those two?

#

even if its lower?

#

so like is it basically just find the highest tree in the list and it can see everything in between those two?

rugged fossil
#

no

#

it sees 3

#

and then none of the 0s

#

and then 4

timid monolith
#

It just sees the 3 and that's it

#

Because everything behind the 3 is blocked by it

rugged fossil
#

i thought the 4 was higher than the 3 so you can see it over the 3?

timid monolith
#

There was something about the treehouse having a roof

rugged fossil
#

oh

#

then i'm wrong

timid monolith
#

So you just count till the first tree >= to the current