【问题标题】:Haskell: List all common factorsHaskell:列出所有公因数
【发布时间】:2021-02-25 14:34:16
【问题描述】:

我正在学习 Haskell,目前正在创建一个程序,该程序可以从 3 个不同的 Int:s 中找到所有公约数。 我有一个工作程序,但对大数字的评估时间很长。我想要关于如何优化它的建议。

示例:combineDivisors 234944 246744 144456 == [1,2,4,8]

如前所述,我对此很陌生,因此感谢您提供任何帮助。

import Data.List

combineDivisors :: Int -> Int -> Int -> [Int]
combineDivisors n1 n2 n3 =
    mergeSort list
    where list = getTrips concList
          concList = isDivisor n1 ++ isDivisor n2 ++ isDivisor n3
             
isDivisor n = [x | x <- [1..n], mod n x == 0]

getTriplets :: Ord a => [a] -> [a]
getTriplets = map head . filter (\l -> length l > 2) . group . sort


--Merge sort--

split :: [a] -> ([a],[a])
split xs =
   let
     l = length xs `div` 2
   in
    (take l xs, drop l xs)

merge :: [Int] -> [Int] -> [Int]
merge [] ys = ys
merge xs [] = xs
merge (x:xs) (y:ys)
   | y < x = y : merge (x:xs) ys
   | otherwise = x : merge xs (y:ys)

mergeSort :: [Int] -> [Int]
mergeSort [] = []
mergeSort [x] = [x]
mergeSort xs =
   let
     (xs1,xs2) = split xs
   in
    merge (mergeSort xs1) (mergeSort xs2)

【问题讨论】:

  • 函数isDivisor 的名称似乎暗示它返回一个布尔值。也许最好使用divisorList。效率:您可以从将数字分解为素数的幂开始。可能在素数上查看Wiki page

标签: list sorting haskell


【解决方案1】:

如果您不太关心内存使用情况,您可以使用Data.IntSet 和一个函数来查找给定数字的所有因素。

首先,让我们创建一个函数,它返回一个数字的所有因子的IntSet-

import qualified Data.IntSet as IntSet

factors :: Int -> IntSet.IntSet
factors n = IntSet.fromList . f $ 1    -- Convert the list of factors into a set
  where
      -- Actual function that returns the list of factors
      f :: Int -> [Int]
      f i
        -- Exit when i has surpassed square root of n
        | i * i > n = []
        | otherwise = if n `mod` i == 0
            -- n is divisible by i - add i and n / i to the list
            then i : n `div` i : f (i + 1)
            -- n is not divisible by i - continue to the next
            else f (i + 1)

现在,一旦您拥有与每个数字对应的IntSet,您只需对它们执行intersection 即可获得结果

commonFactors :: Int -> Int -> Int -> [Int]
commonFactors n1 n2 n3 = IntSet.toList $ IntSet.intersection (factors n3) $ IntSet.intersection (factors n1) $ factors n2

这可行,但有点难看。如何创建一个intersections 函数,它可以接受多个IntSets 并产生最终的交集结果。

intersections :: [IntSet.IntSet] -> IntSet.IntSet
intersections [] = IntSet.empty
intersections (t:ts) = foldl IntSet.intersection t ts

这应该折叠在IntSets 的列表中以找到最终的交叉点

现在你可以将commonFactors重构为-

commonFactors :: Int -> Int -> Int -> [Int]
commonFactors n1 n2 n3 = IntSet.toList . intersections $ [factors n1, factors n2, factors n3]

更好?我会这么认为。最后一项改进怎么样,一个通用的 commonFactors 函数,用于 n 整数数量

commonFactors :: [Int] -> [Int]
commonFactors = IntSet.toList . intersections . map factors

请注意,这里使用的是IntSet,因此自然仅限于Ints。如果您想改用Integer - 只需将IntSet 替换为常规Set Integer

输出

> commonFactors [234944, 246744, 144456]
[1,2,4,8]

【讨论】:

    【解决方案2】:

    你应该使用标准算法来分解他们的 GCD:

    import Data.List
    import qualified Data.Map.Strict as M
    
    -- infinite list of primes
    primes :: [Integer]
    primes = 2:3:filter
        (\n -> not $ any
            (\p -> n `mod` p == 0)
            (takeWhile (\p -> p * p <= n) primes))
        [5,7..]
    
    -- prime factorizing a number
    primeFactorize :: Integer -> [Integer]
    primeFactorize n
        | n <= 1 = []
        -- we search up to the square root to find a prime factor
        -- if we find one then add it to the list, divide and recurse
        | Just p <- find
            (\p -> n `mod` p == 0)
            (takeWhile (\p -> p * p <= n) primes) = p:primeFactorize (n `div` p)
        -- if we don't then the number has to be prime so we're done
        | otherwise = [n]
    
    -- count the number of each element in a list
    -- e.g.
    -- getCounts [1, 2, 2, 3, 4] == fromList [(1, 1), (2, 2), (3, 1), (4, 1)]
    getCounts :: (Ord a) => [a] -> M.Map a Int
    getCounts [] = M.empty
    getCounts (x:xs) = M.insertWith (const (+1)) x 1 m
        where m = getCounts xs
    
    -- get all possible combinations from a map of counts
    -- e.g. getCombos (M.fromList [('a', 2), ('b', 1), ('c', 2)])
    -- == ["","c","cc","b","bc","bcc","a","ac","acc","ab","abc","abcc","aa","aac","aacc","aab","aabc","aabcc"]
    getCombos :: M.Map a Int -> [[a]]
    getCombos m = allFactors
        where
            list = M.toList m
            factors = fst <$> list
            counts = snd <$> list
            possible = (\n -> [0..n]) <$> counts
            allCounts = sequence possible
            allFactors = (\count -> concat $ zipWith replicate count factors) <$> allCounts
    
    -- get the common factors of a list of numbers
    commonFactorsList :: [Integer] -> [Integer]
    commonFactorsList [] = []
    commonFactorsList l = sort factors
        where
            totalGcd = foldl1 gcd l
            -- then get the combinations them and take their products to get the factor
            factors = map product . getCombos . getCounts . primeFactorize $ totalGcd
    
    -- helper function for 3 numbers
    commonFactors3 :: Integer -> Integer -> Integer -> [Integer]
    commonFactors3 a b c = commonFactorsList [a, b, c]
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-02-24
      • 1970-01-01
      • 2020-05-12
      • 1970-01-01
      • 2021-10-13
      相关资源
      最近更新 更多