我正在与Haskell一起处理Facebook Hackercup 2015问题并被卡在this problem上。
输入:以整数T开头,问题数量。对于每个问题,有一行包含3个以空格分隔的整数:A,B和K。
输出:对于第i个问题,打印一行包含“Case #i:”的行,后跟包含范围[A,B]中的原点数为K的整数。
数字X的优先级是其主要因素的数量。例如,12的优先级是2(因为它可以被素数2和3整除),550的优先级是3(因为它可以被素数2,5和11整除),7的优先级是1(作为只有素数可以被7整除。
1≤T≤100 2≤A≤B≤10^ 7 1≤K≤10^ 9
这是我的Haskell解决方案:
import System.IO
import Data.List
import Control.Monad
incEvery :: Int -> [(Int -> Int)]
incEvery n = (cycle ((replicate (n-1) id) ++ [(+ 1)]))
primes2 :: [Int]
primes2 = sieve 2 (replicate (10^7) 0)
where
sieve _ [] = []
sieve n (a:xs) = (a + (if a == 0 then 1 else 0))
: if a == 0 then
sieve (n+1) (zipWith ($) (incEvery n) xs)
else
sieve (n+1) xs
process :: (Int, Int, Int) -> Int
process (lo, hi, k) =
length . filter (\(a, b) -> a >= lo && a <= hi && b == k) . zip [2,3..] $ primes2
readIn :: [String] -> (Int, Int, Int)
readIn =
(\[x, y, z] -> (x, y, z)) . fmap (read::String->Int) . take 3
lib :: String -> String
lib xs = unlines . fmap (\(i, x) -> "Case #" ++ (show i) ++ ": " ++ x) . zip [1,2..]
. fmap parse . tail . lines $ xs
where
parse = (show . process . readIn . words)
main :: IO ()
main = interact lib
这是我的Perl解决方案:
use strict;
use warnings;
my $max = 10000010;
my @f = (0) x $max;
for my $i (2 .. $max) {
if($f[$i] == 0) {
$f[$i] = 1;
# print $i . "\n";
for my $j (2 .. ($max / $i)) {
$f[$i * $j] ++;
}
}
}
my $k = <STDIN>;
for my $i (1 .. $k) {
my $line = <STDIN>;
if($line) {
chomp $line;
my ($a, $b, $t) = split(' ', $line);
my $ans = 0;
for my $j ($a .. $b) {
if($f[$j] == $t) {
$ans ++;
}
}
print "Case #$i: " . $ans . "\n";
}
}
虽然我对两种语言使用相同的筛选算法,但Haskell版本在10 ^ 7比例的数据上明显慢于Perl版本。 基本上,以下Haskell函数比它的Perl函数慢:
incEvery :: Int -> [(Int -> Int)]
incEvery n = (cycle ((replicate (n-1) id) ++ [(+ 1)]))
primes2 :: [Int]
primes2 = sieve 2 (replicate (10^7) 0)
where
sieve _ [] = []
sieve n (a:xs) = (a + (if a == 0 then 1 else 0))
: if a == 0 then
sieve (n+1) (zipWith ($) (incEvery n) xs)
else
sieve (n+1) xs
我认为递归和(zipWith ($) (incEvery n) xs)
都会导致问题。有什么想法吗?
答案 0 :(得分:8)
绝对没有理由要求使用命令式编程来获得性能。关于Haskell的独特之处在于,如果您想以纯粹的功能性方式进行编程,您必须学会不同的思考方式。利用懒惰来加快速度:
{-# LANGUAGE ScopedTypeVariables #-}
import Control.Applicative ( pure, (<$>) )
import Data.List ( nub )
import Data.Monoid ( (<>) )
isPrime :: (Integral i) => i -> Bool
isPrime n = isPrime_ n primes
where isPrime_ n (p:ps)
| p * p > n = True
| n `mod` p == 0 = False
| otherwise = isPrime_ n ps
primes :: (Integral i) => [i]
primes = 2 : filter isPrime [3,5..]
primeFactors :: (Integral i) => i -> [i]
primeFactors n = factors n primes
where factors n (x:xs)
| x * x > n = [n]
| n `mod` x == 0 = x : factors (n `div` x) (x:xs)
| otherwise = factors n xs
primacity :: (Integral i) => i -> Int
primacity = length . nub . primeFactors
user :: IO Int
user = do
xs <- getLine
let a :: Int = read . takeWhile (/=' ') . dropN 0 $ xs
let b :: Int = read . takeWhile (/=' ') . dropN 1 $ xs
let k :: Int = read . takeWhile (/=' ') . dropN 2 $ xs
let n = length . filter (== k) . fmap primacity $ [a..b]
pure n
where
dropN 0 = id
dropN n = dropN (pred n) . drop 1 . dropWhile (/= ' ')
printNTimes :: Int -> Int -> IO ()
printNTimes 0 _ = pure ()
printNTimes n total = do
ans <- user
putStr $ "Case #" <> show (total - n + 1) <> ": "
putStrLn $ show ans
printNTimes (pred n) total
main :: IO ()
main = do
n :: Int <- read <$> getLine
printNTimes n n
这基本上是与懒惰相混合的相互递归。可能需要一段时间来理解它,但我可以保证它很快。
答案 1 :(得分:6)
是的,当然。您有效地使用了两种不同的算法。您的Haskell zipWith ($) (incEvery n) xs
必须处理列表中的每个条目,而您的Perl for my $j (2 .. ($max / $i)) { $f[$i * $j] ++; }
只需要处理它实际递增的条目,这是{{1}的因子} 快点。这是可变数组有用的问题的典型示例:例如,在Haskell中,您可以使用$i
。