Usa Python per risolvere il problema aziendale di un ente di beneficenza
Confrontare come diversi linguaggi di programmazione risolvono lo stesso problema è divertente e istruttivo. Il prossimo passo è Python.
Nel mio primo articolo di questa serie, ho descritto il problema di dividere le forniture sfuse in cesti di valore simile da distribuire ai vicini in difficoltà nella tua comunità. Ho anche scritto di come mi diverto a risolvere piccoli problemi come questo con piccoli programmi in varie lingue e di confrontare come lo fanno.
Nel primo articolo ho risolto questo problema con il linguaggio di programmazione Groovy. Groovy è come Python in molti sensi, ma sintatticamente è più simile a C e Java. Pertanto, dovrebbe essere interessante e istruttivo creare la stessa soluzione in Python.
La soluzione Python
In Java, dichiaro che le classi di utilità contengono tuple di dati (la nuova funzionalità di record sarà eccezionale per questo). In Groovy utilizzo il supporto linguistico per le mappe e seguo lo stesso approccio in Python.
Utilizza un elenco di dizionari per conservare gli articoli sfusi ritirati dal grossista:
packs = [
{'item':'Rice','brand':'Best Family','units':10,'price':5650,'quantity':1},
{'item':'Spaghetti','brand':'Best Family','units':1,'price':327,'quantity':10},
{'item':'Sardines','brand':'Fresh Caught','units':3,'price':2727,'quantity':3},
{'item':'Chickpeas','brand':'Southern Style','units':2,'price':2600,'quantity':5},
{'item':'Lentils','brand':'Southern Style','units':2,'price':2378,'quantity':5},
{'item':'Vegetable oil','brand':'Crafco','units':12,'price':10020,'quantity':1},
{'item':'UHT milk','brand':'Atlantic','units':6,'price':4560,'quantity':2},
{'item':'Flour','brand':'Neighbor Mills','units':10,'price':5200,'quantity':1},
{'item':'Tomato sauce','brand':'Best Family','units':1,'price':190,'quantity':10},
{'item':'Sugar','brand':'Good Price','units':1,'price':565,'quantity':10},
{'item':'Tea','brand':'Superior','units':5,'price':2720,'quantity':2},
{'item':'Coffee','brand':'Colombia Select','units':2,'price':4180,'quantity':5},
{'item':'Tofu','brand':'Gourmet Choice','units':1,'price':1580,'quantity':10},
{'item':'Bleach','brand':'Blanchite','units':5,'price':3550,'quantity':2},
{'item':'Soap','brand':'Sunny Day','units':6,'price':1794,'quantity':2}]
C'è una confezione sfusa da 10 sacchetti di riso e 10 confezioni sfuse con un sacchetto ciascuna di spaghetti. Nell'esempio sopra, la variabile packs
è impostata su un elenco di dizionari Python. Questo risulta essere molto simile all'approccio Groovy. Alcuni punti degni di nota sulla differenza tra Groovy e Python:
- In Python, non esiste una parola chiave utilizzata per definire la variabile
packs
; Python si aspetta che al primo utilizzo venga impostato un valore. - Chiavi del dizionario Python (ad esempio,
item
,brand
,units
,price
,quantity
) richiedono virgolette per indicare che sono stringhe; Groovy presuppone che si tratti di stringhe, ma accetta anche virgolette. - In Python, la notazione
{ … }
indica una dichiarazione di dizionario; Groovy utilizza le stesse parentesi quadre di un elenco, ma la struttura in entrambi i casi deve avere coppie chiave-valore.
E sì, quei prezzi non sono in dollari USA.
Successivamente, disimballa i pacchetti sfusi. Disimballando il singolo pacco sfuso di riso, ad esempio, si otterranno 10 unità di riso; ovvero, il numero totale di unità prodotte è unità * quantità
. Lo script Groovy utilizza una pratica funzione chiamata collectMany
che può essere utilizzata per appiattire elenchi di elenchi. Per quanto ne so, Python non ha nulla di simile, quindi usa due list comprehension per produrre lo stesso risultato:
units = [[{'item':pack['item'],'brand':pack['brand'],
'price':(pack['price'] / pack['units'])}] *
(pack['units'] * pack['quantity']) for pack in packs]
units = [x for sublist in units for x in sublist]
La prima comprensione dell'elenco (assegnazione alle unità) costruisce l'elenco degli elenchi dei dizionari. Il secondo lo "appiattisce" solo in un elenco di dizionari. Tieni presente che sia Python che Groovy forniscono un operatore *
che accetta un elenco a sinistra e un numero N
a destra e replica l'elenco N
volte.
Il passaggio finale consiste nel reimballare le unità nei cesti per la distribuzione. Come nella versione Groovy, devi essere un po' più specifico riguardo al valore ideale del cesto, e potresti anche non essere eccessivamente restrittivo quando scendi a poche unità rimaste:
valueIdeal = 5000
valueMax = valueIdeal * 1.1
OK! Riconfezionare i cesti:
import random
hamperNumber = 0 # [1]
while len(units) > 0: # [2]
hamperNumber += 1
hamper = []
value = 0
canAdd = True # [2.1]
while canAdd: # [2.2]
u = random.randint(0,len(units)-1) # [2.2.1]
canAdd = False # [2.2.2]
o = 0 # [2.2.3]
while o < len(units): # [2.2.4]
uo = (u + o) % len(units)
unit = units[uo]
unitPrice = unit['price'] # [2.2.4.1]
if len(units) < 3 or not (unit in hamper) and (value + unitPrice) < valueMax:
# [2.2.4.2]
hamper.append(unit)
value += unitPrice
units.pop(u) # [2.2.4.3]
canAdd = len(units) > 0
break # [2.2.4.4]
o += 1 # [2.2.4.5]
# [2.2.5]
print('')
print('Hamper',hamperNumber,'value',value)
for item in hamper:
print('%-25s%-25s%7.2f' % (item['item'],item['brand'],item['price'])) # [2.3]
print('Remaining units',len(units)) # [2.4]
Alcuni chiarimenti, con numeri tra parentesi nei commenti sopra (ad esempio, [1]) corrispondenti ai chiarimenti seguenti:
- 1. Importa le strutture del generatore di numeri casuali di Python e inizializza il numero del cesto.
- 2.1 Incrementa il numero del cesto, ottieni un nuovo cesto vuoto (un elenco di unità) e imposta il suo valore su 0; inizia supponendo che tu possa aggiungere più oggetti al cesto.
- 2.2.1 Ottieni un numero casuale compreso tra zero e il numero di unità rimanenti meno 1.
- 2.2.2 Supponiamo che non riesci a trovare più unità da aggiungere.
- 2.2.3 Crea una variabile da utilizzare per l'offset dal punto di partenza in cui stai cercando gli oggetti da mettere nel cesto.
- 2.2.4.1. Scopri quale unità guardare (punto di partenza casuale + offset) e ottieni il suo prezzo.
- 2.2.4.2 Puoi aggiungere questa unità al cesto se ne rimangono solo poche o se il valore del cesto non è troppo alto una volta aggiunta l'unità.
- 2.2.4.3 Aggiungere l'unità al cesto, incrementare il valore del cesto del prezzo unitario, rimuovere l'unità dall'elenco delle unità disponibili.
- 2.2.4.4 Finché rimangono unità, puoi aggiungerne altre, quindi esci da questo ciclo per continuare a cercare.
- 2.2.4.5 Incrementare l'offset.
while
, se hai ispezionato ogni unità rimanente e non sei riuscito a trovarne una da aggiungere al cesto, il cesto è completo; altrimenti ne hai trovato uno e puoi continuare a cercarne altri.Quando esegui questo codice, l'output sembra abbastanza simile all'output del programma Groovy:
Hamper 1 value 5304.0
UHT milk Atlantic 760.00
Tomato sauce Best Family 190.00
Rice Best Family 565.00
Coffee Colombia Select 2090.00
Sugar Good Price 565.00
Vegetable oil Crafco 835.00
Soap Sunny Day 299.00
Remaining units 148
Hamper 2 value 5428.0
Tea Superior 544.00
Lentils Southern Style 1189.00
Flour Neighbor Mills 520.00
Tofu Gourmet Choice 1580.00
Vegetable oil Crafco 835.00
UHT milk Atlantic 760.00
Remaining units 142
Hamper 3 value 5424.0
Soap Sunny Day 299.00
Chickpeas Southern Style 1300.00
Sardines Fresh Caught 909.00
Rice Best Family 565.00
Vegetable oil Crafco 835.00
Spaghetti Best Family 327.00
Lentils Southern Style 1189.00
Remaining units 135
…
Hamper 21 value 5145.0
Tomato sauce Best Family 190.00
Tea Superior 544.00
Chickpeas Southern Style 1300.00
Spaghetti Best Family 327.00
UHT milk Atlantic 760.00
Vegetable oil Crafco 835.00
Lentils Southern Style 1189.00
Remaining units 4
Hamper 22 value 2874.0
Sardines Fresh Caught 909.00
Vegetable oil Crafco 835.00
Rice Best Family 565.00
Rice Best Family 565.00
Remaining units 0
L'ultimo cesto è abbreviato nel contenuto e nel valore.
Pensieri conclusivi
A prima vista, non c'è molta differenza tra le versioni Python e Groovy di questo programma. Entrambi hanno un insieme simile di costrutti che rendono la gestione di elenchi e dizionari molto semplice. Nessuno dei due richiede molti "codici standard" o altre azioni "cerimoniali".
Inoltre, come nell'esempio di Groovy, c'è qualche complicazione nel poter aggiungere unità al cesto. Fondamentalmente, scegli una posizione casuale nell'elenco delle unità e, partendo da quella posizione, scorri l'elenco finché non trovi un'unità il cui prezzo ne consente l'inclusione o finché non esaurisci l'elenco. Inoltre, quando rimangono solo pochi oggetti, li getti nell'ultimo cesto.
Un altro problema degno di nota: questo non è un approccio particolarmente efficiente. Rimuovere elementi dalle liste, non prestare attenzione alle espressioni ripetute e poche altre cose lo rendono meno adatto a un enorme problema di ridistribuzione. Tuttavia, funziona in un batter d'occhio sulla mia vecchia macchina.
Se tremi per il mio utilizzo dei cicli while
e per la modifica dei dati in questo codice, probabilmente vorresti che lo rendessi più funzionale. Non riuscivo a pensare a un modo per utilizzare map e ridurre le funzionalità in Python insieme a una selezione casuale di unità per il riconfezionamento. Puoi?
Nel prossimo articolo, lo rifarò in Java solo per vedere quanto meno sforzo sono Groovy e Python, e gli articoli futuri riguarderanno Julia e Go.