Browse Source

Time to finish this article I guess

master
parent
commit
6c30fb3fb2
Signed by: Gauvain "GovanifY" Roussel-Tarbouriech <gauvain@govanify.com> GPG Key ID: DE62E1E2A6145556
12 changed files with 422 additions and 1 deletions
  1. +1
    -0
      .gitignore
  2. +2
    -1
      README.md
  3. +111
    -0
      bringing_a_gun_to_a_guard_fight.py
  4. +69
    -0
      distract_the_guards.py
  5. +34
    -0
      en_route_salute.py
  6. +8
    -0
      end.py
  7. +41
    -0
      expanding_nebula.py
  8. +19
    -0
      find_the_access_code.py
  9. +26
    -0
      fuel_injection_perfection.py
  10. +44
    -0
      ion_flux_relabeling.py
  11. +35
    -0
      queue_to_do.py
  12. +32
    -0
      solar_doomsday.py

+ 1
- 0
.gitignore View File

@@ -0,0 +1 @@
hack/*

+ 2
- 1
README.md View File

@@ -1,2 +1,3 @@
# foobar

My Solutions for the [Google Foobar](https://google.com/foobar) challenge, full
writeup available [here](https://govanify.com/post/foobar/)

+ 111
- 0
bringing_a_gun_to_a_guard_fight.py View File

@@ -0,0 +1,111 @@
from math import atan2, sqrt

"""
The first thing you had to realize in order to solve this challenge is that you
could simply mirror the room and check if the beam gets where you want. Let me
show you:
In our case let us make a room of dimension [3,2], with the position of the
target at [1,2]. For the sake of simplicity let us imagine the beam is shot at
[1,2] and so we are trying to shot ourselves. We get something like this:
-----
|*xx|
|xxx|
-----
If we shoot with a vector of [1,0], aka straight ahead, we will get this result:
-----
|*~~|
|xxx|
-----
We can see that the beam gets back to you directly, thanks to the reflection,
and that the goal is achieved! But there is another way to do that:
----- -----
|*~~| |~~*|
|xxx| |xxx|
----- -----
We can here realize that, by putting a mirror version of the room where the beam
gets, we can make a straight line instead of calculating the reflection and see
that it gets to our original target, [1,2], being ourselves!
The good thing with that is that it also allows us to determine the distance
needed by the beam to get through, and thus we can mirror the rooms up to the
maximum distance easily.

The next thing to realize was that room mirrors in a pattern, see the below
diagram:
-----
|*xx|
|xxx|
-----
[...] ----- [...]
|xxx|
|*xx|
-----
----- ----- ----- ----- -----
|*xx| |xx*| |*xx| |xx*| |*xx|
|xxx| |xxx| |xxx| |xxx| |xxx|
----- ----- ----- ----- -----
-----
|xxx|
|*xx|
-----
[...] ----- [...]
|*xx|
|xxx|
-----
x always repeats in the same way, and so does y
Thus we need to figure out how to calculate how to mirror one room twice and we
can make an entire atlas of mirrored rooms!
In our case though the function below only calculates the mirrors of the point
of coordinates node across an atlas of length (distance*2)^2
"""
def mirror_atlas(node, dimensions, distance):
node_mirrored=[]
for i in range(len(node)):
points=[]
# since it is floored we add 1, and python stops before the upper bound
# so +2 in our case
for j in range(-(distance//dimensions[i])-1, (distance//dimensions[i]+2)):
points.append(get_mirror(j, node[i], dimensions[i]))
node_mirrored.append(points)
return node_mirrored

# this simply calculates the mirror nth of a point in a 1D space
def get_mirror(mirror, coordinates, dimensions):
res=coordinates
mirror_rotation=[2*coordinates, 2*(dimensions-coordinates)]
if(mirror<0):
for i in range(mirror, 0):
res-=mirror_rotation[(i+1)%2]
else:
for i in range(mirror, 0, -1):
res+=mirror_rotation[i%2]
return res

# pretty simple too: we get the angle at which the beam is needed to be to get
# to the guard and store its distance. First we do that to yourself, then to the
# guard and if the distance for the guard is lower than yourself then he'll be
# hit first by the beam.
def answer(dimensions, your_position, guard_position, distance):
mirrored = [mirror_atlas(your_position, dimensions,
distance),mirror_atlas(guard_position, dimensions, distance)]
res=set()
angles_dist={}
for i in range(0, len(mirrored)):
for j in mirrored[i][0]:
for k in mirrored[i][1]:
# position of the beam to touch the node, either yourself or the
# guard
beam=atan2((your_position[1]-k), (your_position[0]-j))
# pythagorean theorem gets you the hypothenuse given two points
# of a triangle, which is basically the distance between those
# two points
l=sqrt((your_position[0]-j)**2 + (your_position[1]-k)**2)
if [j,k] != your_position and distance >= l:
if((beam in angles_dist and angles_dist[beam] > l) or beam not in angles_dist):
if i == 0:
angles_dist[beam] = l
else:
angles_dist[beam] = l
# we could find a better result later so let's just
# put that in a set to sort out duplicates
res.add(beam)
return len(res)

+ 69
- 0
distract_the_guards.py View File

@@ -0,0 +1,69 @@
from fractions import gcd

"""
Ah, combinatronics maths, always fun.
Let's suppose we have two guards starting with a and b bananas, where we assume a > b
Let k be the largest positive integer such that a > (2^k - 1)b
If a = b(2^k - 2^(m-1) - 1) or a = b(2^(k+1) - 2^m - 1) for some integer m between 0 and k included
then we can match the two guards to play an infinite round of thumb wrestling.
We can thus deduce the formula below after development.
"""
def loops(x, y):
res = (x+y)/gcd(x,y)
return bool(res & (res - 1))

"""
This function might looks like a bottleneck but we need to find a
way to update node sizes either way, so i don't think a better way
is possible at the time of writing. I might totally be wrong but for our use
case arrays won't be bigger than 100 so *shrug*
"""
def remove(guards, ref):
for i in range(len(guards)):
j = 0
while j < len(guards[i]):
if(guards[i][j]==ref):
guards[i].pop(j)
j+=1
guards[ref]=[-1]

def answer(banana_list):
guards= [[] for i in range(len(banana_list))]
bad=0
for i in range(len(guards)):
for j in range(len(guards)):
if(loops(banana_list[i], banana_list[j])):
guards[i].append(j)

to_process=len(banana_list)
while(to_process>0):

# we first find the lesser used number
min_num=0
for i in range(len(guards)):
if(i!=0 and (len(guards[i])<len(guards[min_num]) or guards[min_num]
== [-1]) and guards[i]!=[-1]):
min_num=i

# if this node absolutely cannot be paired we purge it
if((len(guards[min_num])) == 0 or (len(guards[min_num])==1 and
guards[min_num][0] == guards[min_num]) and guards[min_num] !=
[-1]):
remove(guards, min_num)
to_process-=1
bad+=1
else:
# now we deduce the lesser used node associated to this number
min_node=guards[min_num][0]
for i in range(len(guards[min_num])):
if(i!=0 and guards[min_num][i]!=min_num and len(guards[guards[min_num][i]])<len(guards[min_node])):
min_node=guards[min_num][i]
# if we fail here infinite loop, which is bad because that means
# only processed nodes exist and we haven't exited; logic bug
if(guards[min_node]!=[-1]):
remove(guards, min_num)
remove(guards, min_node)
to_process-=2

return bad

+ 34
- 0
en_route_salute.py View File

@@ -0,0 +1,34 @@
"""
Almost no computation there, we get positions of arrows depending of their type
sorted and then do two nested loops to detect collisions, can't think of
anything better rn
"""
def answer(s):

# readme.txt
if(len(s) > 100 or len(s) < 1):
raise ValueError('Height is outside of bounds')
# we keep only the arrows and cast it into a list ot make it iterable easily
s = list(s.replace("-",""))
left = []
right = []
res=0

for i in range(0,len(s)):
if s[i] == '<':
left.append(i)
if s[i] == '>':
right.append(i)

# two nested loops to detect collision
for i in right:
for y in left:
if i < y:
res+=1
for i in left:
for y in right:
if y < i:
res+=1
return res

+ 8
- 0
end.py View File

@@ -0,0 +1,8 @@
import base64
encrypted="HEYGAwIKCxQSUlZbSUkAExAXFU5CR0YWGQ0FCwYGABNGSVRHRhAFFQwLCgQRUU1JSQIHExkTHR1AQU9WRgAABBMQEggLAgJGWVZGCA0PCBAABAQLCRVSVltJSRIPGRkCAgsDRllWRhsPBQMcAhJOTl1BUgUADwtATVVRBwYBQEFPVkYeBwlAUgs="
my_eyes=str.encode("gauvain")
decoded=base64.b64decode(encrypted)
decrypted=""
for i in range(0,len(decoded)):
decrypted+=chr((my_eyes[i%len(my_eyes)] ^ decoded[i]))
print(decrypted)

+ 41
- 0
expanding_nebula.py View File

@@ -0,0 +1,41 @@
"""
The hard part of this challenge was figuring out how to make it in the time
limit, much like the old XOR challenge. This one though was on another
optimization technique, memoization(I see what you did there Google, challenges
using the skills you ask for interviews on your website).
Hashing the entire list would have been too long, even as a tuple, so instead we
had to be clever and use another trick: building an history of all the moves that
happened.
This allows us, given a pair of row and column a and b, to avoid having to do
extreme amount of recursive computation to get a result, as long as an history
of the last (past grid row + current move) moves is saved.
The rest of the problem is fairly easy: we recursively try out all possible
combinations for this grid and save the number of correct ones we could get.
"""
def answer(state, a=0, b=0, past=0, solutions=0, history=0):
if(past==0):
past=[[True] * (len(state[0])+1) for i in range(len(state)+1)]
solutions = {}
history = []

if(b==len(state[0])+1):
return True

res=0
index=((a,b), tuple(history[-(len(state)+2):]))
if index in solutions:
return solutions[index]

for cell in [True, False]:
# either all True(c[0][0] and 1 cell) or all False (!c[0][0] and !1 cell)
if (not a or not b) or len(set([((past[a][b-1] + past[a-1][b]
+ past[a-1][b-1] + cell)==1), state[a-1][b-1]]))==1:
history.append(cell)
past[a][b] = cell
res+=answer(state, a=(a+1)%(len(state)+1),
b=b+(a+1)//(len(state)+1), past=past,
solutions=solutions, history=history)
history.pop()

solutions[index]=res
return res

+ 19
- 0
find_the_access_code.py View File

@@ -0,0 +1,19 @@
"""
This one was really simple, compared to the XORFest I did before: you just
get all the pairs that divide themselves with a larger number after, then do the
same again.
There's really nothing else to say, weird to see that as a level 3
"""
def answer(l):
triples = 0
pairs = [0]*len(l)

for i in range(1, len(l)-1):
for j in range(i):
if(l[i]%l[j]==0):
pairs[i]+=1
for i in range(2, len(l)):
for j in range(1, i):
if(l[i]%l[j]==0):
triples += pairs[j]
return triples

+ 26
- 0
fuel_injection_perfection.py View File

@@ -0,0 +1,26 @@
"""
Logic is pretty much self explained there, complexity should be also fairly low,
but heck was it tricky
"""
def answer(n):

# n is given as a string so let's cast it
n=int(n)
res = 0

while(n!=1):
# if our number is odd then let's just divide by 2
if(n%2==0):
n=n/2
# now this is the tricky part and took me a bunch of googling to figure
# out: instead of going the brute force O(n²) way and just trying out
# every possibility in nested loops you can actually figure out if you
# should add or remove: when rightmost bits are set as "111" the
# preferred operation is to add, as that would push back everything as
# 000s, while any other case would be faster the other way around.
elif((n==3) or ((n+1)&n) > ((n-1)&(n-2))):
n-=1
else:
n+=1
res+=1
return res

+ 44
- 0
ion_flux_relabeling.py View File

@@ -0,0 +1,44 @@
"""
Creating a perfect tree and going through it would be way over the top
for that, so we just do a recursive search calculating on the fly the values,
which results in an O(log(n)) complexity
"""
def find_item(item,cur,dif):
#the difference between the right node and the left
#node is the floor of the right node divided by 2: since we have a perfect
#tree our tree have the same number of nodes on the left side and right
#side; as such dividing by 2 should get you to the left one.
right_node=cur-1
left_node=right_node-dif//2

#Check readme.txt
if(right_node==item or left_node==item):
return cur
else:
#Cannot be on the right side, otherwise it'd be larger than the
#top left node thanks to perfect trees properties.
if(item<=left_node):
return find_item(item,left_node,dif//2)
else:
return find_item(item,right_node,dif//2)

#Check readme.txt for what this function does, almost no computation there
def answer(h, q):
#check readme.txt
if(h > 30 or h < 1):
raise ValueError('Height is outside of bounds')
if(len(q) > 10000 or len(q) < 1):
raise ValueError('Flux converters list is outside of bounds')


#Note: A perfect binary tree has 2n+1-1 nodes, where n is the height
#https://xlinux.nist.gov/dads/HTML/perfectBinaryTree.html
items=(2**h)-1

array=[]
for i in range(len(q)):
if (q[i]<items and q[i]>0):
array.append(find_item(q[i],items,items-1))
else:
array.append(-1)
return array

+ 35
- 0
queue_to_do.py View File

@@ -0,0 +1,35 @@
"""
This would have been fairly easy without the time limit but of course we must do
some XOR regression bs to get it working.

This was actually fairly simple maths once you think about it! XOR is a rotary
operator when dealing with lists of following integer, ie:
>>> 1
1
>>> 1^2
3
>>> 1^2^3
0
>>> 1^2^3^4
4
>>> 1^2^3^4^5
1
[...]
And thus we can deduce a pattern, noted below as the xor_rotation.
Thanks to that and XOR properties we can reduce the complexity of the original
dumb "XOR EVERYTHING" down to an O(1) for each array, so we end up roughly
having an O(length) complexity
"""

def xor(a, b):
if(a%2==0):
xor_rotation = [b, 1, b+1, 0]
else:
xor_rotation= [a, a^b, a-1, (a-1)^b]
return xor_rotation[(b-a)%4]

def answer(start, length):
res=0
for i in range(0, length):
res ^= xor(start+(length*i), start+(length*i)+(length-i)-1)
return res

+ 32
- 0
solar_doomsday.py View File

@@ -0,0 +1,32 @@
#I might over-comment those solutions since it might be
#reviewed one day, so bear with me, reviewers!
"""
Gets the biggest square below or equal to max_number
I'm sure it can be optimized but as we never get over 1M
it should be largely fast enough for most cpus:
➜ /tmp time python2 tmp.py
[1000000]
python2 tmp.py 0.01s user 0.00s system 96% cpu 0.009 total
The code is also more readable this way
"""
def get_biggest_square(max_number):
n=1
while(n*n < max_number+1):
n=n+1
return n-1

#Check readme.txt for an explaination of what this does.
# "Heavy" computation is done in get_biggest_square
def answer(area):
#check readme.txt
if(area > 1000000 or area < 1):
raise ValueError('Area is outside of bounds')
array=[]
#no need to sort or do fancy pant stuff, we go from biggest to smallest in
#a natural fashion this way
while(area != 0):
res=get_biggest_square(area)
array.append(res*res)
area-=res*res
return array

Loading…
Cancel
Save