Reference
When solving puzzles from websites, I always include the link and the task description.
In case of Project Euler questions, this already helped me figure out that their tasks change over time.
In your case, such a reference could look like
# https://www.geeksforgeeks.org/generate-all-possible-sorted-arrays-from-alternate-elements-of-two-given-arrays/
# Given two sorted arrays A and B, generate all possible arrays such that first element is taken from A then from B then from A and so on in increasing order till the arrays exhausted. The generated arrays should end with an element from B.
Redundant comparisons
In your code I see if xxx == True: which can be shortened to if xxx:. I wrote the same code as a beginner, so I guess it's absolutely normal.
Similarly, if xxx == False: would be written as if not xxx:.
An IDE like PyCharm will give you hints for such issues and even help you replace it by an equivalent.
Unnecessary continue statement
if print_valid == False:
continue
This part is unnecessary, since it's the last statement of the loop, so the loop would continue anyway.
Separate output from logic
Do the calculations on their own, then do all the printing. E.g. define a function which prints the lists as you like to:
def print_all(results: List) -> None:
for result in results:
for item in result:
print(item, sep=" ", end=" ")
print()
Type safety
You can from typing import *and use type hints to make clear what types are used. This is especially useful when using functions (see next section).
Use testable functions
Right now, you have some input which gives some output, but you don't have a test whether your code works for the given input. The website already indicates that there is a defined set of solutions for the given input A = {10, 15, 25}and B = {1, 5, 20, 30}.
You could implement it like this:
def get_potential_solutions() -> List:
list_solutions = []
...
return list_solutions = []
def filter_solutions(list_solutions: List) -> List:
# Print only valid solutions:
valid_results = []
current_result = []
for item in list_solutions:
...
return valid_results
list_solutions = get_potential_solutions(A, B)
list_solutions = filter_solutions(list_solutions)
print_all(list_solutions)
You can then implement an assertion which will warn you whenever you broke your code.
givensolution = [[10, 20], [10, 30], [15, 20], [15, 30], [25, 30], [10, 20, 25, 30], [15, 20, 25, 30]]
solution = filter_solutions(get_potential_solutions([10, 15, 25], [1, 5, 20, 30]))
assert (solution == givensolution)
If you do this many times, read about unit tests.
Naming
I still didn't understand the algorithm you implemented. It may have to do with the terms x, item, index, newA and newB, itemA and itemB, which tell me nothing.
x is used in itertools.combinations(), so it must be the length
newA and newB are combinations, so I renamed them to combinationsA and combinationsB
itemA and itemB are a specific combination, so I renamed them to combinationA and combinationB
You may say that this is not an improvement. I'd say I moved from a nonsense name to a honest name, which is at least one step better, but still on level 2 of the 6 stages of naming
Doubled condition
IMHO, the condition in get_potential_solutions()
if combinationA[position] >= combinationB[position]:
valid = False
break
is identical to the condition in filter_solutions()
if item[0][index] >= item[1][index]:
valid = False
break
Since it is about filtering, I'd prefer to remove it in the potentials method.
Make smaller methods
The check whether a potential solution is valid or not can be moved into its own method.
def is_valid_solution(potential):
for index in range(len(potential[0])):
if potential[0][index] >= potential[1][index]:
return False
if index >= 1:
if potential[0][index] <= potential[1][index - 1]:
return False
return True
The next loop seems to just clean up the results in order to remove the tupels. This can be done in a method as well:
def flatten(potential):
current_result = []
for index in range(len(potential[0])):
current_result.append(potential[0][index])
current_result.append(potential[1][index])
return current_result
Single responsibility
The filter_solutions() method now does 2 things: filtering and flattening. One could argue that this should be separated. And it's simple to do now.
Final result
import itertools
from typing import *
A = [10, 20, 30, 40]
B = [11, 21, 31, 41]
def get_potential_solutions(a: List, b: List) -> List:
potentials = []
for length in range(min(len(a), len(b))):
combinationsA = list(itertools.combinations(a, length + 1))
combinationsB = list(itertools.combinations(b, length + 1))
for combinationA in combinationsA:
for combinationB in combinationsB:
potentials.append([combinationA, combinationB])
return potentials
def filter_solutions(potentials: List) -> List:
valid_results = []
for potential in potentials:
if is_valid_solution(potential):
valid_results.append(potential)
return valid_results
def is_valid_solution(potential):
for index in range(len(potential[0])):
if potential[0][index] >= potential[1][index]:
return False
if index >= 1:
if potential[0][index] <= potential[1][index - 1]:
return False
return True
def flatten_list(solutions: List) -> List:
result = []
for solution in solutions:
result.append(flatten(solution))
return result
def flatten(potential):
current_result = []
for index in range(len(potential[0])):
current_result.append(potential[0][index])
current_result.append(potential[1][index])
return current_result
def print_all(results: List) -> None:
for result in results:
for item in result:
print(item, sep=" ", end=" ")
print()
givensolution = [[10, 20], [10, 30], [15, 20], [15, 30], [25, 30], [10, 20, 25, 30], [15, 20, 25, 30]]
solution = flatten_list(filter_solutions(get_potential_solutions([10, 15, 25], [1, 5, 20, 30])))
assert (solution == givensolution)
list_solutions = get_potential_solutions(A, B)
list_solutions = filter_solutions(list_solutions)
print_all(flatten_list(list_solutions))
selector ^ 1moment? I have added explanation to the answer. \$\endgroup\$