packages feed

Binpack 0.3.1 → 0.4

raw patch · 5 files changed

+551/−336 lines, 5 filesdep −QuickCheckPVP ok

version bump matches the API change (PVP)

Dependencies removed: QuickCheck

API changes (from Hackage documentation)

- Data.BinPack: instance Arbitrary OrderPolicy
- Data.BinPack: instance Arbitrary PlacementPolicy
- Data.BinPack: instance Eq OrderPolicy
- Data.BinPack: instance Ord OrderPolicy
- Data.BinPack: instance Show OrderPolicy
+ Data.BinPack: SumOfSquaresFit :: PlacementPolicy
+ Data.BinPack: addItem :: (Num a, Ord a) => a -> b -> Bin a b -> Bin a b
+ Data.BinPack: addItems :: (Ord a, Num a) => Bin a b -> Measure a b -> [b] -> Bin a b
+ Data.BinPack: asBin :: (Ord a, Num a) => a -> Measure a b -> [b] -> Bin a b
+ Data.BinPack: emptyBin :: (Num a, Ord a) => a -> Bin a b
+ Data.BinPack: emptyBins :: (Num a, Ord a) => a -> Int -> [Bin a b]
+ Data.BinPack: gap :: Bin a b -> a
+ Data.BinPack: items :: Bin a b -> [b]
+ Data.BinPack: tryAddItem :: (Num a, Ord a) => a -> b -> Bin a b -> Maybe (Bin a b)
- Data.BinPack: binpack :: (Num a, Ord a) => PlacementPolicy -> OrderPolicy -> Measure a b -> [a] -> [b] -> ([a], [Bin b], [b])
+ Data.BinPack: binpack :: (Num a, Ord a) => PlacementPolicy -> OrderPolicy -> Measure a b -> [Bin a b] -> [b] -> ([Bin a b], [b])
- Data.BinPack: minimizeBins :: (Num a, Ord a) => PlacementPolicy -> OrderPolicy -> Measure a b -> a -> [b] -> ([a], [Bin b])
+ Data.BinPack: minimizeBins :: (Num a, Ord a) => PlacementPolicy -> OrderPolicy -> Measure a b -> a -> [b] -> [Bin a b]
- Data.BinPack: type Bin = []
+ Data.BinPack: type Bin a b = (a, [b])

Files

Binpack.cabal view
@@ -1,5 +1,5 @@ Name:               Binpack-Version:            0.3.1+Version:            0.4 Cabal-Version:      >= 1.2 License:            BSD3 License-File:       LICENSE@@ -7,13 +7,13 @@ Maintainer:         bbb@cs.unc.edu Category:           Algorithms, Heuristics Build-Type:         Simple-Synopsis:           Common bin packing heuristics+Synopsis:           Common bin-packing heuristics. Description:    An implementation of the first-fit, modified-first-fit, last-fit, best-fit,-  worst-fit, and almost-last-fit bin packing heuristics. Items can be packed in-  order of both decreasing and increasing size (and, of course, in unmodified-  order).+  sum-of-squares-fit, worst-fit, and almost-last-fit bin packing+  heuristics. Items can be packed in order of both decreasing and increasing+  size (and, of course, in unmodified order).   .   The module supports both the standard (textbook) minimization problem   (/How many bins do I need?/) and the more practical fitting problem@@ -30,7 +30,10 @@  Library   Exposed-Modules:  Data.BinPack-  Build-Depends:    base >= 3 && < 5, haskell98, QuickCheck-  Ghc-Options:      -Wall -fno-warn-unused-binds -fno-warn-unused-imports+  Other-Modules:    Data.BinPack.Internals+                  , Data.BinPack.Internals.MFF+                  , Data.BinPack.Internals.SumOfSquares+  Build-Depends:    base >= 3 && < 5, haskell98+  Ghc-Options:      -Wall   if impl(ghc >= 6.8)     Ghc-Options:    -fwarn-tabs
Data/BinPack.hs view
@@ -27,13 +27,17 @@  {- | -This module implements a number of common bin packing heuristics: 'FirstFit',+This module implements a number of common bin-packing heuristics: 'FirstFit', 'LastFit', 'BestFit', 'WorstFit', and 'AlmostWorstFit'.  In addition, the not-so-common, but analytically superior (in terms of worst-case behavior),-'ModifiedFirstFit' heuristic is also supported. Items can be packed in order of-both 'Decreasing' and 'Increasing' size (and, of course, in unmodified order;-see 'AsGiven').+'ModifiedFirstFit' heuristic is also supported.  Further, the (slow)+'SumOfSquaresFit' heuristic, which has been considered in the context of online+bin-packing (Bender et al., 2008), is also supported. +Items can be packed in order of both 'Decreasing' and 'Increasing' size (and,+of course, in unmodified order; see 'AsGiven').++ The module supports both the standard (textbook) minimization problem (/"How many bins do I need to pack all items?"/; see 'minimizeBins' and 'countBins') and the more practical fitting problem@@ -49,9 +53,9 @@ (mostly 'Decreasing') order. This module does not enforce such assumptions, rather, any ordering can be combined with any placement heuristic. -If unsure what to pick, then try 'FirstFit' 'Decreasing' as a default. Use-'BestFit' (in combination with 'binpack') if you want your bins filled-evenly.+If unsure what to pick, then try 'FirstFit' 'Decreasing' or 'BestFit'+'Decreasing' as a default. Use 'WorstFit' 'Decreasing' (in combination with+'binpack') if you want a pre-determined number of bins filled evenly.  A short overview of the 'ModifiedFirstFit' heuristic follows. This overview is based on the description given in (Yue and Zhang, 1995).@@ -93,45 +97,49 @@  References: - * D.S. Johnson and M.R. Garey. A 71/60 Theorem for Bin-Packing.-   /Journal of Complexity/, 1:65-106, 1985.+ * D.S. Johnson and M.R. Garey (1985). A 71/60 Theorem for Bin-Packing.+   /Journal of Complexity/, 1:65-106. - * M. Yue and L. Zhang. A Simple Proof of the Inequality MFFD(L) <= 71/60+ * M. Yue and L. Zhang (1995). A Simple Proof of the Inequality MFFD(L) <= 71/60    OPT(L) + 1, L for the MFFD Bin-Packing Algorithm.-   /Acta Mathematicae Applicatae Sinica/, 11(3):318-330, 1995.+   /Acta Mathematicae Applicatae Sinica/, 11(3):318-330.++  * M.A. Bender, B. Bradley, G. Jagannathan, and K. Pillaipakkamnatt (2008).+	Sum-of-Squares Heuristics for Bin Packing and Memory Allocation.+	/ACM Journal of Experimental Algorithmics/, 12:1-19. -} -module Data.BinPack ( PlacementPolicy(..)+module Data.BinPack (+                    -- * Types+                     PlacementPolicy(..)                     , OrderPolicy (AsGiven, Increasing, Decreasing)                     , Measure-                    , Bin+                    -- * Feature Enumeration+                    -- $features                     , allOrders                     , allPlacements                     , allHeuristics+                    -- * Bin Abstraction+                    -- $bin+                    , Bin+                    , emptyBin+                    , emptyBins+                    , asBin+                    , tryAddItem+                    , addItem+                    , addItems+                    , items+                    , gap+                    -- * Bin-Packing Functions                     , minimizeBins                     , countBins                     , binpack                     ) where -import List (sortBy, sort, partition, findIndex, intersect {- testing only -}) -import Control.Monad (replicateM)---- for debugging-import Test.QuickCheck---- | How to pre-process the input.-data OrderPolicy = AsGiven     -- ^ Don't modify item order.-                 | Decreasing  -- ^ Sort from largest to smallest.-                 | Increasing  -- ^ Sort from smallest to largest.-                   deriving (Show, Eq, Ord)---- | The list of all possible 'OrderPolicy' choices. Useful for benchmarking.-allOrders :: [OrderPolicy]-allOrders = [Decreasing, Increasing, AsGiven]--instance Arbitrary OrderPolicy where-    arbitrary = elements allOrders+import Data.BinPack.Internals+import Data.BinPack.Internals.MFF (binpackMFF, minimizeMFF)+import Data.BinPack.Internals.SumOfSquares (sosfit, sosfitAnyFit)  -- | What placement heuristic should be used? data PlacementPolicy = FirstFit           -- ^ Traverse bin list from 'head' to@@ -147,120 +155,84 @@                                           -- least (but sufficient) capacity.                      | AlmostWorstFit     -- ^ Choose the 2nd to worst-fitting                                           -- bin.+                     | SumOfSquaresFit    -- ^ Choose bin such that sum-of-squares+                                          -- heuristic is minimized.                        deriving (Show, Eq, Ord) --- | The list of all possible 'PlacementPolicy' choices. Useful for benchmarking.+-- $features+-- Lists of all supported heuristics. Useful for benchmarking and testing.++-- | The list of all possible 'PlacementPolicy' choices. allPlacements :: [PlacementPolicy]-allPlacements = [FirstFit, ModifiedFirstFit, LastFit, BestFit, WorstFit, AlmostWorstFit]+allPlacements = [FirstFit, ModifiedFirstFit, LastFit, BestFit+                , WorstFit, AlmostWorstFit, SumOfSquaresFit] -instance Arbitrary PlacementPolicy where-    arbitrary = elements allPlacements+-- | The list of all possible 'OrderPolicy' choices.+allOrders :: [OrderPolicy]+allOrders = [Decreasing, Increasing, AsGiven] --- | All supported ordering and placment choices. Useful for benchmarking.+-- | All supported ordering and placment choices. allHeuristics :: [(PlacementPolicy, OrderPolicy)] allHeuristics = [(p, o) | p <- allPlacements, o <- allOrders] --- | A 'Bin' is a list of items.-type Bin = []---- | A function that maps an item @b@ to its size @a@. The constraint @('Num'--- a, 'Ord' a)@ has been omitted from the type, but is required by the exposed--- functions.-type Measure a b = (b -> a)---- | Given a 'Measure', an item @b@, a list of capacities @[a]@, and a list of--- bins @['Bin' b]@, a placement heuristic returns @Just@ an updated lists of--- capacities and bins if the item could be placed, and @Nothing@ otherwise.-type Placement a b = Measure a b -> b -> [a] -> [Bin b] ->-                                       Maybe ([a],[Bin b])- placement :: (Ord a, Num a) => PlacementPolicy -> Placement a b-placement WorstFit = worstfit-placement BestFit  = bestfit-placement FirstFit = firstfit-placement LastFit  = lastfit-placement AlmostWorstFit = almostWorstfit+placement WorstFit         = worstfit+placement BestFit          = bestfit+placement FirstFit         = firstfit+placement LastFit          = lastfit+placement AlmostWorstFit   = almostWorstfit+placement SumOfSquaresFit  = sosfitAnyFit placement ModifiedFirstFit = error "Not a simple placment policy." -order :: (Ord a) => OrderPolicy -> Order a b-order AsGiven    = const id-order Decreasing = decreasing-order Increasing = increasing --- | Given a 'Measure' for @b@s and a list of items @[b]@, an 'Order' returns--- a re-ordered version of the item list.-type Order a b = Measure a b -> [b] -> [b]+-- $bin+-- Conceptually, a bin is defined by its remaining capacity and the contained+-- items. Currently, it is just a tuple, but this may change in future+-- releases. Clients of this module should rely on the following accessor+-- functions. --- | Reorder items prior to processing. Items are placed into bins in the order--- from largest to smallest.-decreasing :: (Ord a) => Order a b-decreasing size items = sortBy decreasing' items-    where-      decreasing' x y = if size x >= size y then LT else GT --- | Reorder items prior to processing. Items are placed into bins in the order--- from smallest to largest.-increasing :: (Ord a) => Order a b-increasing size items = sortBy increasing' items-    where-      increasing' x y = if size x <= size y then LT else GT-------------------------------------------------------------------------------{- |-Bin packing without a limit on the number of bins (minimization problem).-Assumption: The maximum item size is at most the size of one bin (this is not checked).+{- | Bin-packing without a limit on the number of bins (minimization problem).+Assumption: The maximum item size is at most the size of one bin (this is not+checked).  Examples:  * Pack the words of the sentence /"Bin packing heuristics are a lot of fun!"/-  into bins of size 11, assuming the size of a word is its length.-  The 'Increasing' ordering yields a sub-optimal result that leaves a lot of empty space-  in the bins.+  into bins of size 11, assuming the size of a word is its length.  The+  'Increasing' ordering yields a sub-optimal result that leaves a lot of empty+  space in the bins.    > minimizeBins FirstFit Increasing length 11 (words "Bin packing heuristics are a lot of fun!")-  > ~~> ([1,4,4,2],[["heuristics"],["packing"],["fun!","lot"],["are","Bin","of","a"]])+  > ~~> [(2,["are","Bin","of","a"]),(4,["fun!","lot"]),(4,["packing"]),(1,["heuristics"])]  -* Similarly, for 'Int'. Note that we use 'id' as the 'Measure' for the size of an 'Int'. In this case, all bins are full.+* Similarly, for 'Int'. Note that we use 'id' as a 'Measure' of the size of an 'Int'.    > minimizeBins FirstFit Decreasing id 11 [3,7,10,3,1,3,2,4]-  > ~~> ([0,0,0],[[2,3,3,3],[4,7],[1,10]])+  > ~~> [(0,[1,10]),(0,[4,7]),(0,[2,3,3,3])]  -}  minimizeBins :: (Num a, Ord a) =>                 PlacementPolicy -- ^ How to order the items before placement.-             -> OrderPolicy     -- ^ The bin packing heuristic to use.+             -> OrderPolicy     -- ^ The bin-packing heuristic to use.              -> Measure a b     -- ^ How to size the items.              -> a               -- ^ The size of one bin.              -> [b]             -- ^ The items.-             -> ([a], [Bin b])  -- ^ The result: a list of the remaining-                                -- capacities and a list of the bins.-minimizeBins fitPol ordPol size capacity items =-    let-        fit       = placement fitPol-        items'    = order ordPol size items-    in-      case fitPol of-        ModifiedFirstFit -> minimizeMFF ordPol size capacity items-        _ -> minimize capacity size fit [] [] items'+             -> [Bin a b]       -- ^ The result: a list of 'Bins'.+minimizeBins fitPol ordPol size capacity objects =+    case fitPol of+      -- special MFF: more complicated looping; no re-ordered items.+      ModifiedFirstFit -> minimizeMFF ordPol size capacity objects+      -- special SOS: not an any-fit heuristic.+      SumOfSquaresFit  -> minimize capacity size (sosfit capacity) [] items'+      -- everything else can be handled by minimize+placement.+      _                -> minimize capacity size (placement fitPol) [] items'+    where items' = order ordPol size objects --- The actual workhorse. minimize traverses the list of items and--- tries to place each in a bin.  If an item doesn't fit anymore, then a new--- empty bin is created and the item is placed in that bin.-minimize :: (Num a, Ord a) => a -> Measure a b ->-            Placement a b -> [a] -> [Bin b] -> [b] -> ([a], [Bin b])-minimize _   _    _   caps bins []       = (caps, bins)-minimize cap size fit caps bins (x : xs) =-    case fit size x caps bins of-      Nothing             -> minimize cap size fit caps'' bins'' xs-      Just (caps', bins') -> minimize cap size fit caps' bins'   xs-    where-      -- assumption: size x <= cap. Doesn't make much sense otherwise.-      caps'' = (cap - size x) : caps-      bins'' = [x]            : bins + {- | Wrapper around 'minimizeBins'; useful if only the number of required bins is of interest. See 'minimizeBins' for a description of the arguments.@@ -273,243 +245,45 @@   > countBins FirstFit Increasing length 11 (words "Bin packing heuristics are a lot of fun!")   > ~~> 4 -* Similarly, for 'Int'. Note that we use 'id' as the 'Measure' for the size of an 'Int'.+*  Similarly, for 'Int'. As before, we use 'id' as a 'Measure' for the size of an 'Int'.    > countBins FirstFit Decreasing id 11 [3,7,10,3,1,3,2,4]   > ~~> 3  -} countBins :: (Num a, Ord a) =>-               PlacementPolicy -> OrderPolicy -> Measure a b -> a -> [b] -> Int-countBins fitPol ordPol size capacity items = length bins-    where (_, bins) = minimizeBins fitPol ordPol size capacity items+             PlacementPolicy -> OrderPolicy -> Measure a b -> a -> [b] -> Int+countBins fitPol ordPol size cap = length+                                   . minimizeBins fitPol ordPol size cap +{- | Bin-pack a list of items into a list of (possibly non-uniform) bins.  If+ an item cannot be placed, instead of creating a new bin, this version will+ return a list of items that could not be packed (if any). -{- |-Bin pack with a given limit on the number (and sizes) of bins. Instead of-creating new bins, this version will return a list of items that could not be-packed (if any).+Example: We have two empty bins, one of size 10 and one of size 12.+         Which words can we fit in there? -Example: We have two bins, one of size 10 and one of size 12. Which words can-we fit in there?+> binpack WorstFit Decreasing length [emptyBin 10, emptyBin 12]  (words "Bin packing heuristics are a lot of fun!")+> ~~> ([(0,["Bin","packing"]),(0,["of","heuristics"])],["a","lot","are","fun!"]) -> binpack WorstFit Decreasing length [10, 12]  (words "Bin packing heuristics are a lot of fun!")-> ~~> ([0,0],[["heuristics"],["a","fun!","packing"]],["of","lot","are","Bin"])--}+Both bins were filled completely, and the words /"are a lot fun!"/ coult not be+packed.  -}  binpack :: (Num a, Ord a)  =>            PlacementPolicy     -- ^ The bin packing heuristic to use.         -> OrderPolicy         -- ^ How to order the items before placement.         -> Measure a b         -- ^ How to size the items.-        -> [a]                 -- ^ Intitial per-bin capacities.+        -> [Bin a b]           -- ^ The bins; may be non-uniform and pre-filled.         -> [b]                 -- ^ The items.-        -> ([a], [Bin b], [b]) -- ^ The result; a list of residue capacities,-                               -- the bins, and a list of items that could not+        -> ([Bin a b], [b])    -- ^ The result; a list of bins+                               -- and a list of items that could not                                -- be placed.-binpack fitPol ordPol size capacities items =+binpack fitPol ordPol size bins objects =     let         fit       = placement fitPol-        emptyBins = replicate (length capacities) []-        items'    = order ordPol size items+        items'    = order ordPol size objects     in       case fitPol of-        ModifiedFirstFit -> binpackMFF ordPol size capacities emptyBins items'-        _ -> binpack' (fit size) capacities emptyBins items' []---- | Actual binpacking function. Tries to place each item in order.-binpack' :: (Num a, Ord a) =>-            (b -> [a] -> [Bin b] -> Maybe ([a], [Bin b])) -- ^ Function to-                                                          -- place on item.-         -> [a]      -- ^ Remaining capacities.-         -> [Bin b]  -- ^ The bins.-         -> [b]      -- ^ Items yet to be placed.-         -> [b]      -- ^ Items that didn't fit anywhere (accumulator).-         -> ([a], [Bin b], [b])-binpack' _   caps bins []       misfits = (caps, bins, misfits)-binpack' fit caps bins (x : xs) misfits =-    case fit x caps bins of-      Nothing             -> binpack' fit caps bins xs (x : misfits)-      Just (caps', bins') -> binpack' fit caps' bins' xs misfits-------------------------------------- Simple bin packing heuristics.---- generic X fit heuristic-xfit :: (Ord a, Num a) => (a -> a -> Bool) -> Placement a b-xfit cmp size item caps bins =-    case best Nothing caps of-      Nothing -> Nothing-      opt     -> Just (drop' False opt caps bins [] [])-    where-      fit c = if size item <= c then Just (c - size item) else Nothing-      better Nothing _ = False-      better _ Nothing = True-      better (Just a) (Just b) = a `cmp` b-      best = foldl (\ a b -> if better (fit b) a then fit b else a)-      drop' _ _ [] [] caps' bins' = (reverse caps', reverse bins')-      drop' dropped opt (c : caps) (b : bins) caps' bins' =-          if not dropped && better (fit c) opt-            then drop' True opt caps bins-                     ((c - size item) : caps')-                     ((item : b) : bins')-            else drop' dropped opt caps bins (c : caps') (b : bins')--bestfit, firstfit, lastfit, worstfit :: (Ord a, Num a) => Placement a b-bestfit  = xfit (>=)-worstfit = xfit (<=)-firstfit = xfit (==)--lastfit size item caps bins =-    case firstfit size item (reverse caps) (reverse bins) of-      Nothing             -> Nothing-      Just (caps', bins') -> Just (reverse caps', reverse  bins')---- almost worst fit: choose the 2nd to worst-fitting bin-almostWorstfit :: (Ord a, Num a) => Placement a b-almostWorstfit size item caps bins =-    let-        s          = size item-        space      = sort [ (c - s, i) | (c, i) <- zip caps (enumFrom 0), c >= s]-    in-      case space of-        []               -> Nothing-        (_, i) : []      -> Just (insertAt i item s caps bins)-        _ : ((_, i) : _) -> Just (insertAt i item s caps bins)------------------------------------------------------------------- Modified first fit heuristic (see above).--minimizeMFF :: (Num a, Ord a) =>-               OrderPolicy -> Measure a b -> a -> [b] -> ([a], [Bin b])-minimizeMFF ordPol size cap items = minimize cap size firstfit gC' gB' rest'-    where-      -- split in categories-      (lA, lC, rest)  = splitMFF cap size items-      -- pack lA items-      gBins           = map return lA-      gCaps           = map (\i -> cap - size i) lA-      (rgC, rgB)      = (reverse gCaps, reverse gBins)-      -- pack lC items-      (gC', gB', lC') = packCs size [] [] rgC rgB (increasing size lC)-      -- The rest that has yet to be packed.-      rest'           = order ordPol size $ lC' ++ rest--binpackMFF :: (Ord a, Num a) =>-              OrderPolicy -> Measure a b -> [a] -> [[b]] -> [b] -> ([a], [[b]], [b])-binpackMFF ordPol size caps bins items = (c, b, rejA ++ rej)-    where-      cap = head caps -- We use the first bin as the representative bin; the-                      -- assumption is that they are all of the same size.-      (lA, lC, rest)         = splitMFF cap size items-      -- pack the lA items-      (caps', bins', rejA)   = binpack' (firstfit size) caps bins lA []-      (rC, rB)               = (reverse caps', reverse bins')-      -- pack the lC items-      (caps'', bins'', rejC) = packCs size [] [] rC rB (increasing size lC)-      -- The rest that still might fit.-      rest'                  = order ordPol size $ rejC ++ rest-      -- pack the rest-      (c, b, rej)            = binpack' (firstfit size) caps'' bins'' rest' []----- | Split items into the A,B,C,D groups of the MFF algorithm. Only A, C, and--- | the rest are returned.-splitMFF :: (Num a, Ord a) => a -> Measure a b -> [b] -> ([b], [b], [b])-splitMFF cap size items = (lA, lC, rest)-    where-      x            = minimum . map size $ items-      (lA, items') = partition (\ i -> 2 * size i > cap) items-      (lC, rest)   = partition (\ i -> 5 * size i > cap - x && 3 * size i <= cap) items'--packCs :: (Num a, Ord a) => Measure a b-       -> [a] -> [Bin b]      -- bins that we are done with-       -> [a] -> [Bin b]      -- bins yet to do-       -> [b]                 -- remainder of lC, sorted from largest to-                              -- smallest-       -> ([a], [Bin b], [b]) -- caps, bins, remainder (reversed)-packCs _ caps bins [] [] lC        = (caps, bins, lC)-packCs _ caps bins caps2 bins2 []  = (caps ++ caps2, bins ++ bins2, [])-packCs size caps bins (c:cs) (b:bs) (s1:lC) =-    if null lC || size s1 + size s2 > c-      then packCs size (c:caps) (b:bins) cs bs (s1:lC) -- there aren't two items that fit-      else -- approximate two largest items that fit-          let lC'             = reverse lC-              Just (x1, lC'') = removeIf (\i -> size i + size s1 <= c) lC'-          in case removeIf (\i -> size i + size x1 <= c) lC'' of-               Just (x2, lC''') ->-                   -- we can ignore s1 as something larger fits, too-                   let-                       caps' = (c - size x1 - size x2 : caps)-                       bins' = ((x2:x1:b) : bins)-                   in-                     packCs size caps' bins' cs bs $ s1 : reverse lC'''-               Nothing ->-                   -- s1, the smallest item in lC, is the only that fits with x1-                   let-                       caps' = (c - size x1 - size s1 : caps)-                       bins' = ((s1:x1:b) : bins)-                   in-                     packCs size caps' bins' cs bs $ reverse lC''-    where-      s2 = head lC------------------------------------------------- Some convenience list handling functions.---- Like a map on a specific element.-update :: Int -> (a -> a) -> [a] -> [a]-update i f xs = pre ++ (f (head post) : tail post)-    where (pre, post) = splitAt i xs---- Insert an item into a bin and reduce the bin's capacity.-insertAt :: (Num a) => Int -> b -> a -> [a] -> [[b]] -> ([a], [[b]])-insertAt i x s caps bins = (update i (\c -> c - s) caps,-                            update i (\b -> x : b) bins)---- Retrieve an element from a list at a given index.-removeAt :: Int -> [a] -> (a, [a])-removeAt i xs = (head post, pre ++ tail post)-    where (pre, post) = splitAt i xs---- Retrieve the first element from a list that satisfies--- a given condition.-removeIf :: (a -> Bool) -> [a] -> Maybe (a, [a])-removeIf p lst = case findIndex p lst of-                   Just idx -> Just $ removeAt idx lst-                   Nothing  -> Nothing---------------------------------------------------------- tests--- TODO: Move into testing module and add more tests.--prop_lA, prop_lC1, prop_lC2, prop_rest :: [Double] -> Bool-prop_lA nums = all (> 0.5) lA-    where (lA, _, _) = splitMFF 1.0 id nums-prop_lC1 nums = all (<= 1/3.0) lC-    where (_, lC, _) = splitMFF 1.0 id nums-prop_lC2 nums = all (> (1.0 - x) / 5.0) lC-    where (_, lC, _) = splitMFF 1.0 id nums-          x          = minimum nums-prop_rest nums = lA `intersect` rest == [] && lC `intersect` rest == []-    where (lA, lC, rest) = splitMFF 1.0 id nums--prop_notLossy :: PlacementPolicy -> OrderPolicy -> [Double] -> Bool-prop_notLossy pPol oPol nums = sort nums == sort nums'-    where (_, bins) = minimizeBins pPol oPol id 1.0 nums-          nums'     = concat bins--prop_remCap  :: PlacementPolicy -> OrderPolicy -> [Int] -> Bool-prop_remCap pPol oPol nums = all (\ (c, b) -> sum b == 100 - c) $ zip caps bins-    where (caps, bins) = minimizeBins pPol oPol id 100 nums+        ModifiedFirstFit -> binpackMFF ordPol size bins items'+        _ -> binpack' (fit size) bins items' [] -runTests :: IO ()-runTests = do-  let n = 100-      i = replicateM n $ choose (1, 100)-      g = replicateM n $ choose (0.0, 1.0)-  quickCheck $ forAll g prop_lA-  quickCheck $ forAll g prop_lC1-  quickCheck $ forAll g prop_lC2-  quickCheck $ forAll g prop_rest-  sequence_ [quickCheck $ forAll g $ prop_notLossy p o | (p, o) <- allHeuristics]-  sequence_ [quickCheck $ forAll i $ prop_remCap p o | (p, o) <- allHeuristics]
+ Data/BinPack/Internals.hs view
@@ -0,0 +1,253 @@+-- Copyright (c) 2009, Bjoern B. Brandenburg <bbb [at] cs.unc.edu>+--+-- All rights reserved.+--+-- Redistribution and use in source and binary forms, with or without+-- modification, are permitted provided that the following conditions are met:+--     * Redistributions of source code must retain the above copyright+--       notice, this list of conditions and the following disclaimer.+--     * Redistributions in binary form must reproduce the above copyright+--       notice, this list of conditions and the following disclaimer in the+--       documentation and/or other materials provided with the distribution.+--     * Neither the name of the copyright holder nor the names of any+--       contributors may be used to endorse or promote products derived from+--       this software without specific prior written permission.+--+-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"+-- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE+-- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE+-- ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE+-- LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR+-- CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF+-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS+-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN+-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)+-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE+-- POSSIBILITY OF SUCH DAMAGE.++-- | The implementation of 'Data.BinPack'. This module should not be imported+-- directly; all relevant functions are re-exported by 'Data.BinPack'.++module Data.BinPack.Internals where++import List (sortBy+            , maximumBy+            , minimumBy+            )++import Data.Ord (comparing)++----------------------------------------------+-- Some convenience type and function aliases.++-- | How to pre-process the input.+data OrderPolicy = AsGiven     -- ^ Don't modify item order.+                 | Decreasing  -- ^ Sort from largest to smallest.+                 | Increasing  -- ^ Sort from smallest to largest.+                   deriving (Show, Eq, Ord)++-- | A function that maps an item @b@ to its size @a@. The constraint @('Num'+-- a, 'Ord' a)@ has been omitted from the type, but is required by the exposed+-- functions.+type Measure a b = (b -> a)++-- | Given a 'Measure', an item @b@, a list of capacities @[a]@, and a list of+-- bins @['Bin' b]@, a placement heuristic returns @Just@ an updated lists of+-- capacities and bins if the item could be placed, and @Nothing@ otherwise.+type Placement a b = Measure a b -> b -> [Bin a b] ->+                                       Maybe [Bin a b]++order :: (Ord a) => OrderPolicy -> Order a b+order AsGiven    = const id+order Decreasing = decreasing+order Increasing = increasing++-- | Given a 'Measure' for @b@s and a list of items @[b]@, an 'Order' returns+-- a re-ordered version of the item list.+type Order a b = Measure a b -> [b] -> [b]++-- | Reorder items prior to processing. Items are placed into bins in the order+-- from largest to smallest.+decreasing :: (Ord a) => Order a b+decreasing size xs = sortBy decreasing' xs+    where+      decreasing' x y = if size x >= size y then LT else GT++-- | Reorder items prior to processing. Items are placed into bins in the order+-- from smallest to largest.+increasing :: (Ord a) => Order a b+increasing size xs = sortBy increasing' xs+    where+      increasing' x y = if size x <= size y then LT else GT++-----------------------+-- The Bin abstraction.++-- | A 'Bin' consists of the remaining capacity together with a list of items+--   already placed.+type Bin a b = (a, [b])++-- | Create an empty bin.+emptyBin :: (Num a, Ord a) =>+            a              -- ^ The initial capacity.+            -> Bin a b     -- ^ The empty bin.+emptyBin cap = (cap, [])++-- | Create multiple empty bins with uniform capacity.+emptyBins :: (Num a, Ord a) =>+             a              -- ^ The initial capacity.+          -> Int            -- ^ Number of bins.+          -> [Bin a b]+emptyBins cap = flip replicate $ emptyBin cap++-- | Try placing an item inside a 'Bin'.+tryAddItem :: (Num a, Ord a) =>+              a                -- ^ The item's size.+           -> b                -- ^ The item.+           -> Bin a b          -- ^ The bin.+           -> Maybe (Bin a b)  -- ^ 'Just' the updated bin with the item inside,+                               -- 'Nothing' if it does not fit.+tryAddItem s _ (c, _) | s > c = Nothing+tryAddItem s x (c, xs)        = Just (c - s, x:xs)++-- | Place an item inside a 'Bin'. Fails if there is insufficient capacity.+addItem ::  (Num a, Ord a) =>+              a                -- ^ The item's size.+           -> b                -- ^ The item.+           -> Bin a b          -- ^ The bin.+           -> Bin a b          -- ^ 'Just' the updated bin with the item inside,+                               -- 'Nothing' if it does not fit.+addItem s x b = case tryAddItem s x b of+                  Nothing -> error "Bin overflow."+                  Just b' -> b'++-- | Add a list of items to an existing bin. Fails if there is+--   insufficient capacity.+addItems :: (Ord a, Num a) =>+            Bin a b           -- ^ The bin that should be augmented.+         -> Measure a b       -- ^ A function to determine each item's size.+         -> [b]               -- ^ The items that are to be added.+         ->  Bin a b          -- ^ The resulting bin.+addItems (avail, obj) size xs =+    if req <= avail+       then (avail - req, xs ++ obj)+       else error "Data.BinPack.addItems: insufficient capacity."+    where+      req = sum . map size $ xs++-- | Turn a list of items into a pre-filled bin.+asBin :: (Ord a, Num a) => a -> Measure a b -> [b] -> Bin a b+asBin cap = addItems (emptyBin cap)++makeBin :: (Ord a, Num a) => Measure a b -> a -> b -> Bin a b+makeBin size cap x = asBin cap size [x]++-- | Get the items in a bin.+items :: Bin a b -> [b]+items = snd++-- |  Get the remaining capacity of a bin.+gap :: Bin a b -> a+gap = fst++--------------------------------------------+-- Some convenience list handling functions.++-- Like a map on a specific element.+update :: Int -> (a -> a) -> [a] -> [a]+update i f xs = pre ++ (f (head rst) : tail rst)+    where (pre, rst) = splitAt i xs++-- Insert an item into a bin and reduce the bin's capacity.+insertAt :: (Num a) => Int -> b -> a -> [Bin a b] -> [Bin a b]+insertAt i x s = update i (\ (c, xs) -> (c - s, x:xs))++-- Retrieve the first element from a list that satisfies+-- a given condition.+removeIf :: (a -> Bool) -> [a] -> Maybe (a, [a])+removeIf p lst = case break p lst of+                   (_, [])    -> Nothing+                   (pre, rst) -> Just (head rst, pre ++ tail rst)++---------------------------------+-- Simple bin packing heuristics.++-- generic X fit heuristic+xfit :: (Ord a, Num a) => ([(Int, a)] -> (Int, a)) -> Placement a b+xfit _   _    _    []   = Nothing+xfit choose size item bins =+    let+        s     = size item+        gaps  = filter (\(_, g) -> g >= s) . zip [0..] . map gap+    in+      case gaps bins of+        [] -> Nothing+        pl  -> let (i, _) = choose pl in Just (insertAt i item s bins)++bestfit, firstfit, lastfit, worstfit, almostWorstfit+    :: (Ord a, Num a) => Placement a b+bestfit        = xfit chooseBest+worstfit       = xfit chooseWorst+firstfit       = xfit head+lastfit        = xfit last+almostWorstfit = xfit chooseAlmostWorst++chooseBest, chooseWorst, chooseAlmostWorst :: (Ord a, Ord b) =>+                                              [(a, b)] -> (a, b)+chooseBest  = minimumBy (comparing snd `withTieBreakOn` fst)+chooseWorst = maximumBy (comparing snd `withReverseTieBreakOn` fst)+-- almost worst fit: choose the 2nd to worst-fitting bin+chooseAlmostWorst pl = case filter (/= worst) pl of+                         []   -> worst+                         rest -> chooseWorst rest+    where worst = chooseWorst pl+++withReverseTieBreakOn, withTieBreakOn :: (Ord a, Ord b) =>+                                         (a -> a -> Ordering)+                                      -> (a -> b)+                                      -> a -> a+                                      -> Ordering+withTieBreakOn cmp key x y =+    case x `cmp` y of+      EQ   -> (key x) `compare` (key y)+      ord  -> ord++withReverseTieBreakOn cmp key x y =+    case x `cmp` y of+      EQ   -> (key y) `compare` (key x)+      ord  -> ord+++--------------------------------------------+-- The actual bin-packing functions.++-- | 'minimize' traverses the list of items and+-- tries to place each in a bin.  If an item doesn't fit anymore, then a new+-- empty bin is created and the item is placed in that bin.+minimize :: (Num a, Ord a) => a -> Measure a b ->+            Placement a b -> [Bin a b] -> [b] -> [Bin a b]+minimize _   _    _   bins []       = bins+minimize cap size fit bins (x : xs) =+    case fit size x bins of+      Just bins' -> minimize cap size fit bins'  xs+      Nothing    -> minimize cap size fit bins'' xs+    where+      -- assumption: size x <= cap. Doesn't make much sense otherwise.+      -- concat at end is ugly, but required for first/last semantics+      bins'' = bins ++ [makeBin size cap x]+++-- | Actual binpacking function. Tries to place each item in order.+binpack' :: (Num a, Ord a) =>+            (b -> [Bin a b] -> Maybe [Bin a b])  -- ^ Function to+                                                 -- place on item.+         -> [Bin a b]  -- ^ The bins.+         -> [b]        -- ^ Items yet to be placed.+         -> [b]        -- ^ Items that didn't fit anywhere (accumulator).+         -> ([Bin a b], [b])+binpack' _   bins []       misfits = (bins, misfits)+binpack' fit bins (x : xs) misfits =+    case fit x bins of+      Nothing    -> binpack' fit bins  xs (x : misfits)+      Just bins' -> binpack' fit bins' xs misfits
+ Data/BinPack/Internals/MFF.hs view
@@ -0,0 +1,105 @@+-- Copyright (c) 2009, Bjoern B. Brandenburg <bbb [at] cs.unc.edu>+--+-- All rights reserved.+--+-- Redistribution and use in source and binary forms, with or without+-- modification, are permitted provided that the following conditions are met:+--     * Redistributions of source code must retain the above copyright+--       notice, this list of conditions and the following disclaimer.+--     * Redistributions in binary form must reproduce the above copyright+--       notice, this list of conditions and the following disclaimer in the+--       documentation and/or other materials provided with the distribution.+--     * Neither the name of the copyright holder nor the names of any+--       contributors may be used to endorse or promote products derived from+--       this software without specific prior written permission.+--+-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"+-- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE+-- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE+-- ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE+-- LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR+-- CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF+-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS+-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN+-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)+-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE+-- POSSIBILITY OF SUCH DAMAGE.++-- | Modified-first-fit heuristic support.++module Data.BinPack.Internals.MFF where++import Data.BinPack.Internals++import List (partition)++minimizeMFF :: (Num a, Ord a) =>+               OrderPolicy -> Measure a b -> a -> [b] -> [Bin a b]+minimizeMFF ordPol size cap objects = minimize cap size firstfit gB' rest'+    where+      -- split in categories+      (lA, lC, rest)  = splitMFF cap size objects+      -- pack lA items+      bins           = map (makeBin size cap) lA+      -- pack lC items+      (gB', lC')      = packCs size [] (reverse bins) (increasing size lC)+      -- The rest that has yet to be packed.+      rest'           = order ordPol size $ lC' ++ rest++binpackMFF :: (Ord a, Num a) =>+              OrderPolicy -> Measure a b -> [Bin a b] -> [b] -> ([Bin a b], [b])+binpackMFF ordPol size bins objects = (bins''', rejA ++ rej)+    where+      (cap, _) = head bins -- We use the first bin as the representative bin; the+                           -- assumption is that they are all of the same size.+      (lA, lC, rest) = splitMFF cap size objects+      -- pack the lA items+      (bins', rejA)  = binpack' (firstfit size) bins lA []+      -- pack the lC items+      (bins'', rejC) = packCs size [] (reverse bins') (increasing size lC)+      -- The rest that still might fit.+      rest'          = order ordPol size $ rejC ++ rest+      -- pack the rest+      (bins''', rej)    = binpack' (firstfit size) bins'' rest' []+++-- | Split items into the A,B,C,D groups of the MFF algorithm. Only A, C, and+-- | the rest are returned.+splitMFF :: (Num a, Ord a) => a -> Measure a b -> [b] -> ([b], [b], [b])+splitMFF cap size objects = (lA, lC, rest)+    where+      x            = minimum . map size $ objects+      (lA, items') = partition (\ i -> 2 * size i > cap) objects+      (lC, rest)   = partition (\ i -> 5 * size i > cap - x && 3 * size i <= cap) items'++packCs :: (Num a, Ord a) =>+          Measure a b         -- sizing function+       -> [Bin a b]           -- bins that we are done with+       -> [Bin a b]           -- bins yet to do+       -> [b]                 -- remainder of lC, sorted from largest to+                              -- smallest+       -> ([Bin a b], [b])    -- caps, bins, remainder (reversed)+packCs _ bins []    lC  = (bins, lC)+packCs _ bins bins2 []  = (bins ++ bins2, [])+packCs size bins ((c,b):bs) (s1:lC) =+    if null lC || size s1 + size s2 > c+      then packCs size ((c,b):bins) bs (s1:lC) -- there aren't two fitting items+      else -- approximate two largest items that fit+          let lC'             = reverse lC+              Just (x1, lC'') = removeIf (\i -> size i + size s1 <= c) lC'+          in case removeIf (\i -> size i + size x1 <= c) lC'' of+               Just (x2, lC''') ->+                   -- we can ignore s1 as something larger fits, too+                   let+                       bins' = (c - size x1 - size x2, (x2:x1:b)) : bins+                   in+                     packCs size bins' bs $ s1 : reverse lC'''+               Nothing ->+                   -- s1, the smallest item in lC, is the only that fits with x1+                   let+                       bins' = (c - size x1 - size s1, s1:x1:b) : bins+                   in+                     packCs size bins' bs $ reverse lC''+    where+      s2 = head lC+
+ Data/BinPack/Internals/SumOfSquares.hs view
@@ -0,0 +1,80 @@+-- Copyright (c) 2009, Bjoern B. Brandenburg <bbb [at] cs.unc.edu>+--+-- All rights reserved.+--+-- Redistribution and use in source and binary forms, with or without+-- modification, are permitted provided that the following conditions are met:+--     * Redistributions of source code must retain the above copyright+--       notice, this list of conditions and the following disclaimer.+--     * Redistributions in binary form must reproduce the above copyright+--       notice, this list of conditions and the following disclaimer in the+--       documentation and/or other materials provided with the distribution.+--     * Neither the name of the copyright holder nor the names of any+--       contributors may be used to endorse or promote products derived from+--       this software without specific prior written permission.+--+-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"+-- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE+-- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE+-- ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE+-- LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR+-- CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF+-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS+-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN+-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)+-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE+-- POSSIBILITY OF SUCH DAMAGE.++-- | Sum-of-squares heuristic support.++module Data.BinPack.Internals.SumOfSquares where++import Data.List ( group+                 , sort+                 , minimumBy)++import Data.Ord (comparing)++import Data.BinPack.Internals++-- | Sum of squares metric. The sum of the square of the counts of each gap+--   size, ignoring empty and completely-packed bins.+sumOfSquares :: (Num a, Ord a) =>+                [Bin a b]  -- ^ The bins.+             -> Int        -- ^ Sum of the squared 'gapCount's.+sumOfSquares = sum+               . map sqrlen                   -- square heuristic+               . group . sort                 -- find bins with equal gaps+               . filter (/= 0)                -- ignore completely packed bins+               . map gap                      -- determine gap+               . filter (not . null . items)  -- ignore empty bins+    where sqrlen xs = length xs * length xs++-- | Pick a bin that minimizes the sum-of-squares heuristic.+sosfit' :: (Ord a, Num a) =>+           Measure a b -> b -> [Bin a b] -> Maybe (Int, [Bin a b])+sosfit' _    _    []   = Nothing+sosfit' size item bins =+    let+        s       = size item+        placed  = map (\(_,bs) -> (sumOfSquares bs, bs))+                  . map (\(i,_) -> (i, insertAt i item s bins))+                  . filter (\(_,b) -> gap b >= s)+                  . zip [0..]+        best    = minimumBy (comparing fst)+    in+      case placed bins of+        [] -> Nothing+        pl  -> Just $ best pl++-- | sosfit, but without the option of adding an additional bin.+sosfitAnyFit :: (Ord a, Num a) => Placement a b+sosfitAnyFit size item = fmap snd . sosfit' size item++-- | sofit, which may add a bin if it lowers the sum-of-squares heuristic.+sosfit :: (Ord a, Num a) => a -> Placement a b+sosfit cap size item bins = fmap testAppend . sosfit' size item $ bins+    where+        app = bins ++ [makeBin size cap item]+        sos = sumOfSquares app+        testAppend (sos', bins') = if sos' <= sos then bins' else app