Bra att du fått ihop en lösning.
Passar på att lämna lite "lösa" kommentarer.
Det är god praxis att i huvudprogrammet wrappa alla kod i en metod, kalla den vad du vill och sedan använda:
Kod: Markera allt
if __name__ == "__main__":
det_namn_du_satte()
Anledningen är att alla som använder din python-modul och importerar den filen automagiskt kommer få sidoeffekten att alla kod faktiskt exekveras. Vilket kanske inte är önskvärt.
Vidare så fundera noga om du verkligen behöver wrappa din modell i en klass.
I python är det lätt gjort och ofta landar man i att man skapar "fusk-objekt" mest beståendes av metoder.
Använder man klasser så ska det finnas en poäng med det och inte bara rakt av exekvera en massa kod i initieringen av en klass.
I detta exempel (som mycket väl kan vara väldigt kondenserat) så skulle jag starkt överväga att bara ha en metod likt:
Kod: Markera allt
def find_optimal_component_values(expression, variables, series, goal):
...
När jag ändå nosar på moduler, klasser etc. så är python trevlig då det finns så mycket färdigt att använda. Man bör överväga vad "kostnaden" är att ta in en dependency (storlek, maintenance, uppdateringar etc.) mot nyttan.
Just i det exempel du postar så förstår jag att du började från en numpy-lösning som var minnesintensiv till att sedan göra en mer iterativ lösning. Vidare har du använt sympy för att på ett smidigt sätt uttrycka ekvationer. Dessa använder ca 14MB respektive 6MB och de används tämligen sparsmakat. Jag hade övervägt att klara mig utan dessa.
När du jobbar med enklare typer av formler så kan du enkelt definiera dom som vanliga python-metoder (eller lambda-metoder).
En till punkt som man kan disktuera är iterationer i python. Ofta när man ser.
Kod: Markera allt
for i in range(len(some_list)):
do_stuff(some_list[i])
eller
Kod: Markera allt
i = 0
while i < len(range(some_list)):
do_stuff(some_list[i])
i += 1
så är det i 99 fall av 100 pga vilken bakrund man har. Det är (nästan) alltid renare och enklare att följa med då man låter python få iterera över objekt likt:
om man behöver ha ut ett index samtidigt för status eller vad som helst så överväg att istället använda enumerate() på iteratorn:
Kod: Markera allt
for index, value in enumerate(some_list):
print(f"working on {index}")
do_stuff(value)
Så till sist en hastigt omskriven version (utan utskrifter etc. för att fokusera mer på koden
Kod: Markera allt
from functools import reduce
from itertools import product
def calculate_best(expression, series: list, goal: float):
min_error = float("inf")
results = set()
if len(series) != 2:
print("ERROR in init of model/BestCombinations")
return
for x1, x2 in product(*series):
error = abs(goal - expression(x1, x2))
if x2 < x1:
x1, x2 = x2, x1
if error < min_error:
min_error = error
results = {(x1, x2)}
elif error == min_error:
results.add((x1, x2))
return results
def main():
def expression(x1: float, x2: float):
return x1+x2
def add_multiples(s: set, n: float):
s.update(list(map(lambda v: v*n, ser1)))
ser1 = set((0, 1, 1.5, 2.2, 3.3, 4.7, 6.8))
add_multiples(ser1, 10)
add_multiples(ser1, 100)
ser2 = set((0, 1, 2.2, 4.7))
add_multiples(ser2, 10)
add_multiples(ser2, 100)
result = calculate_best(expression, [ser1, ser2], 56)
print(f"{result=}")
if __name__ == "__main__":
main()
Hoppas du fortsatt gillar python och att du fortsätter att lösa spännande problem
PS. problemet du löser är en variant på "Coin Change Problem" som man klassiskt löser med dynamisk programmering. En fördel med att göra denna uttömmande sökning är att du hittar alla lösningar och att du kan knyta vikter/kostnader till varje motstånd och för varje lösning räkna ut den "bästa" (den som använder billigast motstånd, den som använder motstånd som finns på lager, den som använder motstånd som det finns gott om på lager, etc.... möjligheterna är "oändliga").
DS