Kirjautuminen

Haku

Tehtävät

Keskustelu: Yleinen keskustelu: Tilauksen optimointi: erillisiä tuotteita vastaavien pakettien valinta

Sivun loppuun

Kevins16 [11.01.2017 20:42:35]

#

Heellou!

En ole varma kuinka aktiivinen Ohjelmointiputka nykyään on mutta nakkaampa pulmani tänne. Elikkäs ongelma, joka itselle on tullut vastaan enkä ole osannut ratkaista on seuraavanlainen; Minulla on neljä eri laatikkoa joissa kussakin on eri sisältöä vaihtelevissa määrissä:

Laatikko A
  Pikkulaatikko A x 20
  Pikkulaatikko B x 10
  Pikkulaatikko C x 5
  Pikkulaatikko D x 2
Laatikko B
  Pikkulaatikko B x 15
  Pikkulaatikko C x 10
  Pikkulaatikko D x 7
Laatikko C
  Pikkulaatikko A x 5
  Pikkulaatikko C x 15
  Pikkulaatikko D x 20
Laatikko D
  Pikkulaatikko A x 5
  Pikkulaatikko E x 10

Minulle on esitetty tilaus tietyistä määrästä pikkulaatikoita, mutta tilaukset lähetetään isoissa laatikoissa(Laatikot A, B, C, D).

Kuinka lasken Laatikko A, B, C ja D määrät mahdollisimman lähelle pyydyä tilausta pikkulaatikoita(nollan yläpuolella muttei ali):
Pikkulaatikko A x 300
Pikkulaatikko B x 140
Pikkulaatikko C x 120
Pikkulaatikko D x 70
Pikkulaatikko E x 80

Tämä onkelma on päätä vaivannut pitkään eikä ole ratkennut. Moista ohjelmaa olisin kirjoittamassa eri kielissä ja ongelma on se etten tiedä kuinka moinen laskusuoritus pitäisi kirjoittaa.

Työ kaikkivaltiaat Koodiwelhot ja Matematiikan maisterit kaikki apu otetaan vastaan! Kiitoksia etukäteen!

P.S. Jos kirjoitus on liian epäselvässä muodossa niin voin tarpeen tullen kirjoittaa sen uusiksi eri esimerkkiä käyttäen, mutta ongelma kuitenkin pysyy samana.

Metabolix [11.01.2017 21:16:55]

#

Mikä tarkalleen on ”paras” ratkaisu? Pitääkö tavaraa olla mieluummin liikaa vai liian vähän? Onko parempi olla 1×A liikaa vai 1×B liian vähän? Onko parempi olla 2×A liikaa vain 1×A ja 1×B liikaa, vai onko mitään väliä?

Kevins16 [11.01.2017 21:25:32]

#

Ah! Pahoittelen etten tuota maininnut tarkemmin! Eli täydellinen tulos olisi jossa isompien laatikoiden määrä olisi just eikä melkein, jolloin Pikkulaatikoita olisi tasan tarkalleen pyydetty määrä. Jos moinen ei ole mahdollista niin pikkulaatikoita(pikkulaatikko A, B, C, D, E) voi olla enemmän.

Eli tulos olisi joko tasan pyydetty määrä tai lähin mahdollinen jokaisen pikkulaatikon kohdalla(positiivisena eikä negatiivisena). En ole varma selvensikö kuinka paljon.

Isot laatikot ovat aina kokonaisina eivätkä koskaan desimaaleina

Antti Laaksonen [11.01.2017 21:44:37]

#

Hyvä algoritmi ongelman ratkaisuun riippuu paljon siitä, millaisia laatikot ja tilaukset ovat tarkalleen.

Jos esimerkki on lähellä todellisuutta, voisi riittää ihan vain käydä läpi kaikki mahdollisuudet valita laatikoita. Voiko esimerkiksi olettaa, että mitään isoa laatikkoa ei tarvita yli 50 kappaletta ratkaisussa?

Monia algoritmeja on olemassa, mutta ensin olisi hyvä tietää tarkat rajoitteet ongelmalle.

Kevins16 [11.01.2017 22:10:43]

#

Hhmm... no koko homman idea on että isojen laatikkojen on sisältö on aina vakio ja niiden määrälle ei ole mitään max. määrää, mutta tilaajalle annetaan kuitenkin pienin mahdollinen määrä joka täyttää pyydettyjen pienten laatikkojen(A-E) määrät.

Pienten laatikkojen määrät on tilauksissa miljoonissa ja monesti biljoonissa.

Jaska [12.01.2017 12:13:28]

#

Eikö tuo mene lineaarisella Diofantoksen yhtälöllä? Siis jos on A' kpl laatikoita A, B' kpl laatikoita B jne., niin A-laatikoista saadaan yhtälö muotoa 20A+5C+5D=E, ja samoin muille yhtälöille. Lineaarisille Diofantoksen yhtälöille voidaan käyttää Eukleideen algoritmia. Kahden tuntemattoman tapaus tuli aikoinaan lukiossa, useamman muuttujan tapauksessa ainakin Mapleen on koodattu ratkaisija.

Grez [12.01.2017 13:51:41]

#

Mun mielestä tossa tulee turhaa hankaluutta ymmärtää tehtävä oikein siitä että käytetään samoja kirjaimia isoille ja pienille laatikoille. Miksei isot voisi olla esim. K-N

Eli
K = 20A + 10B + 5C +2D
L = 15B + 10C + 7D
M = 5A + 15C +20D
N = 5A + 10E

Täsmälleenhän noita ei useimmissa tapauksissa saa menemään, mutta jos ymmärsin oikein niin pitäisi saada mahdollisimman pieni ei-negatiivinen määrä K-N, jolla saataisiin pyydetyt A-E:t niin että jotain mahdollisesti jää yli, mutta yhtään ei jää puuttumaan.

Periaatteessa jos on yksinkertainen tapaus, että tarvitaan 100 000 E-laatikkoa, niin lopputulos on että 10 000 N:ää, jolloin jää 50 000 A:ta ylimääräiseksi, mutta E:tä saadaan pyydetty määrä.

Jos taas on toinen yksinkertainent tapaus, että halutaan 100 000 A-laatikkoa, niin kumpi on parempi saada ylimääräisenä:
* 200 000 E
vai
* 50 000 B + 25 000C + 10 000 D

Kevins16 [12.01.2017 15:49:50]

#

Kiitoksia vinkistä jaska minäpä tarkastelen ja opettelen nuo ja katson jos niillä saisin toimimaan.

Grez, Ei ole itelle tuottanut ongelmia mutta voihan sitä eri kirjaimet laittaa välttääkseen sekavuutta.

Kyllä se olisi erittäin yksinkertainen tapaus jos tarvitaan vain yhtä tyyppiä, mutta useamman kohdalla hankaloittuu ja sen takia täällä.

Toisen tapauksen kohdalla voisi varmaan sanoa että se vaihtoehto joka tuo pienimmän määrän isoja laatikoita.

Chiman [12.01.2017 16:36:55]

#

Nyt tarkennuksen jälkeen matemaattisesti muotoillen ongelma menisi siis näin:

Etsi alla olevaan lausekkeeseen ei-negatiiviset kokonaislukukertoimet K, L, M ja N niin, että annetut minimimäärät objekteille A, B, C, D ja E (kokonaislukuja väliltä [0, 10^12]) täyttyvät ja lausekkeen K+L+M+N arvo on pienin mahdollinen.

K*(20A+10B+5C+2D) + L*(15B+10C+7D) + M*(5A+15C+20D) + N*(5A+10E)

Itse asettaisin ensin N:n alustavan arvon E:n perusteella. Sen jälkeen haarukoisin K:n ja L:n jakautumista B:n perusteella ja edelleen rekursiivisesti muut arvot kohdilleen kokonaisminimiä kohti pyrkien. Joku varmasti tietää fiksumman ratkaisun.

Grez [12.01.2017 18:29:22]

#

Jos ei bruteforceamista älykkäämpää ratkaisua keksi, niin sitäkin voi tällaisessa tapauksessa varmaan vähän optimoida. Eli jos haetaan vaikka biljoonia laatikoita niin ensin haarukoi 100 miljardin erillä, sitten siitä 10 miljardin, 1 miljardin jne.

Metabolix [12.01.2017 19:49:05]

#

Jos nyt tosiaan paras ratkaisu on se, jossa on vähiten isoja laatikoita, aika hyvään (ehkä optimaaliseen?) ratkaisuun voisi päästä seuraavalla algoritmilla:

  1. Otetaan A5/E10-laatikoita niin, että E riittää.
  2. Otetaan A20/B10/C5/D2-laatikoita niin, että A riittää.
  3. Otetaan B15/C10/D7-laatikoita niin, että B riittää.
  4. Otetaan A5/C15/D20-laatikoita niin, että C ja D riittävät.
  5. Vaihdetaan A20/B10/C5/D2-laatikoita B15/C10/D7-laatikoiksi, jos voidaan.
  6. Poistetaan B15/C10/D7-laatikoita, jos voidaan.

Algoritmin perustelu:

  1. A5/E10 on ainoa tapa saada E:tä eli ja siksi välttämätön. Toisaalta tätä laatikkoa ei tarvita muuhun.
  2. A20/B10/C5/D2 on paras tapa saada A:ta mutta kaikissa muissa huonompi kuin B15/C10/D7, mikä on olennaista algoritmin loppuvaiheessa.
  3. B15/C10/D7 on tämän jälkeen ainoa tapa saada B:tä.
  4. A5/C15/D20 on tehokkain tapa saada C:tä ja D:tä.
  5. Edellinen vaihe voi tuottaa ylimäärän A:ta. A20/B10/C5/D2:n vaihto B15/C10/D7:ksi on käytännössä A-20/B5/C5/D5 eli vähentää 20 A:ta mutta lisää 5 kaikkia muita – selvästi hyvä vaihto, kun A:ta on varaa vähentää.
  6. Nyt A on lähes minimissä mutta B/C/D ehkä plussalla. Näissä ylimäärää on saman verran, koska edellinen vaihto tuottaa näitä saman verran. Näin ollen, vaikka A:ta olisi muutama jäänytkin, B15/C10/D7 on varmasti vähintään yhtä hyvä poistettavaksi kuin A5/C15/D20.

Testiksi PHP-toteutus:

<?php
# Tilaus:
$A = 300;
$B = 140;
$C = 120;
$D = 70;
$E = 80;

# Laskurit:
$lA = $lB = $lC = $lD = $lE = 0;

# Otetaan A5/E10-laatikoita niin, että E riittää.
$l_A5_E10 = ceil(max(0, $E) / 10);
$lA += 5 * $l_A5_E10;
$lE += 10 * $l_A5_E10;

# Otetaan A20/B10/C5/D2-laatikoita niin, että A riittää.
$l_A20_B10_C5_D2 = ceil(max(0, $A - $lA) / 20);
$lA += 20 * $l_A20_B10_C5_D2;
$lB += 10 * $l_A20_B10_C5_D2;
$lC += 5 * $l_A20_B10_C5_D2;
$lD += 2 * $l_A20_B10_C5_D2;

# Otetaan B15/C10/D7-laatikoita niin, että B riittää.
$l_B15_C10_D7 = ceil(max(0, $B - $lB) / 15);
$lB += 15 * $l_B15_C10_D7;
$lC += 10 * $l_B15_C10_D7;
$lD += 7 * $l_B15_C10_D7;

# Otetaan A5/C15/D20-laatikoita niin, että C ja D riittävät.
$l_A5_C15_D20 = ceil(max(0, ($C - $lC) / 15, ($D - $lD) / 20));
$lA += 5 * $l_A5_C15_D20;
$lC += 15 * $l_A5_C15_D20;
$lD += 20 * $l_A5_C15_D20;

# Vaihdetaan A20/B10/C5/D2-laatikoita B15/C10/D7-laatikoiksi, jos voidaan.
$tmp = floor(($lA - $A) / 20);
$l_A20_B10_C5_D2 -= $tmp;
$l_B15_C10_D7 += $tmp;
$lA -= 20 * $tmp;
$lB += 5 * $tmp;
$lC += 5 * $tmp;
$lD += 5 * $tmp;

# Poistetaan B15/C10/D7-laatikoita, jos voidaan.
$tmp = floor(($lB - $B) / 15);
$l_B15_C10_D7 -= $tmp;
$lB -= 15 * $tmp;
$lC -= 10 * $tmp;
$lD -= 7 * $tmp;

echo "Tilaus:\t$A A\t$B B\t$C C\t$D D\t$E E\n";
echo "Tulos:\t$lA A\t$lB B\t$lC C\t$lD D\t$lE E\n";
echo "Osat:\n";
$l_tot = $l_A20_B10_C5_D2 + $l_B15_C10_D7 + $l_A5_C15_D20 + $l_A5_E10;
echo " * $l_A20_B10_C5_D2 × A20/B10/C5/D2\n";
echo " * $l_B15_C10_D7 × B15/C10/D7\n";
echo " * $l_A5_C15_D20 × A5/C15/D20\n";
echo " * $l_A5_E10 × A5/E10\n";
echo "Laatikoita: $l_tot\n";

Toivottavasti antamasi luvut olivat oikeat, koska muuten mietin tätä turhaan. :(

Jaska kirjoitti:

Eikö tuo mene lineaarisella Diofantoksen yhtälöllä?

No ei kai, kun ei välttämättä ole tarkkaa ratkaisua vaan etsitään lähintä suurempaa määrää ja vielä mahdollisimman pienillä kertoimilla.

Kevins16 [13.01.2017 00:14:05]

#

Jos ymmärsin tuon oikein niin tuo on itseasiassa tapa, joka tuli ensimmäisenä mieleen, mutta en ollut varma oliko se paras mahdollinen taikka yksinkertaisin.

luvuthan voi aina vaihtaa kunhan tekee tuolla algoritmilla ja muistaa täyttää suuremmilla ensin

Jaska [13.01.2017 15:18:59]

#

Metabolix kirjoitti:

Jaska kirjoitti:

Eikö tuo mene lineaarisella Diofantoksen yhtälöllä?

No ei kai, kun ei välttämättä ole tarkkaa ratkaisua vaan etsitään lähintä suurempaa määrää ja vielä mahdollisimman pienillä kertoimilla.

Ajattelin, että tarkkaa ratkaisua ei tarvita vaan kasvatetaan laskuria ja tarkistetaan onko ratkaisu olemassa niin kauan kunnes ratkaisu löytyy.

Metabolix [13.01.2017 16:26:42]

#

Kevins16 kirjoitti:

Jos ymmärsin tuon oikein niin tuo on itseasiassa tapa, joka tuli ensimmäisenä mieleen, mutta en ollut varma oliko se paras mahdollinen taikka yksinkertaisin.

luvuthan voi aina vaihtaa kunhan tekee tuolla algoritmilla ja muistaa täyttää suuremmilla ensin

Näyttää, että et ymmärtänyt oikein. Pelkkä suurimpien laatikoiden valitseminen järjestyksessä ei välttämättä ole paras ratkaisu. Algoritmissani on lopussa tuo vaihto- ja vähennysvaihe, joka riippuu käytössä olevista laatikoista.

Antti Laaksonen [13.01.2017 17:17:56]

#

Jaska kirjoitti:

(13.01.2017 15:18:59): ”– –” Ajattelin, että tarkkaa ratkaisua ei tarvita...

Toinen ongelma on, että Diofantoksen yhtälön ratkaisussa voi olla myös negatiivisia lukuja, jotka eivät ole mielekkäitä tässä tehtävässä.

Jaska [13.01.2017 18:22:05]

#

Antti Laaksonen kirjoitti:

Jaska kirjoitti:

(13.01.2017 15:18:59): ”– –” Ajattelin, että tarkkaa ratkaisua ei tarvita...

Toinen ongelma on, että Diofantoksen yhtälön ratkaisussa voi olla myös negatiivisia lukuja, jotka eivät ole mielekkäitä tässä tehtävässä.

Juu. Huomasin itsekin. Ajattelin tätä huomiota ehkä turhan triviaalina. Pitää siis rajoittua epänegatiivisiin ratkaisuihin.

Kevins16 [14.01.2017 02:22:17]

#

Metabolix kirjoitti:

(13.01.2017 16:26:42): ”– –” Näyttää, että et ymmärtänyt oikein. Pelkkä...

Ymmärsin ymmärsin, mutta kuten on saattanut aikaisemmista kirjoituksistani huomata niin kirjoitan sanomani jokseenkin epäselvästi.

mpni [14.01.2017 15:56:54]

#

Mikäli haluttaisiin optimoida pienten laatikoiden lukumäärä, riittäisi hakea luonnollisilla luvuilla ratkaisua seuraavaan optimointiongelmaan:

min 37A + 32B + 40C + 15D
s.t.
4A + C + D >= 60
2A + 3B >= 28
A + 2B + 3C >= 24
2A + 7B + 20C >= 70
D >= 8

Ohessa vielä toteutus.

def f(A,B,C,D):
    #pienet laatikot
	return 37*A + 32*B + 40*C + 15*D;
    #isot laatikot
    #return A + B + C + D

if __name__ == "__main__":
	minimum = float('Inf');
	for A in range(35+1):
		for B in range(12+1):
			for C in range(60+1):
				for D in range(8,60+1):
					if (
						4*A + C + D >= 60 and
						2*A + 3*B >= 28 and
						A + 2*B + 3*C >= 24 and
						2*A + 7*B + 20*C >= 70
						):
						if f(A,B,C,D) < minimum:
							minimum = f(A,B,C,D);
							A1 = A;
							B1 = B;
							C1 = C;
							D1 = D;
	a = 20*A1 + 5*C1 + 5*D1;
	b = 10*A1 + 15*B1;
	c = 5*A1 + 10*B1 + 15*C1;
	d = 2*A1 + 7*B1 + 20*C1;
	e = 10*D1;
	print "min: " + str(minimum);
	print "A: " + str(A1) + " B: " + str(B1) + " C: " + str(C1) + " D: " + str(D1);
	print "a: " + str(a) + " b: " + str(b) + " c: " + str(c) + " d: " + str(d) + " e: " + str(e);

min: 753
A: 13 B: 1 C: 3 D: 8
a: 315 b: 145 c: 120 d: 93 e: 80

Jaska [15.01.2017 11:07:44]

#

Pythonissa ei tarvita puolipisteitä rivien perään ellei halua sovittaa useampaa riviä yhdelle riville. Voit myös käyttää itertoolsin productia yksinkertaistamaan sisäkkäisten silmukoiden kirjoittamista.

mpni [15.01.2017 12:36:37]

#

Jaska kirjoitti:

Pythonissa ei tarvita puolipisteitä rivien perään ellei halua sovittaa useampaa riviä yhdelle riville. Voit myös käyttää itertoolsin productia yksinkertaistamaan sisäkkäisten silmukoiden kirjoittamista.

Ok. Lisäksi esim. silmukoiden kokoja voitaisiin varmasti vielä optimoida (nyt käytössä karkeahkot raja-arvot). Lisäsin koodiin rivin f()-funktioon, jolla saadaan optimitulos isojen laatikoiden määrässä (kysyjän alkuperäinen toivomus).

fergusq [15.01.2017 16:46:33]

#

Jaska kirjoitti:

Pythonissa ei tarvita puolipisteitä rivien perään ellei halua sovittaa useampaa riviä yhdelle riville. Voit myös käyttää itertoolsin productia yksinkertaistamaan sisäkkäisten silmukoiden kirjoittamista.

Puolipisteet ovat pelkkä tyyliseikka, mutta niiden kirjoittaminen on täysin sallittua.

Eikö tuo mpnin toteutus ole muuten vain brute-force? Se ei auttane, jos laatikoita tarvitaan biljoonia.

Grez [15.01.2017 18:12:44]

#

fergusq kirjoitti:

Puolipisteet ovat pelkkä tyyliseikka, mutta niiden kirjoittaminen on täysin sallittua.

Juu, yhtä lailla voi kirjoittaa vaikka 7 puolipistettä vastaaviin paikkoihin - sekin on täysin sallittu "tyyliseikka".


Sivun alkuun

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta