I recently did Day 5 of Advent of Code 2022 (spoilers for the code down below) and for some reason, the list of stacks that I was passing through to the function for Part A was changing the original list and then failing for part B. Part A and Part B were done in separate python files (A.py, B.py) which I then referenced to in the main file (main.py) to clear up my code. I did some research and could not work out why Python was changing the original list but I could tell I needed to make an explicit copy, which I never had to do before and I was wondering if anyone could tell me why. I made the explicit copy by turning the list into a tuple, passing it through, and then converting it back into a list.
Running my code now will pull up an error and that is because stacks
is changed to the final stacks of part A even though it shouldn't. Adding stacks = tuple(stacks)
to main.py and then data = list(data)
to the two sub-programs fixes it but I want to know why it is changing the stacks list from main.py without me asking it to.
Any help is appreciated. Just want to understand why python is doing this and whether it is me or an inbuilt feature of python.
main.py:
import A
import B
with open("input.txt", "r") as file:
data = [_ for _ in file.read().split("\n")]
stacks = []
controls = []
stackData = data[:8]
for i in range(9):
stack = ""
for row in stackData:
box = row[(i*4 1)]
stack = box
stacks.append(stack)
for i in range(len(stacks)):
stacks[i] = stacks[i].strip()
controlData = data[10:]
for row in controlData:
control = row.split(" ")
amount = int(control[1])
spot = int(control[3])
destination = int(control[5])
controls.append([amount, spot, destination])
print(f"Answer to section A is: {A.calculate(stacks, controls)}")
print(f"Answer to section B is: {B.calculate(stacks, controls)}")
A.py:
def calculate(data, controls):
answer = ""
for control in controls:
amount = control[0]
start = control[1] - 1
destination = control[2] - 1
boxes = data[start][:amount]
boxes = boxes[::-1]
data[start] = data[start][amount:]
data[destination] = boxes data[destination]
for stack in data:
answer = stack[0]
return answer
B.py:
def calculate(data, controls):
answer = ""
for control in controls:
amount = control[0]
start = control[1] - 1
destination = control[2] - 1
boxes = data[start][:amount]
data[start] = data[start][amount:]
data[destination] = boxes data[destination]
for stack in data:
answer = stack[0]
return answer
CodePudding user response:
Good question! And so, you already suspect that this is a built in feature of python. Python passes everything by value. However, when you pass any data structure, the function being called can change the contents of parameter (but cannot change the parameter itself).
Here's the "offending" lines in A.calculate():
data[start] = data[start][amount:]
data[destination] = boxes data[destination]
It appears as if the B.caculate() also modifies the contents of data:
data[start] = data[start][amount:]
data[destination] = boxes data[destination]
And as you guessed, you can correct this by passing a "cloned" list to the calculate functions.
Here's some sample code where I test cloning a list:
a = [ 1 ,2 , 3]
def dellast( l ):
del l[-1]
print( "in dellast: " , l )
dellast(list(a))
print(a)
The output is this:
in dellast: [1, 2]
[1, 2, 3]
And so, if you would to correct your program, try this edit:
print(f"Answer to section A is: {A.calculate(list(stacks), controls)}")
print(f"Answer to section B is: {B.calculate(list(stacks), controls)}")
Actually, the second "clone" is not required since you are done with the data at this point, but you might as well add it to avoid this problem with any additions you make to the code.
Perhaps, it could be argued that you can solve this problem once and forever by just putting a copy at the beginning of your calculate functions. For example:
def calculate(data, controls):
data = list(data)