Skip to content

Instantly share code, notes, and snippets.

@itsthejb
Last active November 1, 2018 07:21
Show Gist options
  • Save itsthejb/7f0b64eb6a7e9eee1767483ed1c2ebce to your computer and use it in GitHub Desktop.
Save itsthejb/7f0b64eb6a7e9eee1767483ed1c2ebce to your computer and use it in GitHub Desktop.
Pack dirs to maximise fill space (Packer problem)
#!/usr/bin/env python3
import sys
import shutil
from functools import reduce
from bisect import insort
try:
from os import scandir, walk
except ImportError:
from scandir import scandir, walk
class DirPacker(object):
targetSize = 0
candidates = []
dirMap = None
results = None
bestFit = None
def __init__(self, candidates, targetSize):
self.targetSize = targetSize
self.candidates = candidates
print("Target size {}, {} candiate directories".format(targetSize, len(candidates)))
def calculate(self):
dirMap = self.DirMap(self.candidates, self.targetSize)
results = []
for i in range(dirMap.size):
total = 0
current = []
for j in range(i, dirMap.size):
t = dirMap.sortedTuples[j]
if total + t.size <= self.targetSize:
current.append(t)
total += t.size
if current:
insort(results, current)
results.reverse()
self.dirMap = dirMap
self.results = results
self.bestFit = DirPacker.Result(results[0], self.targetSize) if results else None
class Result(object):
paths = None
total = None
remaining = None
targetSize = None
def __init__(self, dirs, targetSize):
paths = []
total = 0
for d in dirs:
paths.append(d.path)
total += d.size
self.paths = paths
self.total = total
self.targetSize = targetSize
self.remaining = targetSize - total
assert self.remaining >= 0, "Remaining size is -ve {remaining}".format(remaining=str(self.remaining))
def __repr__(self):
return "Available: {target}, Max Pack: {actual}, Remaining: {diff}\nPaths: {paths}".format(target=str(self.targetSize),
actual=str(self.total),
diff=str(self.remaining),
paths=str(self.paths))
def output(self, verbose):
if verbose:
return self.__repr__
else:
return "\n".join(self.paths)
class DirMap(object):
sortedTuples = []
size = 0
def __init__(self, dirs, space):
sortedTuples = []
for d in dirs:
t = DirPacker.DirTuple(d)
if t.size <= space:
insort(sortedTuples, t)
sortedTuples.reverse()
self.sortedTuples = sortedTuples
self.size = len(self.sortedTuples)
class DirTuple(object):
path = ""
size = 0
def __init__(self, dir):
self.path = dir
self.size = self.sizeOf(dir)
def __lt__(self, other):
return self.size < other.size
def __repr__(self):
return "{path} <{size}>".format(path=self.path, size=str(self.size))
def sizeOf(self, dir):
# l = lambda t,f: f.stat().st_size + t if f.is_file else t
# return reduce(l, os.scandir(dir))
# TODO use reduce
c = 0
for f in scandir(dir):
c += f.stat().st_size
# if f.is_file:
return c
def stdInDirs():
res = []
print("Reading STDIN...")
for line in sys.stdin:
res.append(line.strip())
return res
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: " + sys.argv[0] + " <-v> <target parent directory>")
print("Example: find /mnt/1/Media/Movies/* -type d | dir-packer /mnt/2/Media/Movies")
exit(1)
verbose = "-v" in sys.argv
usage = shutil.disk_usage(sys.argv[-1])
res = DirPacker(stdInDirs(), usage.free)
res.calculate()
if res.bestFit:
print("Fill the destination with:")
print(res.bestFit.output(verbose))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment