I am having an issue where my “all_possible_cuts” array is changing when I append another cut to it.
The specific sections of my code with the issue is:
def append_to_all_possible_cuts(next_cuts):
"""Appends the next possible way to cut the material to the array \"all_possible_cuts\" """
Order.all_possible_cuts.append(next_cuts[:])
previous_cuts = next_cuts
return(previous_cuts)
...
Print(Order.all_possible_cuts)
Output: [[[19.125, 0], [16.125, 0]], [[19.125, 0], [16.125, 0]], ...[[19.125, 0], [16.125, 0]]]
Expected Output: [[19.125, 6], [16.125, 1]], [[19.125, 6], [16.125, 0]], [[19.125, 5], [16.125, 3]],...[[19.125, 0], [16.125, 0]]
I have found articles with people having the same issue, but I tried their solutions and it doesn’t seem to help.
Enter the inputs 144, 2, 19, 15, 16, 30
Here is my full code:
class Order:
purchase_lines = []
all_possible_cuts = []
def get_order_information():
"""Gathers all the starting bar length and cutting information from the order and sorts from largest cut to smallest cut."""
Order.get_starting_material_length()
Order.get_purchase_lines()
Order.purchase_lines = sorted(Order.purchase_lines, reverse=True)
def get_starting_material_length():
"""Gathers the length of the stock bar for the order. Returns starting lenght of the stock material."""
starting_material_length = int(input("What is the length of the stock (in inches)? "))
Order.starting_material_length = starting_material_length
def get_purchase_lines():
"""Gathers all the cut lengths and the quantities needed for each cut length. Adds a tolerance of 0.125 inches to each cutting length. Appends length + quantity needed to """
tolerance = 0.125
num_of_cut_lengths = int(input("How many different cut lengths are there? "))
for cut_length in range(num_of_cut_lengths):
length = float(input("What is the length of the cut (in inches)? "))
qty = int(input("How many " + str(length) + " in. cuts are needed? "))
Order.purchase_lines.append([(length + tolerance), qty])
def generate_all_possible_cuts():
"""Generates an array of every possible way to cut a single piece of stock material"""
first_possible_cut = Order.generate_first_possible_cut(Order.purchase_lines)
another_cut_possible = Order.determine_if_another_cut_possible(first_possible_cut)
previous_cuts = first_possible_cut
while another_cut_possible:
next_cuts = Order.generate_next_possible_cut(previous_cuts)
previous_cuts = Order.append_to_all_possible_cuts(next_cuts)
another_cut_possible = Order.determine_if_another_cut_possible(previous_cuts)
def generate_first_possible_cut(purchase_lines):
"""Using the cut lengths and quantities need, cutting the most pieces possible out of the stock material, starting with the first cut length entered, until the material has remainder of 0 or all cutting lengths have been attempted."""
possible_cut_length_and_qtys = []
remaining_material_length = Order.starting_material_length
for line in purchase_lines:
possible_cut_length = line[0]
max_qty = line[1]
possible_qty = int(remaining_material_length // possible_cut_length)
if possible_qty >= 0 and remaining_material_length - (possible_cut_length * possible_qty) >= 0:
if possible_qty > max_qty:
possible_cut_length_and_qtys.append([possible_cut_length, max_qty])
remaining_material_length -= possible_cut_length * max_qty
else:
possible_cut_length_and_qtys.append([possible_cut_length, possible_qty])
remaining_material_length -= possible_cut_length * possible_qty
Order.remaining_material_length = remaining_material_length
return(possible_cut_length_and_qtys)
def determine_if_another_cut_possible(previous_cuts):
"""Determines if another cut is possible. Returns True if possible, False if not possible."""
for cut in previous_cuts:
cut_qty = cut[1]
if cut_qty > 0:
return(True)
break
else:
return(False)
def generate_next_possible_cut(previous_cuts):
"""Using the previous counts, generates the next possible cut."""
index_of_lowered_digit, lowered_cuts = Order.lower_smallest_digit_possible(previous_cuts)
Order.increase_remaining_material_length(index_of_lowered_digit)
next_cuts = Order.use_remaining_stock(index_of_lowered_digit, lowered_cuts)
return(next_cuts)
def append_to_all_possible_cuts(next_cuts):
"""Appends the next possible way to cut the material to the array \"all_possible_cuts\" """
Order.all_possible_cuts.append(next_cuts[:])
previous_cuts = next_cuts
return(previous_cuts)
def lower_smallest_digit_possible(previous_cuts):
"""Using the previous counts, generates the next possible cut."""
current_cut_index = len(previous_cuts) - 1
while current_cut_index >= 0:
if previous_cuts[current_cut_index][1] > 0:
previous_cuts[current_cut_index][1] -= 1
break
else:
current_cut_index -= 1
return(current_cut_index, previous_cuts)
def increase_remaining_material_length(index_of_lowered_digit):
"""Using the previous counts, generates the next possible cut."""
Order.remaining_material_length += Order.purchase_lines[index_of_lowered_digit][0]
def use_remaining_stock(index_of_lowered_digit, lowered_cuts):
"""Using the previous counts, generates the next possible cut."""
current_index = index_of_lowered_digit + 1
while current_index < len(lowered_cuts):
if Order.remaining_material_length >= lowered_cuts[current_index][0]:
qty = int(Order.remaining_material_length // lowered_cuts[current_index][0])
lowered_cuts[current_index][1] += qty
Order.remaining_material_length -= lowered_cuts[current_index][0] * qty
current_index += 1
return(lowered_cuts)
Order.get_order_information()
Order.generate_all_possible_cuts()
Looks like you are reusing arrays (a.k.a. Python lists) when you meant to be copying them.
To quickly prove this, I changed every return statement in the code that was returning an array to instead return copy.deepcopy(a)
where a
is the name of the array. The changed code then produced the expected output.
The full changed code is included at the bottom of this answer.
To help explain the problem in general, consider this sample code:
a = [0, 1, 2, 3, 4]
b = [a, a, a, a]
print(b)
a[0] = 123
print(b)
The output of the sample code is:
[[0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4]]
[[123, 1, 2, 3, 4], [123, 1, 2, 3, 4], [123, 1, 2, 3, 4], [123, 1, 2, 3, 4]]
The reason is because the a
array is never copied into b
. The b
array only gets 4 copies of the name a
, not the whole array named a
. More formally, this means that a
was used 'by reference'.
Full changed code:
import copy
class Order:
purchase_lines = []
all_possible_cuts = []
def get_order_information():
"""Gathers all the starting bar length and cutting information from the order and sorts from largest cut to smallest cut."""
Order.get_starting_material_length()
Order.get_purchase_lines()
Order.purchase_lines = sorted(Order.purchase_lines, reverse=True)
def get_starting_material_length():
"""Gathers the length of the stock bar for the order. Returns starting lenght of the stock material."""
starting_material_length = int(input("What is the length of the stock (in inches)? "))
Order.starting_material_length = starting_material_length
def get_purchase_lines():
"""Gathers all the cut lengths and the quantities needed for each cut length. Adds a tolerance of 0.125 inches to each cutting length. Appends length + quantity needed to """
tolerance = 0.125
num_of_cut_lengths = int(input("How many different cut lengths are there? "))
for cut_length in range(num_of_cut_lengths):
length = float(input("What is the length of the cut (in inches)? "))
qty = int(input("How many " + str(length) + " in. cuts are needed? "))
Order.purchase_lines.append([(length + tolerance), qty])
def generate_all_possible_cuts():
"""Generates an array of every possible way to cut a single piece of stock material"""
first_possible_cut = Order.generate_first_possible_cut(Order.purchase_lines)
another_cut_possible = Order.determine_if_another_cut_possible(first_possible_cut)
previous_cuts = first_possible_cut
while another_cut_possible:
next_cuts = Order.generate_next_possible_cut(previous_cuts)
previous_cuts = Order.append_to_all_possible_cuts(next_cuts)
another_cut_possible = Order.determine_if_another_cut_possible(previous_cuts)
def generate_first_possible_cut(purchase_lines):
"""Using the cut lengths and quantities need, cutting the most pieces possible out of the stock material, starting with the first cut length entered, until the material has remainder of 0 or all cutting lengths have been attempted."""
possible_cut_length_and_qtys = []
remaining_material_length = Order.starting_material_length
for line in purchase_lines:
possible_cut_length = line[0]
max_qty = line[1]
possible_qty = int(remaining_material_length // possible_cut_length)
if possible_qty >= 0 and remaining_material_length - (possible_cut_length * possible_qty) >= 0:
if possible_qty > max_qty:
possible_cut_length_and_qtys.append([possible_cut_length, max_qty])
remaining_material_length -= possible_cut_length * max_qty
else:
possible_cut_length_and_qtys.append([possible_cut_length, possible_qty])
remaining_material_length -= possible_cut_length * possible_qty
Order.remaining_material_length = remaining_material_length
return copy.deepcopy(possible_cut_length_and_qtys)
def determine_if_another_cut_possible(previous_cuts):
"""Determines if another cut is possible. Returns True if possible, False if not possible."""
for cut in previous_cuts:
cut_qty = cut[1]
if cut_qty > 0:
return(True)
break
else:
return(False)
def generate_next_possible_cut(previous_cuts):
"""Using the previous counts, generates the next possible cut."""
index_of_lowered_digit, lowered_cuts = Order.lower_smallest_digit_possible(previous_cuts)
Order.increase_remaining_material_length(index_of_lowered_digit)
next_cuts = Order.use_remaining_stock(index_of_lowered_digit, lowered_cuts)
return copy.deepcopy(next_cuts)
def append_to_all_possible_cuts(next_cuts):
"""Appends the next possible way to cut the material to the array \"all_possible_cuts\" """
Order.all_possible_cuts.append(next_cuts[:])
previous_cuts = next_cuts
return copy.deepcopy(previous_cuts)
def lower_smallest_digit_possible(previous_cuts):
"""Using the previous counts, generates the next possible cut."""
current_cut_index = len(previous_cuts) - 1
while current_cut_index >= 0:
if previous_cuts[current_cut_index][1] > 0:
previous_cuts[current_cut_index][1] -= 1
break
else:
current_cut_index -= 1
return(current_cut_index, copy.deepcopy(previous_cuts))
def increase_remaining_material_length(index_of_lowered_digit):
"""Using the previous counts, generates the next possible cut."""
Order.remaining_material_length += Order.purchase_lines[index_of_lowered_digit][0]
def use_remaining_stock(index_of_lowered_digit, lowered_cuts):
"""Using the previous counts, generates the next possible cut."""
current_index = index_of_lowered_digit + 1
while current_index < len(lowered_cuts):
if Order.remaining_material_length >= lowered_cuts[current_index][0]:
qty = int(Order.remaining_material_length // lowered_cuts[current_index][0])
lowered_cuts[current_index][1] += qty
Order.remaining_material_length -= lowered_cuts[current_index][0] * qty
current_index += 1
return copy.deepcopy(lowered_cuts)
Order.get_order_information()
Order.generate_all_possible_cuts()
print(Order.all_possible_cuts)
# test with: 144, 2, 19, 15, 16, 30