{- Copyright (C) 2011 Dr. Alistair Ward This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. -} {- | [@AUTHOR@] Dr. Alistair Ward [@DESCRIPTION@] * Provides a polymorphic algorithm, to /unfold/ a list into a tree, to which an /associative binary operator/ is then applied to re-/fold/ the tree to a /scalar/. * Implementations of this strategy have been provided for /addition/ and /multiplication/, though other associative binary operators, like 'gcd' or 'lcm' could also be used. * Where the contents of the list are consecutive, a more efficient implementation is available in /Factory.Data.Interval/. -} module Factory.Math.DivideAndConquer( -- * Types -- ** Type-synonyms BisectionRatio, MinLength, -- * Functions divideAndConquer, product', sum' ) where import Control.Arrow((***)) import qualified Control.Parallel.Strategies import qualified Data.Monoid import qualified Data.Ratio {- | * The ratio of the original list-length at which to bisect. * CAVEAT: the value can overflow. -} type BisectionRatio = Data.Ratio.Ratio Int -- | The list-length beneath which to terminate bisection. type MinLength = Int {- | * Reduces a list to a single scalar encapsulated in a 'Data.Monoid.Monoid', using a /divide-and-conquer/ strategy, bisecting the list and recursively evaluating each part; <https://en.wikipedia.org/wiki/Divide_and_conquer_algorithm>. * By choosing a 'bisectionRatio' other than @(1 % 2)@, the bisection can be made asymmetrical. The specified ratio represents the length of the left-hand portion, over the original list-length; eg. @(1 % 3)@ results in the first part, half the length of the second. * This process of recursive bisection, is terminated beneath the specified minimum list-length, after which the /monoid/'s binary operator is directly /folded/ over the list. * One can view this as a <https://en.wikipedia.org/wiki/Hylomorphism_%28computer_science%29>, in which the list is exploded into a binary tree-structure (each leaf of which contains a list of up to 'minLength' integers, and each node of which contains an associative binary operator), and then collapsed to a scalar, by application of the operators. -} divideAndConquer :: Data.Monoid.Monoid monoid => BisectionRatio -- ^ The ratio of the original list-length at which to bisect. -> MinLength -- ^ For efficiency, the list will not be bisected, when it's length has been reduced to this value. -> [monoid] -- ^ The list on which to operate. -> monoid -- ^ The resulting scalar. divideAndConquer bisectionRatio minLength l | any ($ apportion minLength) [ (< 1), -- The left-hand list may be null. (> pred minLength) -- The right-hand list may be null. ] = error $ "Factory.Math.DivideAndConquer.divideAndConquer:\tbisectionRatio='" ++ show bisectionRatio ++ "' is incompatible with minLength=" ++ show minLength ++ "." | otherwise = slave (length l) l where apportion :: Int -> Int apportion list = (list * Data.Ratio.numerator bisectionRatio) `div` Data.Ratio.denominator bisectionRatio slave len list | len <= minLength = Data.Monoid.mconcat list -- Fold the monoid's binary operator over the list. | otherwise = uncurry Data.Monoid.mappend . Control.Parallel.Strategies.withStrategy ( Control.Parallel.Strategies.parTuple2 Control.Parallel.Strategies.rseq Control.Parallel.Strategies.rseq ) . (slave cut *** slave (len - cut)) $ splitAt cut list where -- Apply the monoid's binary operator to the two operands resulting from bisection. cut = apportion len {- | * Multiplies the specified list of numbers. * Since the result can be large, 'divideAndConquer' is used in an attempt to form operands of a similar order of magnitude, which creates scope for the use of more efficient multiplication-algorithms. -} product' :: Num n => BisectionRatio -- ^ The ratio of the original list-length at which to bisect. -> MinLength -- ^ For efficiency, the list will not be bisected, when it's length has been reduced to this value. -> [n] -- ^ The numbers whose product is required. -> n -- ^ The resulting product. product' bisectionRatio minLength = Data.Monoid.getProduct . divideAndConquer bisectionRatio minLength . map Data.Monoid.Product {- | * Sums the specified list of numbers. * Since the result can be large, 'divideAndConquer' is used in an attempt to form operands of a similar order of magnitude, which creates scope for the use of more efficient multiplication-algorithms. /Multiplication/ is required for the /addition/ of 'Rational' numbers by cross-multiplication; this function is unlikely to be useful for other numbers. -} sum' :: Num n => BisectionRatio -- ^ The ratio of the original list-length at which to bisect. -> MinLength -- ^ For efficiency, the list will not be bisected, when it's length has been reduced to this value. -> [n] -- ^ The numbers whose sum is required. -> n -- ^ The resulting sum. sum' bisectionRatio minLength = Data.Monoid.getSum . divideAndConquer bisectionRatio minLength . map Data.Monoid.Sum