如何在Haskell中获取无限列表的每个第N个元素?

时间:2010-01-08 10:32:05

标签: list haskell functional-programming

更具体地说,如何从现有的无限列表中生成每个第N个元素的新列表?

E.g。如果列表为[5, 3, 0, 1, 8, 0, 3, 4, 0, 93, 211, 0 ...],那么获取每个第3个元素将导致此列表[0,0,0,0,0 ...]

22 个答案:

答案 0 :(得分:59)

我的版本使用drop:

every n xs = case drop (n-1) xs of
              (y:ys) -> y : every n ys
              [] -> []

编辑:这也适用于有限列表。仅限无限列表,Charles Stewart的解决方案略短。

答案 1 :(得分:35)

使用zip等所有解决方案都会进行大量不必要的分配。作为一名功能性程序员,除非你真的需要,否则你要养成不分配的习惯。分配是昂贵的,与分配相比,其他一切都是免费的。并且分配不仅仅出现在您使用分析器找到的“热点”中;如果你不注意分配,它会杀死到处

现在我赞同评论员认为可读性是最重要的事情。没有人想快速得到错误的答案。但它经常发生在函数式编程中,有多个解决方案,所有解决方案都具有相同的可读性,其中一些解决方案是分配的,而另一些则不是。建立寻找那些可读,非分配解决方案的习惯非常重要。

你可能认为优化器会摆脱分配,但你只有一半是正确的。 GHC是世界上最好的Haskell优化编译器,它确实设法避免为每个元素分配一对;它将filter - zip合成融入foldr2。列表[1..]的分配仍然存在。现在你可能不认为这是如此糟糕,但流融合,这是GHC目前的技术,是一个有点脆弱的优化。即使是专家也很难通过查看代码来准确预测它何时起作用。更一般的一点是当谈到像分配这样的关键属性时,你不想依赖一个你无法预测的结果的花哨的优化器。只要您能以同样可读的方式编写代码,最好不要再引入这些分配。

由于这些原因,我发现使用drop的Nefrubyr解决方案是迄今为止最引人注目的解决方案。分配的唯一值正是那些必须成为最终答案一部分的缺点单元格(:)。除此之外,drop的使用使解决方案不仅仅是易于理解:它非常清晰,显然是正确的。

答案 2 :(得分:16)

我没有任何东西可以在工作中测试这个,但是像:

extractEvery m = map snd . filter (\(x,y) -> (mod x m) == 0) . zip [1..]

甚至可以在无限列表上工作。

(编辑:测试并更正。)

答案 3 :(得分:13)

从第一个元素开始:

everyf n [] = []
everyf n as  = head as : everyf n (drop n as)

从第n个元素开始:

every n = everyf n . drop (n-1)

答案 4 :(得分:11)

MHarris的答案很棒。但我喜欢避免使用\,所以这是我的高尔夫:

extractEvery n
  = map snd . filter fst
  . zip (cycle (replicate (n-1) False ++ [True]))

答案 5 :(得分:11)

编译器或解释器将计算步长(减去1,因为它基于零):

f l n = [l !! i | i <- [n-1,n-1+n..]]

The Haskell 98 Report: Arithmetic Sequences

答案 6 :(得分:10)

摆脱mod的替代解决方案:

extractEvery n = map snd . filter ((== n) . fst) . zip (cycle [1..n])

答案 7 :(得分:10)

我前几天修改了我的Haskell版本,所以未经测试,但以下看起来比其他版本更简单(利用模式匹配和删除来避免zip和mod):

everynth :: Int -> [a] -> [a]
everynth n xs = y : everynth n ys
         where y : ys = drop (n-1) xs

答案 8 :(得分:4)

另一种方法:

takeEveryM m lst = [n | (i,n) <- zip [1..] lst, i `mod` m == 0]

又一个:

import Data.List

takeEveryMth m = 
  (unfoldr g)  . dr
     where 
       dr = drop (m-1)
       g (x:xs) = Just (x, dr xs)
       g []     = Nothing

答案 9 :(得分:2)

使用视图模式!

{-# LANGUAGE ViewPatterns #-}

everynth n (drop (n-1) -> l)
  | null l = []
  | otherwise = head l : everynth n (tail l)

保留了Nefrubyr的丑陋版本的答案,所以评论是有道理的。

everynth :: Int -> [a] -> [a]
everynth n l = case splitAt (n-1) l of
                 (_, (x:xs)) -> x : everynth n xs
                 _           -> []

答案 10 :(得分:2)

明确的递归是邪恶的!使用库构造,如地图,过滤,折叠,扫描,再现,迭代等。除非它使代码更容易阅读,即使对于那些使用库的人来说,它只是使你的代码更少模块化,更冗长,更难以推理。

严重的是,明确的递归版本需要被简化为负面的任务,就像这样简单。现在我想我应该说一些有建设性的东西来平衡我的烦恼。

我更喜欢使用地图:

每n xs = map(xs !!)[n-1,n-1 + n ..]

而不是ja的列表理解,所以读者不必担心我是什么。但无论哪种方式,当它可能是O(n)时它是O(n ^ 2),所以也许这样更好:

每个n =地图(!!(n-1))。迭代(drop n)

答案 11 :(得分:1)

extractEvery n l = map head (iterate (drop n) (drop (n-1) l))

我会为自己感到骄傲,直到我看到格雷格得到了同样的答案并且在我之前

答案 12 :(得分:1)

(这是对a comment要求无下落的解决方案的回应)

我看不到这个解决方案,所以:

every _ []     = []
every n (x:xs) = every' n (n-1) (x:xs)
                 where every' n c []     = []
                       every' n 0 (x:xs) = x : every' n (n-1) xs
                       every' n c (x:xs) = every' n (c-1) xs

适用于有限和无限列表:

take 15 (every 3 [1..])
-- [3,6,9,12,15,18,21,24,27,30,33,36,39,42,45]

答案 13 :(得分:1)

带有foldr的愚蠢版本:

everyNth n l = foldr cons nil l (n-1) where
  nil _ = []
  cons x rest 0 = x : rest n
  cons x rest n = rest (n-1)

答案 14 :(得分:0)

这似乎稍微好一点展开:

everyNth :: Int -> [a] -> [a]
everyNth n = unfoldr g
  where
    g [] = Nothing
    g xs = let (ys, zs) = splitAt n xs in Just (head ys, zs)

甚至更好,使用Data.List.Spit:

everyNth n = (map head) . chunksOf n

答案 15 :(得分:0)

旧问题,但我会发布我想出的内容:

everyNth :: [a] -> Int -> [a]
everyNth xs n = [snd x | x <- (zip [1..] xs), fst x `mod` n == 0]

我把list参数放在Int之前,这样我就可以把这个函数和一个列表放在Int的列表上,如果需要的话。

答案 16 :(得分:0)

首先解决一个相关的问题会更优雅:保留索引可被n整除的每个元素。

everyNth n [] = []
everyNth n (x:xs) = x : (everyNth n . drop (n-1)) xs

然后使用

解决该示例
everyNthFirst n = everyNth n . drop (n-1)

everyNthFirst 3 [5, 3, 0, 1, 8, 0, 3, 4, 0, 93, 211, 0 ...]给出[0, 0, 0, ...]

答案 17 :(得分:0)

来自Data.List.HT

utility-ht具有sieve :: Int -> [a] -> [a]

请参见documentationsource

{-| keep every k-th value from the list -}
sieve, sieve', sieve'', sieve''' :: Int -> [a] -> [a]
sieve k =
   unfoldr (\xs -> toMaybe (not (null xs)) (head xs, drop k xs))

sieve' k = map head . sliceVertical k

sieve'' k x = map (x!!) [0,k..(length x-1)]

sieve''' k = map head . takeWhile (not . null) . iterate (drop k)

propSieve :: Eq a => Int -> [a] -> Bool
propSieve n x =
   sieve n x == sieve'  n x   &&
   sieve n x == sieve'' n x

答案 18 :(得分:0)

一个丑陋的,接受答案的更有限版本

every :: Eq a => Int -> [a] -> [a]
every n xs = if rest == [] 
                then [] 
                else head rest : every n (tail rest)
    where rest = drop (n-1) xs

对于“打高尔夫球”,可以这样写:

every n xs = if rest == [] then [] else head rest : every n (tail rest) 
    where rest = drop (n-1) xs

(它更有限,因为它有一个不必要的Eq a约束。)

答案 19 :(得分:0)

还有一个非常实用的功能,没有明显的条件:

everyNth :: Int -> [a] -> [a]
everyNth 0 = take 1
everyNth n = foldr ($) [] . zipWith ($) operations where
  operations = cycle $ (:) : replicate (n-1) (const id)

(请注意,此元素采用索引为n的倍数的每个元素。该元素与原始问题略有不同,但易于转换。)

答案 20 :(得分:0)

我们可以使用列表理解:

takeEvery :: Int -> [a] -> [a]
takeEvery n xs = [x | (x, i) <- zip xs [1..], i `mod` n == 0]

在所有值索引对(x,i)中,如果i被n整除,则取x。请注意,索引从此处开始。

答案 21 :(得分:-1)

我的解决方案是:

every :: Int -> [a] -> [[a]]
every _ [] = []
every n list = take n list : (every n $ drop n list)

它不使用zip,但我无法分辨它的性能和内存配置文件。