Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
necrotic
Aug 2, 2005
I owe my brother big time for this!
Yeah, missing qoutes around the key.

Adbot
ADBOT LOVES YOU

Seventh Arrow
Jan 26, 2005

Yeah, I forgot the quotes, eesh. Thanks for the help!

Seventh Arrow
Jan 26, 2005

Ok, this time I'm sure it's not me (I hope). I'm using the same code but typo-corrected:

code:
import requests
from requests.auth import HTTPBasicAuth

url = 'https://photoai.lotlinx.com/images/optimize'
files = [{"dealerid":12345, "vehicles":[ { "id":55, "images" : [ {"imageId": 1, "imageURL":"https://img.lotlinx.com/vdn/7416/jeep_wrangler%20unlimited_2014_1C4BJWFG3EL326863_7416_339187295.jpg"}, {imageID: 2, "imageURL":"https://img.lotlinx.com/vdn/7416/jeep_wrangler%20unlimited_2014_1C4BJWFG3EL326863_7416_2_339187295.jpg"}]}]}]
r = requests.put(url, files=files, auth=('username', 'password'))
err = r.raise_for_status()
print(err)
When doing this, I get the error: "requests.exceptions.HTTPError: 404 Client Error: Not Found for url: https://photoai.lotlinx.com/images/optimize"

So I thought that maybe they gave me the wrong URL, but if I use "requests.post" instead of "requests.put" I get: "requests.exceptions.HTTPError: 403 Client Error: Forbidden for url: https://photoai.lotlinx.com/images/optimize"

So that makes me think that the URL is valid, but there's something in the request that it's not liking. In the documentation, it says:

"1. SUBMIT REQUESTS
Host - https://photoai.lotlinx.com
POST /images/optimize"

Is there any reason that they would put it like that? I thought maybe it's necessary to first access the URL with the username and password and then pass the files on to the /images/optimize folder, but nothing in the Requests documentation seems to allow for doing things like that. I tried accessing the URL through a web browser and used the username/password and just got an authentication error instead of a 404, so I think the URL must be valid. I feel like I might be missing something obvious here, any ideas?

Data Graham
Dec 28, 2009

📈📊🍪😋



It sounds like it's legitimately giving you an authentication error. Are you literally sending "username" and "password" as the username and password like in your code? Didn't the docs describe what to use for those values?

Seventh Arrow
Jan 26, 2005

No, I'm using the u/p that they sent me in an email - so it's possible that the info is incorrect. Wouldn't an invalid username or password bring up a different kind of error, though?

Wallet
Jun 19, 2006

Seventh Arrow posted:

No, I'm using the u/p that they sent me in an email - so it's possible that the info is incorrect. Wouldn't an invalid username or password bring up a different kind of error, though?

Presumably 401, yes.

Thermopyle
Jul 1, 2003

...the stupid are cocksure while the intelligent are full of doubt. —Bertrand Russell

Seventh Arrow posted:

Ok, this time I'm sure it's not me (I hope). I'm using the same code but typo-corrected:

code:
import requests
from requests.auth import HTTPBasicAuth

url = 'https://photoai.lotlinx.com/images/optimize'
files = [{"dealerid":12345, "vehicles":[ { "id":55, "images" : [ {"imageId": 1, "imageURL":"https://img.lotlinx.com/vdn/7416/jeep_wrangler%20unlimited_2014_1C4BJWFG3EL326863_7416_339187295.jpg"}, {imageID: 2, "imageURL":"https://img.lotlinx.com/vdn/7416/jeep_wrangler%20unlimited_2014_1C4BJWFG3EL326863_7416_2_339187295.jpg"}]}]}]
r = requests.put(url, files=files, auth=('username', 'password'))
err = r.raise_for_status()
print(err)
When doing this, I get the error: "requests.exceptions.HTTPError: 404 Client Error: Not Found for url: https://photoai.lotlinx.com/images/optimize"

So I thought that maybe they gave me the wrong URL, but if I use "requests.post" instead of "requests.put" I get: "requests.exceptions.HTTPError: 403 Client Error: Forbidden for url: https://photoai.lotlinx.com/images/optimize"

So that makes me think that the URL is valid, but there's something in the request that it's not liking. In the documentation, it says:

"1. SUBMIT REQUESTS
Host - https://photoai.lotlinx.com
POST /images/optimize"

Is there any reason that they would put it like that? I thought maybe it's necessary to first access the URL with the username and password and then pass the files on to the /images/optimize folder, but nothing in the Requests documentation seems to allow for doing things like that. I tried accessing the URL through a web browser and used the username/password and just got an authentication error instead of a 404, so I think the URL must be valid. I feel like I might be missing something obvious here, any ideas?

Try it with a tool like this chrome extension: Advanced REST client.

Might give you more information...

Seventh Arrow
Jan 26, 2005

Thermopyle posted:

Try it with a tool like this chrome extension: Advanced REST client.

Might give you more information...

This is strange, because when I use the app, it goes through ok with a '200'.

The only differences is that the python code is being run from my linux laptop, whereas the ARC app is being run from my Windows 10 desktop. Also, the laptop is also using python but none of the code seems out of place. This is going to take a bit of thinking.

SirPablo
May 1, 2004

Pillbug
I've wasted a lot of time trying to figure out an efficient way to do this. I have three 2D arrays of which contain each element's latitude, longitude, and an arbitrary value we'll call Z. I wish to find a way to determine the x/y coordinates of where arbitrarily located smaller domains overlap.The smaller domains would also have lat/lon data, and I wish to extract the Z data for the area of overlap and set those values in the smaller arrays. Both the larger and smaller arrays are from the same projection, so lat/lon values will be the same in both. Visually, I am wanting to find where the red squares exist within the blue square's domain, then extract the values in yellow from the blue array and input them into the red arrays.

Any ideas?

Only registered members can see post attachments!

Linear Zoetrope
Nov 28, 2011

A hero must cook
This is basically just going to be an AABB test for each small square. Test which ones intersect, then if they do:

Test which vertices intersect,
Find the point on the edge that intersects the rectangle's edge between adjacent vertices that do and don't intersect to make a "subrectangle" that fully intersects (if any two adjacent vertices both intersect, they're both part of the subrectangle).
*** To be clear, this is just a line segment test between segments that are partially inside the rectangle. You can find the point that segment intersects and it becomes a "new" vertex that's fully inside the rectangle.
Iterate over this subrectangle of the test rectangle and copy the Z's

That's the easiest way to do it I can think of. You can also, of course, use BVHs or spatial hashing or whatever if you think there will be a significant number of test rectangles that don't intersect at all.

E: Also, if you end up with only 3 intersecting vertices, you need to find which you're missing and then sub in the appropriate x,ys from the other points.
E2: I made a crummy gif

Linear Zoetrope fucked around with this message at 22:48 on Mar 31, 2018

Dr Subterfuge
Aug 31, 2005

TIME TO ROC N' ROLL
Instead of explicitly searching intersections it would probably be easier (at least from a coding perspective) to use pandas and merge dataframes.

Linear Zoetrope
Nov 28, 2011

A hero must cook

Dr Subterfuge posted:

Instead of explicitly searching intersections it would probably be easier (at least from a coding perspective) to use pandas and merge dataframes.

TBH I missed this was the Python thread, saw intersecting rectangles and went "obviously this is the game dev thread, let's go in graphics/geometry mode :v:"

Thermopyle
Jul 1, 2003

...the stupid are cocksure while the intelligent are full of doubt. —Bertrand Russell

Seventh Arrow posted:

This is strange, because when I use the app, it goes through ok with a '200'.

The only differences is that the python code is being run from my linux laptop, whereas the ARC app is being run from my Windows 10 desktop. Also, the laptop is also using python but none of the code seems out of place. This is going to take a bit of thinking.

Maybe they're filtering on user agent?

Seventh Arrow
Jan 26, 2005

Actually I tried using ARC on the linux laptop and it works there too :eng99: the only thing that I can think of is maybe I need to use something other than "json=files". In ARC, I didn't specify anything for "Body Content Type."

I updated the code a little bit:

code:
import requests

url = 'https://photoai.lotlinx.com/images/optimize'
files = {
   "dealerId": 12345,
   "vehicles": [
       {"id": 55,
        "images" : [
           {"imageId": 1,
            "imageUrl": "https://img.lotlinx.com/vdn/7416/jeep_wrangler%20unlimited_2014_1C4BJWFG3EL326863_7416_339187295.jpg"
           }
        ]
       }
   ]
}
r = requests.put(url, json=files, auth=('username', 'password'))
#rsc = r.status_code
err = r.raise_for_status()
print(err)

SirPablo
May 1, 2004

Pillbug

Linear Zoetrope posted:

This is basically just going to be an AABB test for each small square. Test which ones intersect, then if they do:

Test which vertices intersect,
Find the point on the edge that intersects the rectangle's edge between adjacent vertices that do and don't intersect to make a "subrectangle" that fully intersects (if any two adjacent vertices both intersect, they're both part of the subrectangle).
*** To be clear, this is just a line segment test between segments that are partially inside the rectangle. You can find the point that segment intersects and it becomes a "new" vertex that's fully inside the rectangle.
Iterate over this subrectangle of the test rectangle and copy the Z's

That's the easiest way to do it I can think of. You can also, of course, use BVHs or spatial hashing or whatever if you think there will be a significant number of test rectangles that don't intersect at all.

E: Also, if you end up with only 3 intersecting vertices, you need to find which you're missing and then sub in the appropriate x,ys from the other points.
E2: I made a crummy gif



Seeing your crummy gif triggered the idea that I need to find where a column intersects a row. It's a little trickier though because the lat/lons are projected, meaning they won't have a simple linear increase/decrease.

But this idea...

Dr Subterfuge posted:

Instead of explicitly searching intersections it would probably be easier (at least from a coding perspective) to use pandas and merge dataframes.

I hadn't considered that. I'll give it a try, may be relatively quick and easy!

Thermopyle
Jul 1, 2003

...the stupid are cocksure while the intelligent are full of doubt. —Bertrand Russell

Seventh Arrow posted:

Actually I tried using ARC on the linux laptop and it works there too :eng99: the only thing that I can think of is maybe I need to use something other than "json=files". In ARC, I didn't specify anything for "Body Content Type."

I updated the code a little bit:

code:
import requests

url = 'https://photoai.lotlinx.com/images/optimize'
files = {
   "dealerId": 12345,
   "vehicles": [
       {"id": 55,
        "images" : [
           {"imageId": 1,
            "imageUrl": "https://img.lotlinx.com/vdn/7416/jeep_wrangler%20unlimited_2014_1C4BJWFG3EL326863_7416_339187295.jpg"
           }
        ]
       }
   ]
}
r = requests.put(url, json=files, auth=('username', 'password'))
#rsc = r.status_code
err = r.raise_for_status()
print(err)

At this point, I'd probably start up Charles and compare the request data from python and from ARC.

Cingulate
Oct 23, 2012

by Fluffdaddy

Dr Subterfuge posted:

Instead of X it would probably be easier to use pandas
Thread title

Seventh Arrow
Jan 26, 2005

Thermopyle posted:

At this point, I'd probably start up Charles and compare the request data from python and from ARC.

This looks really handy, thank you.

Dr Subterfuge
Aug 31, 2005

TIME TO ROC N' ROLL

Cingulate posted:

Thread title

I had this feeling I had read some joke about how often pandas was recommended in this thread. Somehow I actually hadn't known about it until I read about it here. Intersections between tabular data sounds like a pretty good use case at least!

Baronash
Feb 29, 2012

So what do you want to be called?
Is there a good way to pass data between two scripts running on two different environments? I'm working with Civ IV, which enables Python scripting with Python 2, and am grabbing scoring data. Every few turns, I would like to run that scoring data through a script that uses Python 3-specific modules, and then return a single value to the Python 2 script. Right now, the best I can think of is having both scripts read from and write to the same file.

Thermopyle
Jul 1, 2003

...the stupid are cocksure while the intelligent are full of doubt. —Bertrand Russell

It's hokey, but that's what I'd do unless I ran into some sorta concurrency issues.

Nippashish
Nov 2, 2005

Let me see you dance!
You can also run an http server in the python 3 process and have the python 2 process interact with that to signal when it should do things.

Seventh Arrow
Jan 26, 2005

So I managed to clear up the previous problem...apparently using "post" instead of "put" and "json=files" instead of "data=files" did the trick :confused:

The next thing I want to verify is that there's no simple way to access a value in a nested dictionary, correct? When I print "requests.json()" I get this string:

{'data': [{'token': 'bYrIXkbPK933im6zpM4GoPB59i7pxLhkfAbdCrQr7kWFrjnH5hW5Z130uqqSI1uU', 'status': 'queued'}], 'status': 200}

I need to take the value for 'token' and put it in a URL, like 'https://photoai.lotlinx.com/images/<token>/status' but all the googling I've done seems to indicate that I need a function to pull out the value...does that sound right?

Space Kablooey
May 6, 2009


I mean you could (and should) make that into a function, but you can get the token by doing requests.json()['data'][0]['token'] directly.

To expand, request.json() returns a python dictionary (and not a string), that is, in short, a data structure that maps keys into values. So, for that particular dictionary, your token is inside a dictionary, under the key "token", that is inside a single element list (so index 0), that is inside another dictionary, under the key "data".

Seventh Arrow
Jan 26, 2005

That's great, thank you. Could you maybe explain why it's necessary to put the [0] between ['data'] and ['token']? (and maybe where I could find more info on the topic?)

tef
May 30, 2004

-> some l-system crap ->
It's in a list

Python has collection types, dict, and list, which are used for json arrays and objects:

code:
x  = ["a", "b", "c"]
x[0] # is "a"
x[0] is asking for the 0th element of the list. Yes, it's 0-indexed.

If you're asking questions like this, you might want to run through, or skim over the python tutorial, just to get a feel for some of the names of things.

https://docs.python.org/3/tutorial/

You can pick the right version of the tutorial to match your version of python

SirPablo
May 1, 2004

Pillbug
Pandas you're my hero. I figured there was a simple python way to do this. Thank you for the suggestion!

code:
smallLats = np.load('smallLats.npy') # 289x289 array
smallLons = np.load('smallLons.npy') # 289x289 array
small = pd.DataFrame({'Lats':smallLats.flatten(),
                      'Lons':smallLons.flatten(),
                      'Z': smallLats.flatten()*0})

bigLats = np.load('bigLats.npy') # 1249x2049 array
bigLons = np.load('bigLons.npy') # 1249x2049 array
bigZ = np.load('bigZ.npy') # 1249x2049 array
big = pd.DataFrame({'Lats': bigLats.flatten(),
                    'Lons': bigLons.flatten(),
                    'Z': bigZ.flatten()})

tmp = pd.merge(smallDomain, bigDomain, on=['Lats','Lons'])
smallZ = tmp.Z_x.values.reshape(smallLats.shape) # 289x289 array

Data Graham
Dec 28, 2009

📈📊🍪😋



Seventh Arrow posted:

That's great, thank you. Could you maybe explain why it's necessary to put the [0] between ['data'] and ['token']? (and maybe where I could find more info on the topic?)

Because the [] brackets mean an array or list. There is only one element in that list, the {'token': 'bYrIXkbPK933im6zpM4GoPB59i7pxLhkfAbdCrQr7kWFrjnH5hW5Z130uqqSI1uU', 'status': 'queued'} dict; but you still have to specify which element in the list you want.

Formatted out, your dict might look like this, with more than one item in the list:

code:
{
    'data': [
        {
            'token': 'bYrIXkbPK933im6zpM4GoPB59i7pxLhkfAbdCrQr7kWFrjnH5hW5Z130uqqSI1uU', 'status': 'queued'
        },
        {
            'token': 'asdadasdasdasdsad', 'status': 'queued'
        }
    ], 
    'status': 200
}
And in that case you'd use [1] to refer to the list element with the "asdadasdasdasdsad" token.

Data Graham fucked around with this message at 02:36 on Apr 2, 2018

Seventh Arrow
Jan 26, 2005

Thanks to everyone's help, I was able to tear through a lot of the rest of the project. I'm stuck on the last bit, though...the API has generated some image files and I need to download them, preferable to a specific directory. Without going too much into how I got here, here is the list of image URLs:

code:
import requests
from requests.auth import HTTPBasicAuth
from nested_lookup import nested_lookup
import re

img_url = (nested_lookup('modifiedUrl', r4j))
print(img_url)
['http://lotlinx-ml-images.s3.amazonaws.com/front_3_quarter/jeep_wrangler%2520unlimited_2014_1C4BJWFG3EL326863_7416_339187295.png', 'http://lotlinx-ml-images.s3.amazonaws.com/front/jeep_wrangler%2520unlimited_2014_1C4BJWFG3EL326863_7416_2_339187295.png', 'http://lotlinx-ml-images.s3.amazonaws.com/side/jeep_wrangler%2520unlimited_2014_1C4BJWFG3EL326863_7416_3_339187295.png', 'http://lotlinx-ml-images.s3.amazonaws.com/side/jeep_wrangler%2520unlimited_2014_1C4BJWFG3EL326863_7416_4_339187295.png', 'http://lotlinx-ml-images.s3.amazonaws.com/side/jeep_wrangler%2520unlimited_2014_1C4BJWFG3EL326863_7416_5_339187295.png']

code:
string = ' '.join(img_url)
match = re.findall(r'jeep_wrangler%\w+.png', string)
print(match)
['jeep_wrangler%2520unlimited_2014_1C4BJWFG3EL326863_7416_339187295.png', 'jeep_wrangler%2520unlimited_2014_1C4BJWFG3EL326863_7416_2_339187295.png', 'jeep_wrangler%2520unlimited_2014_1C4BJWFG3EL326863_7416_3_339187295.png', 'jeep_wrangler%2520unlimited_2014_1C4BJWFG3EL326863_7416_4_339187295.png', 'jeep_wrangler%2520unlimited_2014_1C4BJWFG3EL326863_7416_5_339187295.png']

So as mentioned, I want to download these files. I want to do it in a way that uses the existing filenames, but I'm not familiar with the process. To generate a list of the files and the intended location, I did this:

code:
head = '/home/seventh_arrow/Documents/project/'
locs = [head + m for m in match]
print(locs)
['/home/seventh_arrow/Documents/project/jeep_wrangler%2520unlimited_2014_1C4BJWFG3EL326863_7416_339187295.png', '/home/seventh_arrow/Documents/project/jeep_wrangler%2520unlimited_2014_1C4BJWFG3EL326863_7416_2_339187295.png', '/home/seventh_arrow/Documents/project/jeep_wrangler%2520unlimited_2014_1C4BJWFG3EL326863_7416_3_339187295.png', '/home/seventh_arrow/Documents/project/jeep_wrangler%2520unlimited_2014_1C4BJWFG3EL326863_7416_4_339187295.png', '/home/seventh_arrow/Documents/project/jeep_wrangler%2520unlimited_2014_1C4BJWFG3EL326863_7416_5_339187295.png']

So finally, this is what I tried for downloading the files into the directory with their original filenames:

code:
for img in img_url:
    response = requests.get(img, stream=True)
    handle = open(locs, "wb")
    for chunk in response.iter_content(chunk_size=512):
        if chunk:
            handle.write(chunk)
This gives me the error:

TypeError Traceback (most recent call last)
<ipython-input-188-474e4bda9ae7> in <module>()
1 for img in img_url:
2 response = requests.get(img, stream=True)
----> 3 handle = open(locs, "wb")
4 for chunk in response.iter_content(chunk_size=512):
5 if chunk:

TypeError: invalid file: ['/home/seventh_arrow/Documents/project/jeep_wrangler%2520unlimited_2014_1C4BJWFG3EL326863_7416_339187295.png', '/home/seventh_arrow/Documents/project/jeep_wrangler%2520unlimited_2014_1C4BJWFG3EL326863_7416_2_339187295.png', '/home/seventh_arrow/Documents/project/jeep_wrangler%2520unlimited_2014_1C4BJWFG3EL326863_7416_3_339187295.png', '/home/seventh_arrow/Documents/project/jeep_wrangler%2520unlimited_2014_1C4BJWFG3EL326863_7416_4_339187295.png', '/home/seventh_arrow/Documents/project/jeep_wrangler%2520unlimited_2014_1C4BJWFG3EL326863_7416_5_339187295.png']

Admittedly, the last bit of code is ripped off from someone's blog so I'm not 100% what everything does. Is there any way to make it work?

Dr Subterfuge
Aug 31, 2005

TIME TO ROC N' ROLL
You can't open a list of file paths. You have to do it one at a time.

Seventh Arrow
Jan 26, 2005

Yikes, really? So if it generated a hundred files, I'd be pretty hosed. I guess for now I'll just be thankful that the exercise only has five files.

So should I just do five "for img in img_url:" passes, each with a different filename?

Thermopyle
Jul 1, 2003

...the stupid are cocksure while the intelligent are full of doubt. —Bertrand Russell

Seventh Arrow posted:

Yikes, really? So if it generated a hundred files, I'd be pretty hosed.

Why do you think that?

Dr Subterfuge
Aug 31, 2005

TIME TO ROC N' ROLL

Seventh Arrow posted:

Yikes, really? So if it generated a hundred files, I'd be pretty hosed. I guess for now I'll just be thankful that the exercise only has five files.

So should I just do five "for img in img_url:" passes, each with a different filename?

You can iterate over your urls and locations at the same time with zip.

Python code:
for img, loc in zip(img_url, locs):
    response = requests.get(img, stream=True)
    with open(loc, "wb") as handle:
        for chunk in response.iter_content(chunk_size=512):
            if chunk:
                handle.write(chunk)
Also, I changed your open() call to the preferred syntax. It handles all the background stuff of making sure the file closes and whatnot for you.

Seventh Arrow
Jan 26, 2005

Thermopyle posted:

Why do you think that?

Maybe I misunderstood him, but it kind of sounds like you have to do everything manually, instead of letting python feed the image names and URLs into the file-generating functions.

Dr Subterfuge
Aug 31, 2005

TIME TO ROC N' ROLL
You don't have to have any more infrastructure than what I posted. In your code there isn't actually any reason to want to open a bunch of files simultaneously anyway, since each url is accessed sequentially, and each url corresponds to one location.

mr_package
Jun 13, 2000
If you're writing data (app settings, cache, whatever) to disk at what point do you drop Python/pickle/json in individual files and move to SQLite? To my mind the threshold here is pretty low-- otherwise I'm writing a bunch of file load/save stuff, possibly worrying about contention, race conditions on the file(s) etc. when I could just let the database handle it. Am I wrong? This is a very light duty internal / test project-- say 50 reads and 10 writes per day so it's almost a waste of time to even worry about it. But say I scale up 10-100x from there, what choice would you make? Since SQLite is in the standard Python3 library why would you not use it for this type of case?

Boris Galerkin
Dec 17, 2011

I don't understand why I can't harass people online. Seriously, somebody please explain why I shouldn't be allowed to stalk others on social media!
Is there such thing as "statically compiling" an executable Python script? I would like to be able to just distribute the script as an executable with dependencies baked into it so that I don't need to worry about sourcing or creating a venv and whatnot.

Malcolm XML
Aug 8, 2009

I always knew it would end like this.

Boris Galerkin posted:

Is there such thing as "statically compiling" an executable Python script? I would like to be able to just distribute the script as an executable with dependencies baked into it so that I don't need to worry about sourcing or creating a venv and whatnot.

Yeah py2exe or pex

unpacked robinhood
Feb 18, 2013

by Fluffdaddy
Is there a simple common method to correct typos ?
I have a list of maybe mistyped words, and a csv with the reference spelling. The CSV has 36000 entries.

I can bruteforce it using Levenshtein distance between each word of each list but it's massively inefficient.

e: I found this

unpacked robinhood fucked around with this message at 10:23 on Apr 5, 2018

Adbot
ADBOT LOVES YOU

Munkeymon
Aug 14, 2003

Motherfucker's got an
armor-piercing crowbar! Rigoddamndicu𝜆ous.



unpacked robinhood posted:

Is there a simple common method to correct typos ?
I have a list of maybe mistyped words, and a csv with the reference spelling. The CSV has 36000 entries.

I can bruteforce it using Levenshtein distance between each word of each list but it's massively inefficient.

e: I found this

I've run Levenshtein and LCS on bigger lists and it took on the order of minutes, not days, so it's not that bad, even if it is technically very inefficient. I'd characterize filling memory with every edit distance of 1 and 2 as brute force adjacent, at least :v:

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply