Author Topic: Knapsack Problem/0-1 - Rosetta Code  (Read 4789 times)

0 Members and 1 Guest are viewing this topic.

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Knapsack Problem/0-1 - Rosetta Code
« on: October 05, 2020, 11:49:01 am »
Rosetta Code ref link is in comments at top. Basically packing a Knapsack with supplies needed for day and you have more items than you can carry so you assign values to each item and pick best value for 400 weight limit. Items total over 800.

This turned out way easier than I expected, thinking I would have to try all combinations that added to 400 in weight and take the best values score but no:

Code: QB64: [Select]
  1. _TITLE "Knapsack problem/0-1 - Rosetta Code" 'b+ 2020-10-04
  2. ' w < 400   http://rosettacode.org/wiki/Knapsack_problem/0-1
  3. TYPE item
  4.     name AS STRING
  5.     weight AS SINGLE
  6.     value AS SINGLE
  7.     vPerW AS SINGLE ' value Per Weight
  8. REDIM it(0) AS item, insert AS item
  9. FOR i = 1 TO 22
  10.     READ insert.name, insert.weight, insert.value
  11.     insert.vPerW = insert.value / insert.weight
  12.     loadSort insert, it()
  13. PRINT "      *** Knapsack Items Sorted by Value Per Unit Weight ***"
  14. PRINT " #: Item:         Wght:  Val:   V/W:  Accum W:    Packed or Not: Value Packed:"
  15. FOR i = 1 TO 22
  16.     PRINT RIGHT$(SPC(3) + STR$(i), 3); SPC(1);
  17.     PRINT LEFT$(it(i).name + SPC(15), 15);
  18.     PRINT RIGHT$(SPC(4) + _TRIM$(STR$(it(i).weight)), 4);
  19.     PRINT RIGHT$(SPC(6) + _TRIM$(STR$(it(i).value)), 6);
  20.     PRINT RIGHT$(SPC(7) + _TRIM$(STR$(INT(it(i).vPerW * 100) / 100)), 7);
  21.     totW = totW + it(i).weight
  22.     PRINT RIGHT$(SPC(10) + _TRIM$(STR$(totW)), 10); SPC(4);
  23.     IF totW < 400 THEN
  24.         PRINT "Packed!";
  25.         totV = totV + it(i).value
  26.         PRINT RIGHT$(SPC(21) + _TRIM$(STR$(totV)), 21);
  27.     ELSE
  28.         PRINT "Not included.";
  29.     END IF
  30.     IF i < 22 THEN PRINT
  31. DATA map,9,150
  32. DATA compass,13,35
  33. DATA water,153,200
  34. DATA sandwich,50,160
  35. DATA glucose,15,60
  36. DATA tin,68,45
  37. DATA banana,27,60
  38. DATA apple,39,40
  39. DATA cheese,23,30
  40. DATA beer,52,10
  41. DATA suntan cream,11,70
  42. DATA camera,32,30
  43. DATA T-shirt,24,15
  44. DATA trousers,48,10
  45. DATA umbrella,73,40
  46. DATA WP_trousers,42,70
  47. DATA WP_wear,43,75
  48. DATA note-case,22,80
  49. DATA sunglasses,7,20
  50. DATA towel,18,12
  51. DATA socks,4,50
  52. DATA book,30,10
  53. SUB loadSort (insertN AS item, dynArr() AS item) '  modified for this code
  54.     'note this leaves dynArr(0) empty! so ubound of array is also number of items in list
  55.     DIM ub, j, k
  56.     ub = UBOUND(dynArr) + 1
  57.     REDIM _PRESERVE dynArr(LBOUND(dynArr) TO ub) AS item
  58.     FOR j = 1 TO ub - 1
  59.         IF insertN.vPerW > dynArr(j).vPerW THEN '  GT to LT according to descending or ascending sort
  60.             FOR k = ub TO j + 1 STEP -1
  61.                 dynArr(k) = dynArr(k - 1)
  62.             NEXT
  63.             EXIT FOR
  64.         END IF
  65.     NEXT
  66.     dynArr(j) = insertN
  67.  
  68.  

After sort by value per weight unit, just take top items until you exceed 400. Actually this problem might be made trickier by throwing in really light low value items.

On a practical side if you are expecting rain and take all that rain gear, do you need sun glasses, sun protection oil and water or vice versa? Well maybe you could run into both?

WP is short for Water proof and I changed WP_wear from "Waterproof overclothes" because the name was so long.
« Last Edit: October 05, 2020, 11:57:24 am by bplus »

Offline Rubidium

  • Newbie
  • Posts: 10
    • View Profile
Re: Knapsack Problem/0-1 - Rosetta Code
« Reply #1 on: October 06, 2020, 07:13:10 pm »
Yes, by sorting your items in descending order of Value per Weight you are guaranteed the top of the list up to 400 (dag) are the most valuable items to pack. very neat solution.

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Re: Knapsack Problem/0-1 - Rosetta Code
« Reply #2 on: October 06, 2020, 07:43:42 pm »
Thanks. I've been thinking since I posted this code that if list included a couple of really light items, they might have been added to knapsack no matter their value rating, there was room for 4 more weight units and I would have to change code to look for that. My solution matched the others so I let it be.

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Re: Knapsack Problem/0-1 - Rosetta Code
« Reply #3 on: October 13, 2020, 04:44:57 pm »
Yeah, this is a much more satisfactory solution, now what you take may not be the first set of items that appear. You may still have room for lighter but way lower valued items like this:

Code: QB64: [Select]
  1. _TITLE "Knapsack problem/0-1 - Rosetta Code Better Solution" 'b+
  2. ' ref:  http://rosettacode.org/wiki/Knapsack_problem/0-1
  3.  
  4. ' 2020-10-04 post Knapsack - Rosetta Code.bas
  5. ' https://www.qb64.org/forum/index.php?topic=3091.msg123665#msg123665
  6.  
  7. ' 2020-10-13 Knapsack better soln 2020-10-13 Rosetta Code
  8. ' swap DATA beer,52,10 for DATA xtra_paper,3,1 to trip up original code solution and test better code soln.
  9. ' note: need to keep number at 22 to fit default screen 0.
  10.  
  11. ' w < 400 taking highest valued items first.
  12. TYPE item
  13.     name AS STRING
  14.     weight AS SINGLE
  15.     value AS SINGLE
  16.     vPerW AS SINGLE ' value Per Weight
  17. REDIM it(0) AS item, insert AS item
  18. FOR i = 1 TO 22
  19.     READ insert.name, insert.weight, insert.value
  20.     insert.vPerW = insert.value / insert.weight
  21.     loadSort insert, it()
  22. PRINT "              *** Knapsack Items Sorted by Value Per Unit Weight ***"
  23. PRINT " #: Item:         Wght:  Val:   V/W:    Packed or Not:  Accum w: Value Packed:"
  24. FOR i = 1 TO 22
  25.     IF totW + it(i).weight < 400 THEN
  26.         pack = -1: totW = totW + it(i).weight: totV = totV + it(i).value
  27.     ELSE
  28.         pack = 0
  29.     END IF
  30.     PRINT RIGHT$(SPC(3) + STR$(i), 3); SPC(1);
  31.     PRINT LEFT$(it(i).name + SPC(15), 15);
  32.     PRINT RIGHT$(SPC(4) + STR$(it(i).weight), 4);
  33.     PRINT RIGHT$(SPC(6) + STR$(it(i).value), 6);
  34.     PRINT RIGHT$(SPC(7) + STR$(INT(it(i).vPerW * 100) / 100), 7);
  35.     IF pack THEN
  36.         PRINT SPC(8); "Packed!"; SPC(4);
  37.         PRINT RIGHT$(SPC(9) + STR$(totW), 9);
  38.         PRINT RIGHT$(SPC(14) + STR$(totV), 14);
  39.     ELSE
  40.         PRINT SPC(5); "Not included.";
  41.     END IF
  42.     IF i < 22 THEN PRINT
  43. DATA map,9,150
  44. DATA compass,13,35
  45. DATA water,153,200
  46. DATA sandwich,50,160
  47. DATA glucose,15,60
  48. DATA tin,68,45
  49. DATA banana,27,60
  50. DATA apple,39,40
  51. DATA cheese,23,30
  52. DATA suntan cream,11,70
  53. DATA camera,32,30
  54. DATA T-shirt,24,15
  55. DATA trousers,48,10
  56. DATA umbrella,73,40
  57. DATA WP_trousers,42,70
  58. DATA WP_wear,43,75
  59. DATA note-case,22,80
  60. DATA sunglasses,7,20
  61. DATA towel,18,12
  62. DATA socks,4,50
  63. DATA book,30,10
  64. DATA extra_paper,3,1
  65. SUB loadSort (insertN AS item, dynArr() AS item) '  modified for this code
  66.     'note this leaves dynArr(0) empty! so ubound of array is also number of items in list
  67.     DIM ub, j, k
  68.     ub = UBOUND(dynArr) + 1
  69.     REDIM _PRESERVE dynArr(LBOUND(dynArr) TO ub) AS item
  70.     FOR j = 1 TO ub - 1
  71.         IF insertN.vPerW > dynArr(j).vPerW THEN '  GT to LT according to descending or ascending sort
  72.             FOR k = ub TO j + 1 STEP -1
  73.                 dynArr(k) = dynArr(k - 1)
  74.             NEXT
  75.             EXIT FOR
  76.         END IF
  77.     NEXT
  78.     dynArr(j) = insertN

I traded out beer for xtra_paper so that all the stuff still fits on default screen 0.
 
QB64 Knapsack better soln.PNG

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Re: Knapsack Problem/0-1 - Rosetta Code
« Reply #4 on: October 14, 2020, 10:48:38 pm »
bplus method sucks as a general solution to this sort of problem. Here is a counter example, compare the solution the code produces with logic and common sense, take the crackers with carry out value of 100!

Code: QB64: [Select]
  1. _TITLE "Shopping Cart Mod of Knapsack Problem/0-1  from Rosetta Code" ' b+ 2020-10-14
  2. ' mod from: "Knapsack problem/0-1 - Rosetta Code Better Solution" 'b+
  3. ' ref:  http://rosettacode.org/wiki/Knapsack_problem/0-1
  4.  
  5. ' 2020-10-04 post Knapsack - Rosetta Code.bas
  6. ' https://www.qb64.org/forum/index.php?topic=3091.msg123665#msg123665
  7.  
  8. ' 2020-10-13 Knapsack better soln 2020-10-13 Rosetta Code
  9. ' swap DATA beer,52,10 for DATA xtra_paper,3,1 to trip up original code solution and test better code soln.
  10. ' note: need to keep number at 22 to fit default screen 0.
  11. ' post  https://www.qb64.org/forum/index.php?topic=3091.msg123962#msg123962
  12.  
  13. ' 2020-10-14 Shopping Cart Mod bplus Knapsack solution sucks!
  14. ' It will not serve as a general solution or method to approach problems like this.
  15. ' The DATA just happens to work well with sort and pick method
  16.  
  17.  
  18. ' ========================= Here is a counter example with 100 weight limit  =======================
  19. DATA ham,20,25
  20. DATA crackers,100,100
  21. DATA ice cream,20,9
  22. DATA potatoes,70,66
  23. DATA beans,30,17
  24. DATA carrots,20,19
  25. DATA cookies,10,5
  26.  
  27. ' Obviously you should pickup the crackers only!
  28. ' Make the 100 weight limit with a carry out value of 100!
  29. '===================================================================================================
  30. TYPE item
  31.     name AS STRING
  32.     weight AS SINGLE
  33.     value AS SINGLE
  34.     vPerW AS SINGLE ' value Per Weight
  35. REDIM it(0) AS item, insert AS item
  36. FOR i = 1 TO 7
  37.     READ insert.name, insert.weight, insert.value
  38.     insert.vPerW = insert.value / insert.weight
  39.     loadSort insert, it()
  40. PRINT "              *** Shopping Cart Items Sorted by Value Per Unit Weight ***"
  41. PRINT " #: Item:         Wght:  Val:   V/W:    Packed or Not:  Accum w: Value Packed:"
  42. FOR i = 1 TO 7
  43.     IF totW + it(i).weight <= 100 THEN
  44.         pack = -1: totW = totW + it(i).weight: totV = totV + it(i).value
  45.     ELSE
  46.         pack = 0
  47.     END IF
  48.     PRINT RIGHT$(SPC(3) + STR$(i), 3); SPC(1);
  49.     PRINT LEFT$(it(i).name + SPC(15), 15);
  50.     PRINT RIGHT$(SPC(4) + STR$(it(i).weight), 4);
  51.     PRINT RIGHT$(SPC(6) + STR$(it(i).value), 6);
  52.     PRINT RIGHT$(SPC(7) + STR$(INT(it(i).vPerW * 100) / 100), 7);
  53.     IF pack THEN
  54.         PRINT SPC(8); "Packed!"; SPC(4);
  55.         PRINT RIGHT$(SPC(9) + STR$(totW), 9);
  56.         PRINT RIGHT$(SPC(14) + STR$(totV), 14);
  57.     ELSE
  58.         PRINT SPC(5); "Not included.";
  59.     END IF
  60.     IF i < 7 THEN PRINT
  61.  
  62. 'DATA "ham,20,25"
  63. 'DATA "crackers,100,100"
  64. 'DATA "ice cream,20,9"
  65. 'DATA "potatoes,70,66"
  66. 'DATA "beans,30,17"
  67. 'DATA "carrots,20,19"
  68. 'DATA "cookies,10,5"
  69.  
  70. SUB loadSort (insertN AS item, dynArr() AS item) '  modified for this code
  71.     'note this leaves dynArr(0) empty! so ubound of array is also number of items in list
  72.     DIM ub, j, k
  73.     ub = UBOUND(dynArr) + 1
  74.     REDIM _PRESERVE dynArr(LBOUND(dynArr) TO ub) AS item
  75.     FOR j = 1 TO ub - 1
  76.         IF insertN.vPerW > dynArr(j).vPerW THEN '  GT to LT according to descending or ascending sort
  77.             FOR k = ub TO j + 1 STEP -1
  78.                 dynArr(k) = dynArr(k - 1)
  79.             NEXT
  80.             EXIT FOR
  81.         END IF
  82.     NEXT
  83.     dynArr(j) = insertN
  84.  
  85.  
Ham and carrots ect.PNG


Yeah to get best optimization in such problems, you need to run through all the combinations which gets pretty hairy when big numbers of items are involved.

Like this:
Code: QB64: [Select]
  1. _TITLE "Shopping Cart solve by Combo Method" '  b+ 2020-10-15
  2. ' a combinations problem modeled on a bluatigro post 2016-07-23 and fixed at old JB forum.
  3.  
  4. maxWeight = 100 'for problem
  5. maxItems = 7 'for data reading and array dimensions
  6. DIM item$(maxItems), w(maxItems), v(maxItems)
  7. FOR i = 1 TO maxItems
  8.     READ i$, ww, vv
  9.     item$(i) = i$: w(i) = ww: v(i) = vv
  10.     PRINT item$(i), w(i), v(i)
  11. PRINT: PRINT "Find the combination of items above the will fill cart for $100 and maximize the value."
  12. FOR combo = 1 TO (2 ^ (maxItems + 1) - 1)
  13.     testList$ = "": testValue = 0: testWeight = 0
  14.     FOR power = 0 TO maxItems
  15.         IF combo AND 2 ^ power THEN
  16.             IF testList$ = "" THEN testList$ = item$(power) ELSE testList$ = testList$ + CHR$(10) + item$(power)
  17.             testValue = testValue + v(power)
  18.             testWeight = testWeight + w(power)
  19.         END IF
  20.     NEXT
  21.     IF testWeight <= maxWeight AND testValue >= MaxValue THEN
  22.         MaxValue = testValue: saveCombo = combo: saveList$ = testList$: saveWeight = testWeight
  23.     END IF
  24.     IF combo MOD 1000 = 0 THEN LOCATE 1, 1: PRINT SPACE$(20): LOCATE 1, 1: PRINT combo 'progress
  25. PRINT: PRINT "Best value found <= $"; maxWeight; " was:"
  26. PRINT saveList$
  27. PRINT "Total $"; saveWeight; "  Total value: "; MaxValue
  28.  
  29. DATA "ham",20,25
  30. DATA "crackers",100,100
  31. DATA "ice cream",20,9
  32. DATA "potatoes",70,66
  33. DATA "beans",30,17
  34. DATA "carrots",20,19
  35. DATA "cookies",10,5
  36.  
  37.  

 
Crackers.PNG
« Last Edit: October 15, 2020, 02:48:29 am by bplus »

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Re: Knapsack Problem/0-1 - Rosetta Code
« Reply #5 on: October 15, 2020, 02:42:36 am »
Wow what took about a half hour to run at JB took about a minute in QB64:
Code: QB64: [Select]
  1. _TITLE "Knapsack problem/0-1 Combo Method Solve" 'Revised 2020-10-15.txt b+
  2. ' http://rosettacode.org/wiki/Knapsack_problem/0-1
  3. ' Found Knapsack problem posed by bluatigro and fixed 2016-07-23 at old JB Forum.
  4. ' This code follows that fixed method.
  5. DEFLNG A-Z
  6. maxWeight = 400 'for problem
  7. maxItems = 22 'for data reading and array dimensions 8,388,608 combinations!
  8. topItemsIndex = maxItems - 1
  9. DIM item$(topItemsIndex), w(topItemsIndex), v(topItemsIndex)
  10. FOR i = 0 TO topItemsIndex
  11.     READ i$, ww, vv
  12.     item$(i) = i$: w(i) = ww: v(i) = vv
  13. topComboIndex = 2 ^ maxItems - 1
  14. FOR combo = 0 TO topComboIndex
  15.     testList$ = "": testValue = 0: testWeight = 0
  16.     FOR power = 0 TO topItemsIndex
  17.         IF combo AND 2 ^ power THEN
  18.             IF testList$ = "" THEN testList$ = item$(power) ELSE testList$ = testList$ + CHR$(10) + item$(power)
  19.             testValue = testValue + v(power)
  20.             testWeight = testWeight + w(power)
  21.         END IF
  22.     NEXT
  23.     IF testWeight <= maxWeight AND testValue >= MaxValue THEN
  24.         MaxValue = testValue
  25.         saveCombo = combo
  26.         saveList$ = testList$
  27.         saveWeight = testWeight
  28.     END IF
  29.     IF combo MOD 1000 = 0 THEN LOCATE 1, 1: PRINT SPACE$(20): LOCATE 1, 1: PRINT combo 'progress
  30. PRINT "Best value found <= "; maxWeight; " was:"
  31. PRINT saveList$
  32. PRINT: PRINT "Total weight: "; saveWeight; "  Total value: "; MaxValue
  33.  
  34. DATA "map",9,150
  35. DATA "compass",13,35
  36. DATA "water",153,200
  37. DATA "sandwich",50,160
  38. DATA "glucose",15,60
  39. DATA "tin",68,45
  40. DATA "banana",27,60
  41. DATA "apple",39,40
  42. DATA "cheese",23,30
  43. DATA "beer",52,10
  44. DATA "suntan cream",11,70
  45. DATA "camera",32,30
  46. DATA "T-shirt",24,15
  47. DATA "trousers",48,10
  48. DATA "umbrella",73,40
  49. DATA "WP_trousers",42,70
  50. DATA "WP_wear",43,75
  51. DATA "note-case",22,80
  52. DATA "sunglasses",7,20
  53. DATA "towel",18,12
  54. DATA "socks",4,50
  55. DATA "book",30,10
  56.  

EDIT 2020-10-15 AM Because of indexing base 0 and indexing base 1 was a little mixed up, I was doing twice as many calculations as needed 2^23 not 2^22 which it should have been, anyway it's fixed now.
« Last Edit: October 15, 2020, 02:17:43 pm by bplus »