簡體   English   中英

Haskell:列出所有公因數

[英]Haskell: List all common factors

我正在學習 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)

如果您不太關心 memory 的用法,您可以只使用Data.IntSet和 function 來查找給定數字的所有因子來執行此操作。

首先,讓我們創建一個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 function 可以采用多個IntSet並產生最終的交叉點結果。

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

那應該在IntSet的列表上折疊以找到最終的交集

現在您可以將commonFactors重構為-

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

更好的? 我想是的。 最后一個改進怎么樣,一個通用的commonFactors function 用於n個整數

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

請注意,這是使用IntSet ,因此它自然僅限於Int 如果您想改用Integer - 只需將IntSet替換為常規Set Integer

Output

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

您應該使用標准算法對他們的 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]

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM