knapfake

Pick a chunk of items and distribute them in folders of a maximum size (eg, create folders to burn in 700MB CDs).

OS version: OS X

(*

knapfake 1.0

Somebody asked at bbs.applescript.net for a script which would take a bunch of files, then divide them in folders of a maximum size (useful to burn CDs, etc.). I was also looking for such tool, so I tried to write my own. The user mentioned "knapsack". I'm not sure this is related to the knapsack problem, and absolutelly sure this wouldn't be a nice implementation of such algorithm, so I called this one "knapfake".

As I understand it, the perfect algorithm would check any file against any file, creating the perfect combination to fit the maximum allowed size in different containers. "knapfake" does not work so, and most probably someone with a strong mathematical background should create SUCH script. Meanwhile, you can use this one, which:

a) Creates a list of sizes and names of folders/files (any item within "inputFolder")
b) Sorts such list in decreasing order
c) Attempts to fit each item within the first "virtual folder" of a maximum size
d) If it does exceed the maximum size, it attempts the same operation in the second "virtual folder", if it does exist. Otherwise, it creates a new folder and places the item within such "new virtual folder"
e) When all items are virtually distributed, they are moved to sub-folders within "outputFolder"
f) If any item has a size greater than the maximum allowed one, it is placed in a "badItems" list --> implement yourself the actions to be executed with such list

You must CONFIGURE the value "maxMB" (by default 690 -MB-)
You must define manually or programatically both "inputFolder" (the folder containing lots of items to be sub-divided into folders of maxMB size) and "outputFolder" (the folder where the new sub-folders will be created, and items moved within)

Actually, the script will duplicate the items to the sub-folders. If you like this script, you can ALSO configure it to MOVE the items to the sub-folders, instead of duplicating them. Search this code for "YOHOO!" and follow the instructions.


Enjoy!


Pescados Software - 19, october, 2004

*)

set maxMB to 690
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
				set end of folders to {mixedList's item i} --> add to a new "folder"
			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)
		--> YOHOO! if this script works fine, substitute in the following line "duplicate" with "move"
		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