Pick folders to match a specific size (Knapsack)

I have a lot of folder containing work-projects. Regularily I burn these projects to CDs, but the prosess of picking out the projects to fill up the CD is quite timeconsuming. Therefore I want to make a script that automaticly picks the best combination of projects, and puts them in a new folder.

I know this can be done with the Knapsack algoritm (0-1) , but I cannot get it to work.
Now I wonder if someone has a working implementation of knapsack, or has some links/tips.
Its seems to me that the algoritmen picks the same folder many times. (?)

My nonworking knapsack is seen below. (btw. i’m very new to applescipt :slight_smile:

on knapsack(cap, results)
set myBest to 0 – best result so far
set gem to {0, 0, 0} – reference to used item

if cap ? 0 then
	return 0
else if (cap > 0) then
	set myTime to current date
	
	repeat with a from 1 to n
		if ((current date) - myTime ? 5) then
			exit repeat
		end if
		set mySpace to (cap - (first item of item a of myWeights))
		if mySpace ? 0 then
			set b to 1
			repeat n times
				set item b of results to 0
				set b to (b + 1)
			end repeat
			
			set t to (first item of item a of myWeights) + knapsack(mySpace, results)
			if t > myBest then
				-- best result so far
				set myBest to t
				set gem to a
				set item a of results to gem
				display dialog "results: " & results
				repeat with k from 1 to n
					set item k of results to item k of myItemCount
				end repeat
			end if
			
		end if
		set a to (a + 1)
	end repeat
end if
if (myBest > 0) then
	-- remember what we did
	display dialog "results " & last item of results
	set addV to item gem of results
	set item gem of results to addV + 1
end if
return myBest

end knapsack[/b]

Step 1: my mathematical knowledge is really basic.
Step 2: my english is basic.
Step 3: I don’t think this is really a “knapsack” problem.
Step 4: I was looking to create the same script you’re describing, so I’ve been reading a bit about the knapsack thing and I wrote a “knapfake” solution. As I understand it, to create the perfect “distributor”, you should check every posible combination of sizes-of-items-near-to-a-max-size. This script will create a list of items, then distribute them in “folders” of a max size, but not extensivelly. Eg:
MAX SIZE = 100 MB
Item 1 = 50 MB
Item 2 = 45 MB
Item 3 = 40 MB
Item 4 = 10 MB
This script would act as follow:

  1. Item 1 is 50 MB. Fits in the first “folder” (0 MB), so move it there.
  2. Item 2 is 45 MB. Fits in the first “folder” (50 MB), so move it there.
  3. Item 3 is 40 MB. Does not fit in the first “folder” (95 MB), so move it to a new folder.
  4. Item 4 is 10 MB. Does not fit in the first “folder” (95 MB); does fit in the second “folder” (40 MB), so move it there.
    This example is not very clear but, basically, it does not check every possible combination of sizes-of-items, but places sequentially every item in the first folder where it can be placed, or moves it to a new folder. Maybe useful if your projects are of a little-variable-size (eg, some of 70MB, lots of 20MB and some more lots of 5MB), then you’d get an acceptable accuracy.
    The code:
set maxMB to 695 --> fine for a 700MB CD
set maxbytes to maxMB * (1024 ^ 2)

set inputFolder to (choose folder with prompt "Choose the folder where items to be sub-divided are located currently")
set outputFolder to (choose folder with prompt "Choose the folder where I should locate the items in different folders of max-sixe " & maxMB & " MB")

tell application "Finder" to ¬
	{size, name} of items of inputFolder

set mixedList to sortLists from (mixLists(result's item 1, result's item 2)) --> I'm not sure "sortLists" is relevant in this algorithm (???)

set folders to {{}} --> a list with an "empty folder"
set excludedItems to {} --> items shifted from mixedList
set badItems to {} --> items whose size is higher than "maxbytes"

repeat with i from (count mixedList) to 1 by -1
	if i is not in excludedItems and i is not in badItems then --> process this item
		set itemSize to mixedList's item i's item 1
		if itemSize > maxbytes then
			set badItems's end to i
		else --> try to add item to current "folder"
			set itemWasAddedToAnExistingFolder to false
			repeat with x from 1 to count folders --> folders's item x is {{size,name},{size,name},...}, Try to add this item to an existing folder
				set folderSum to sumFolder(folders's item x)
				if (itemSize + folderSum) < maxbytes then --> add item to this "folder"
					set end of folders's item x to mixedList's item i
					set itemWasAddedToAnExistingFolder to true
					exit repeat
				end if
			end repeat
			if not itemWasAddedToAnExistingFolder then
				-- display dialog ("foldersum is " & (folderSum / (1024 ^ 2)) & "MB" & return & "size of " & mixedList's item i's item 2 & " is " & (itemSize / (1024 ^ 2)) & return & return & "SO, I'll add this item to a new folder")
				set end of folders to {mixedList's item i}
			end if
			set end of excludedItems to i --> items is now inside a "folder", so exclude it from "mixedList"
		end if
	end if
end repeat

--> distribute stuff
repeat with i from 1 to count folders --> "folders" is now a list of {{{size, name}, {size, name}}, {{size, name}, {size, name}}}
	set currentFolder to folders's item i --> {{size, name}, {size, name}}
	--> create a folder with a dummy sequential name, where we will place "files" in this "folder"
	set newFolder to ((outputFolder as text) & "dummy" & i)
	do shell script "mkdir -p " & quoted form of POSIX path of newFolder
	repeat with singleFile in currentFolder
		set filepath to alias ((inputFolder as text) & item 2 of singleFile)
		--> if this script works fine, uncomment the following line and comment the second-following one (move, instead of duplicate)
		-- tell application "Finder" to move filepath to newFolder replacing yes
		tell application "Finder" to duplicate filepath to alias newFolder replacing yes
	end repeat
end repeat

beep 2

to sumFolder(l) --> l is a list as {{size,name},{size,name}}
	set sum to 0
	repeat with z in l
		set sum to sum + (z's item 1)
	end repeat
	sum
end sumFolder

to mixLists(a, b)
	script foo
		property x : a
		property y : b
		property nl : {}
	end script
	repeat with i from 1 to count foo's x
		set foo's nl's end to {foo's x's item i, foo's y's item i}
	end repeat
	foo's nl
end mixLists

to sortLists from rList --> credits to Kai Edwards
	if rList's length < 2 then return rList
	set {l, h, {m, s}} to {{}, {}, rList's {item 1, rest}}
	repeat with r in s
		if m's item 1 > r's item 1 then
			set l's end to r's contents
		else
			set h's end to r's contents
		end if
	end repeat
	if l's length > 1 then set l to sortLists from l
	if h's length > 1 then set h to sortLists from h
	l & {m} & h
end sortLists

Hmmm… Talking about “knap”… I’m sure Mr. Knapp would create a much more quick and accurate algorithm :rolleyes:

Thanks!! :slight_smile:

This script will be very helpful! I’m testing it right now and it seems to work brilliantly!
Now I have more time surfing the web :wink:

Eivind