Losowe wyszukiwanie rozwiązań (VBA)

Transkrypt

Losowe wyszukiwanie rozwiązań (VBA)
Losowe wyszukiwanie rozwiązań (VBA)
Dariusz Jabłoński (hurgadion)
[email protected]
W pierwszym moim artykule dotyczącym losowego wyszukiwania rozwiązań
skoncentrowałem się na pokazywaniu problemów dotyczących zagadnień typowo
matematycznych. W niniejszym artykule prezentuję przykłady poszukiwania rozwiązań dla
problemów częściej spotykanych w działalności gospodarczej.
Zaprezentowano ponadto, inaczej niż poprzednio, losową metodę wyszukiwania
rozwiązań z wykorzystaniem języka programowania VBA a nie tylko formuł Excela.
Podejście to, może być wykorzystywane czasem jako alternatywa dla Solvera, zdarza się przy
tym, że daje ono również rozwiązania inne niż Solver.
Zadanie 1.1
Załóżmy, że mamy kilkanaście magazynów umieszczonych w całym kraju, powiedzmy: 15.
W każdym z tych magazynów jest jakiś towar, np w magazynie nr 1. jest towar a, b, c ,d.
W magazynie 2 jest towar b, c, x, y, z... W kolejnych magazynach podobnie. W żadnym
magazynie nie ma całego "alfabetu" towarów. Jak obliczyć optymalną (minimalną) liczbę
magazynów, które zawierają cały alfabet towarów?
Rozwiązanie:
W załączniku (Arkusz magazyny) w tabeli mieszczącej się w zakresie C2:S30 można wpisać
w których magazynach występują poszczególne artykuły. Jest to tablica pomocnicza, na
podstawie której są wykonywane obliczenia. W komórce S4 jest wpisana formuła tablicowa:
=SUMA(D4:R4*$D$31:$R$31)
która zlicza w ilu magazynach wytypowanych występuje dany artykuł z alfabetu. W zakresie
D31:R31 po uruchomieniu Solvera będą występować zera i jedynki. Jedynka oznacza, że
dany magazyn jest wytypowany. W Solverze ponadto jest warunek dla zakresu D4:R4
gwarantujący, że dla każdego artykułu z alfabetu zostanie wytypowany co najmniej jeden
magazyn po optymalizacji. Po odpaleniu Solvera należy uruchomić poniższe makro, aby
wybrać unikalnie magazyn, z którego będziemy sprowadzać dany artykuł.
1
http://www.excelforum.pl/topics1/optymalizacja-ilosci-dostawcow-vt23792.htm#127612
Sub Art()
Dim tbl1(1 To 26)
Dim x&, i&, j&, s As String
x = 1
For i = 1 To 15
s = ""
If Cells(31, i + 5).Value = 1 Then
For j = 1 To 26
If Cells(j + 3, i + 5).Value = 1 Then
If tbl1(j) = 0 Then
If s = "" Then
s = Cells(j + 3, 5).Value
Else
s = s & ", " & Cells(j + 3, 5).Value
End If
tbl1(j) = 1
End If
End If
Next j
End If
Cells(i + 5, 4).Value = s
Next i
End Sub
Makro wybiera pierwszy z kolei magazyn, z którego da się sprowadzić dany artykuł. Po kolei
dla każdego magazynu następuje sprawdzenie czy dany artykuł został już wybrany, jeżeli tak,
to przechodzimy dalej, jeżeli nie, to artykuł jest przypisywany do magazynu.
Problem postawiony w tym zadaniu da się rozwiązać bez wykorzystania Solvera (dzięki temu
da się także uzyskać rozwiązanie nieco innego od Solvera, o ile rozwiązanie nie jest jedyne).
Sub Permutacja()
Dim i&, j&, l&, x&, x1&, a&
ReDim V(1 To 15)
ReDim VK(1 To 15)
ReDim VZ(1 To 26)
ReDim V1(1 To 26, 1 To 15)
ReDim VPom(1 To 26, 1 To 15)
Range("F31:T31").ClearContents
V1 = Range("F4:T29").Value
x1 = 15
For l = 1 To 10000
V = RndInt()
x = 0
For i = 1 To 15
a = V(i)
For j = 1 To 26
VPom(j, i) = V1(j, a)
If i = 1 Then VZ(j) = 0
VZ(j) = VZ(j) + VPom(j, i)
Next j
x = x + 1
If Application.Product(VZ) > 0 Then Exit For
Next i
If x < x1 Then
VK = V
x1 = x
End If
Next l
For i = 1 To x1
Cells(31, VK(i) + 5).Value = 1
Next i
End Sub
Makro działa w następujący sposób:
1) Kluczowym elementem makra jest pętla iterowana przez i (ilość magazynów) oraz
iterowana przez j (ilość artykułów),
2) W każdej iteracji pętli iterowanej przez l (każda pętla odpowiada innemu losowaniu)
zostaje wylosowana jednowymiarowa tablica V o unikalnych elementach 1-15
(umożliwia to pomocnicza funkcja RndInt),
3) Do tablicy V1 zostają wpisane komórki zakresu F4:T29 (ten zakres jest kluczowy,
odpowiada on informacji o rozmieszczeniu artykułów w magazynach)
4) Następnie zostają przepermutowane kolumny tablicy V1 w zależności od kolejności
indukowanej przez elementy tablicy V.
5) W kluczowej pętli iterowanej przez i, j następuje wybór x magazynów, które posiadają
wszystkie artykuły (ten warunek sprawdza funkcja Application.Product)
6) Główna pętla generowana przez l umożliwia znalezienie (a raczej zoptymalizowanie)
liczby magazynów zawierającej wszystkie artykuły.
7) Dla znalezionej optymalnej liczby magazynów zostaje przypisana losowa tablica V.
Funkcja pomocnicza losująca zestaw liczb od 1-15 (na podstawie pozycji [1])
Function RndInt()
Dim V() As Variant, Val As Variant
Dim i&, j&, r&, c&, a&
Dim t1 As Variant, t2 As Variant
Randomize
a = 15
ReDim V(1 To a)
ReDim Val(1 To 2, 1 To a)
For i = 1 To a
Val(1, i) = Rnd
Val(2, i) = i
Next i
For i = 1 To a
For j = i + 1 To a
If Val(1, i) > Val(1, j) Then
t1 = Val(1, j)
t2 = Val(2, j)
Val(1, j) = Val(1, i)
Val(2, j) = Val(2, i)
Val(1, i) = t1
Val(2, i) = t2
End If
Next j
Next i
i = 0
For r = 1 To a
i = i + 1
V(i) = Val(2, i)
Next r
RndInt = V
End Function
▪
Zadanie 2.
Mamy zbiór danych liczbowych (od dwóch do dwudziestu dodatnich składników). Znaleźć
możliwe kombinacje sum pomiędzy dwoma z góry ustalonymi wartościami.
Rozwiązanie:
Unikalne (dzięki kolekcji) kombinacje wyławia losowo następujące makro (Arkusz
losowanki):
Sub
Dim
Dim
Dim
Dim
Losowanko()
tabl(), tablprop(), tabl1()
NoDupes As New Collection
x&, i&, j&, s As Double, m&, a As String, p&, w&
Item As Variant
p = Application.CountA(Range("A1:T1"))
If p < 2 Then Exit Sub
ReDim tabl(1 To p)
ReDim tablprop(1 To p)
ReDim tabl1(1 To p)
tabl = Cells(1, 1).Resize(, p).Value
Application.ScreenUpdating = False
w = Application.Max(2, Cells(Rows.Count, "A").End(xlUp).Row)
Range("A2:T" & w).ClearContents
Randomize
x = 1
On Error Resume Next
For i = 1 To 100 * Sqr(2 ^ (p + 3))
For j = 1 To p
tablprop(j) = Round(Rnd, 0)
Next j
s = Application.SumProduct(tabl, tablprop)
If s <= Cells(3, "V").Value And s >= Cells(2, "V").Value Then
m = Cells(Rows.Count, "A").End(xlUp).Row + 1
m = Application.Max(m, 3)
Cells(m, 1).Resize(, p) = tablprop
For j = 1 To p
Cells(m, j).Value = Cells(m, j).Value * Cells(1, j).Value
Next j
Cells(m, 1).Value = Lacz_Teksty(Cells(m, 1).Resize(, p))
NoDupes.Add Cells(m, 1).Value, CStr(Cells(m, 1).Value)
End If
Next i
On Error GoTo 0
x = 3
w = Application.Max(2, Cells(Rows.Count, "A").End(xlUp).Row)
Range("A3:T" & w).ClearContents
For Each Item In NoDupes
Cells(x, 1).Value = Item
x = x + 1
Next Item
x = Cells(Rows.Count, "A").End(xlUp).Row
For i = 3 To x
a = Cells(i, 1).Value
For j = 1 To p
Cells(i, j).Value = Extra(a, j)
Cells(i, j).Value = Cells(i, j).Value * 1
Next j
Next i
Application.ScreenUpdating = True
End Sub
Function Extra(txt, n)
Dim All As Variant
All = Split(txt, "_")
Extra = All(n - 1)
End Function
Function Lacz_Teksty(x As Range)
Dim s As String, kom As Range
For Each kom In x
If Len(s) = 0 Then
s = kom.Value
Else
If Len(kom.Value) > 0 Then s = s & "_" & kom.Value
End If
Next kom
Lacz_Teksty = s
End Function
Makro działa w następujący sposób:
1) Liczba p zlicza ile jest wypełnionych po kolei komórek w wierszu pierwszym (dla tylu
komórek są wyszukiwane rozwiązania),
2) W komórkach V2, V3 są wpisane graniczne wartości, dla których będzie
przeprowadzane wyszukiwanie rozwiązań,
3) Następnie do tablicy tabl są wpisane wartości niepustych komórek z wiersza
pierwszego,
4) W głównej pętli iterowanej przez i następuje losowe wyszukiwanie odpowiednich
rozwiązań, w każdym kroku zostaje utworzona losowa, zerojedynkowa tablica
tablprop,
5) Następnie jest obliczona SUMA.ILOCZYNÓW (w wersji VBA) tablic tabl, tablprop
6) W kolejnym kroku jest sprawdzana czy obliczona SUMA.ILOCZYNÓW jest
pomiędzy granicznymi wartościami z punktu 2), jeżeli warunek jest sprawdzony
następuje dopisanie danego losowego rozwiązania, o ile wcześniej nie zostało już
wpisane. Umożliwia to fragment kodu wyławiający unikaty za pomocą obiektu
kolekcji,
7) Unikalne rozwiązania są po kolei wypisywane w odpowiednich, kolejnych wierszach
W makrze zostały użyte standardowe, pomocnicze funkcje (Extra: na podstawie [1],
Lacz_Teksty) umożliwiające operacje na łańcuchach tekstowych. Być może da się ich
uniknąć i rozwiązać problem nieco łatwiej, np. za pomocą tablic (Homework )
▪
Zadanie 3
Ogrodnikowi polecono zaprojektować ogród z drzewami i krzewami, przy czym właściciel
ogrodu przeznaczył na zakup 1500zł i ponadto życzył sobie, aby:
a) cisów było co najwyżej 6 sztuk,
b) jałowców tyle samo co tuj i co najmniej po 20 sztuk
c) jodły 2 razy więcej niż sosny
d) kosodrzewiny więcej niż 6 sztuk, ale mniej niż 12
e) świerków srebrnych 4 sztuki, a świerków białych 6 sztuk
f) każdego drzewa i krzewu musi być co najmniej po 2 sztuki
Ile drzew i krzewów może kupić ogrodnik za tą sumę, spełniając jednocześnie
wymagania właściciela ogrodu jeśli ceny drzew i krzewów są takie, jak podano wyżej
w tabeli?
TOWAR
CENA
cis
18,00 zł
jałowiec
11,00 zł
jodła
18,00 zł
sosna
10,00 zł
kosodrzewina
18,00 zł
świerk biały
13,00 zł
świerk srebrny
40,00 zł
tuja
25,00 zł
Rozwiązanie:
Problem można rozwiązać w sposób standardowy wykorzystując Solvera, otrzymamy wtedy
rozwiązanie jak na załączonej tabeli.
CENA
18,00 zł
11,00 zł
18,00 zł
10,00 zł
18,00 zł
13,00 zł
40,00 zł
25,00 zł
LICZBA
6
24
4
2
11
6
4
24
Można jednak spróbować znaleźć rozwiązanie losowo wyszukując optymalne wartości za
pomocą poniższego makra, które wyszukuje także rozwiązania inne niż Solver (Arkusz
solveration).
Sub Solveration()
Dim table(), tablelos()
Dim i&
ReDim table(1 To 8)
ReDim tablelos(1 To 8)
Range("C2:C9").ClearContents
table = Application.Transpose(Range("B2:B9"))
Randomize
For i = 1 To 1000000
tablelos(1)
tablelos(2)
tablelos(4)
tablelos(3)
tablelos(5)
tablelos(6)
tablelos(7)
tablelos(8)
=
=
=
=
=
=
=
=
Int(2 + 4 * Rnd, 0)
Int(20 + 50 * Rnd, 0)
Int(2 + 50 * Rnd(), 0)
2 * tablelos(4)
Int(7 + 5 * Rnd, 0)
6
4
tablelos(2)
If Application.SumProduct(table, tablelos) = 1500 Then
Range("C2:C9").Value = Application.Transpose(tablelos)
Exit Sub
End If
Next i
MsgBox “Nie udało się znaleźć właściwego rozwiązania”
End Sub
Makro działa w następujący sposób:
1) Główną pętlą jest pętla iterowana przez i. Makro odnajduje rozwiązanie, o ile istnieje
(można makro przerobić, aby wyszukiwało rozwiązanie optymalne zamiast
dokładnego: Homework ). W tym przypadku rozwiązanie nie jest jednoznaczne,
istnieje parę różnych rozwiązań problemu. Główna pętla jest ustawiona na milion
kroków, jednak po znalezieniu optymalnego rozwiązania zostaje przerwane działanie
głównej pętli,
2) Każdy krok głównej pętli polega na wylosowaniu optymalnego rozwiązania (tablica
tablelos, poszczególne elementy tej tablicy odpowiadają ilości danego gatunku
drzewa), które jest dobierane w taki sposób, aby spełniało warunki postawione
w zadaniu (jest to możliwe dzięki funkcji zaokrąglającej Int oraz podstawowym
działaniom matematycznym). Do tablicy table są przypisane ceny poszczególnych
drzew. Następnie potencjalne rozwiązanie zostaje zweryfikowane (zostaje obliczona
SUMA.ILOCZYNÓW tablic table oraz tablelos i porównana z oczekiwaną
wartością). Po pozytywnej weryfikacji potencjalnego rozwiązania następuje
przypisanie tablicy tablelos do właściwego zakresu.
▪
Ważnym
zagadnieniem
optymalizacyjnym
jest
tzw.
problem
plecakowy,
poniżej
prezentujemy rozwiązanie oparte o losową metodę wyszukiwania rozwiązań.
Zadanie 4.
Mamy m plecaków oraz n przedmiotów o wadze w i wartości c oraz ograniczenia pojemności
plecaków a_i, i = 1, …, m. Należy upakować te przedmioty do tych plecaków tak, aby suma
wartości przedmiotów w plecakach była jak największa.
Rozwiązanie:
Problem można rozwiązać za pomocą poniższego makro (Arkusz plecak):
Sub Plecaki()
Dim tabil() As Long, tabcost() As Long, tablos() As Long, tab1() As Long,
tabplecak() As Long, tab2() As Long
Dim c1&, a&, b&, c&, d&, x&, i&, j&, k&, m&, n&
m = Application.CountA(Range("1:1"))
n = Application.CountA(Range("4:4"))
Application.ScreenUpdating = False
ReDim tabplecak(1 To m)
For i = 1 To m
tabplecak(i) = Cells(1, i).Value
Next i
ReDim
ReDim
ReDim
ReDim
tabil(1 To n)
tabcost(1 To n)
tablos(1 To n)
tab1(1 To n)
For i = 1 To n
tabil(i) = Cells(4, i).Value
tabcost(i) = Cells(5, i).Value
d = d + tabcost(i)
Next i
Randomize
For i = 1 To Application.Min(1000000, 50 * m ^ UBound(tablos))
ReDim tab2(1 To m)
c = 0
For j = 1 To n
tablos(j) = Round(-0.5 + (m + 1) * Rnd, 0)
For k = 1 To m
If tablos(j) = k Then
tab2(k) = tab2(k) + tabil(j)
c = c + tabcost(j)
End If
Next k
Next j
x = 0
For j = 1 To m
If tab2(j) <= tabplecak(j) Then x = x + 1
Next j
If c > c1 And x = m Then
c1 = c
tab1 = tablos
End If
If c1 = d Then Exit For
Next i
Cells(6, 1).Resize(, n) = tab1
Range("C7").Value = c1
Application.ScreenUpdating = True
End Sub
Podobnie jak w poprzednich wypadkach rozwiązanie polega na losowej optymalizacji
rozwiązania. W rozwiązaniu są wykorzystane tablice na których są wykonywane odpowiednie
działania. Osoby zainteresowane dokładniejszym opisem proszę o kontakt.
▪
Na koniec rozwiążemy (bardziej jako ciekawostkę) w nieco nietypowy sposób klasyczny
problem dotyczący znajdowania unikatów, szerzej o tym problemie można poczytać
w jednym z wątków FAQ www.excelforum.pl2. Rozwiązanie wykorzystuje funkcję Join
(Arkusz unikaty).
Zadanie 5.
Znaleźć w określonym jednokolumnowym zbiorze danych unikaty.
Rozwiązanie:
Losowe Unikaty kolejno wyławia z zakresu z kolumny A makro (Arkusz unikaty):
Sub LosoweUnikaty()
Dim tbl(), x As Range
Dim a&, i&, s&, m As String
a = WorksheetFunction.CountA(Range("A:A"))
Set x = Range("A2:A" & a)
s = 1
For i = 1 To 10 * x.Count
If i = 1 Then
ReDim tbl(1 To s)
tbl(i) = Application.Index(x, Round(0.5 + x.Count * Rnd, 0))
Else
m = Application.Index(x, Round(0.5 + x.Count * Rnd, 0))
2
http://www.excelforum.pl/topics7/41-unikaty-vt22640.htm
If Join(tbl) Like "*" & m & "*" Then
GoTo dalej:
Else
s = s + 1
ReDim Preserve tbl(1 To s)
tbl(s) = m
End If
End If
dalej:
Next i
Range("B2:B" & a).ClearContents
Cells(2, 2).Resize(UBound(tbl)) = Application.Transpose(tbl)
End Sub
Rozwiązanie polega na losowym wyławianiu Unikatów z ustalonego zakresu w kolumnie A.
1) Za pomocą funkcji Rnd oraz Arkuszowej funkcji Indeks następuje wylosowanie
pewnego elementu z zakresu kolumny A.
2) Następnie zostaje sprawdzone za pomocą funkcji Join czy wylosowany element został
wylosowany już wcześniej. Jeżeli wcześniej nie został wylosowany, to element zostaje
dołączony do tablicy tbl przechowującej wylosowane kolejno unikaty.
3) Na koniec unikaty są wpisane do odpowiedniego zakresu kolumny B.
4) Może się zdarzyć, że nie wszystkie unikaty są wytypowane przez makro, ale jest to
bardzo mało prawdopodobne.
5) Aby się zabezpieczyć przed wylosowaniem niewłaściwej liczby unikatów można
wyliczyć tą liczbę unikatów i wykonywać makro do uzyskania odpowiedniej ilości
elementów.
W poprawie niniejszego artykułu pomógł mi Artik: www.excelforum.pl.
Bibliografia:
[1] Walkenbach J., „Excel 2010 PL Programowanie w VBA”, Helion, Gliwice, 2011
[2] Jabłoński D., „Losowe wyszukiwanie rozwiązań”, www.excelperfect.pl, 2011
Załącznik: losowe.wyszukiwanie.VBA.xls
▪

Podobne dokumenty