简体   繁体   English

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

[英]How to get every Nth element of an infinite list in Haskell?

More specifically, how do I generate a new list of every Nth element from an existing infinite list?更具体地说,如何从现有的无限列表中生成每个第 N 个元素的新列表?

Eg if the list is [5, 3, 0, 1, 8, 0, 3, 4, 0, 93, 211, 0 ...] then getting every 3rd element would result in this list [0,0,0,0,0 ...]例如,如果列表是[5, 3, 0, 1, 8, 0, 3, 4, 0, 93, 211, 0 ...]然后获取每个第三个元素将导致此列表[0,0,0,0,0 ...]

My version using drop:我使用 drop 的版本:

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

Edit: this also works for finite lists.编辑:这也适用于有限列表。 For infinite lists only, Charles Stewart's solution is slightly shorter.仅对于无限列表,Charles Stewart 的解决方案略短。

All the solutions using zip and so on do tons of unnecessary allocations.所有使用zip等的解决方案都会进行大量不必要的分配。 As a functional programmer, you want to get into the habit of not allocating unless you really have to.作为一个函数式程序员,你应该养成不分配的习惯,除非你真的需要。 Allocation is expensive, and compared to allocation, everything else is free.分配是昂贵的,与分配相比,其他一切都是免费的。 And allocation doesn't just show up in the "hot spots" you would find with a profiler;并且分配不仅仅出现在您可以使用分析器找到的“热点”中; if you don't pay attention to allocation, it kills you everywhere .如果你不注意分配,它就会到处杀了你。

Now I agree with the commentators that readability is the most important thing .现在我同意评论员的观点,即可读性是最重要的 Nobody wants to get wrong answers fast.没有人想快速得到错误的答案。 But it happens very often in functional programming that there are multiple solutions, all about equally readable, some of which allocate and some of which do not.但是在函数式编程中经常发生有多种解决方案,所有解决方案都具有同等可读性,其中一些分配,一些不分配。 It's really important to build a habit of looking for those readable, non-allocating solutions.养成寻找那些可读的、非分配的解决方案的习惯非常重要。

You might think that the optimizer would get rid of allocations, but you'd only be half right.您可能认为优化器会消除分配,但您只猜对了一半。 GHC, the world's best optimizing compiler for Haskell, does manage to avoid allocating a pair per element; GHC 是世界上最好的 Haskell 优化编译器,它确实设法避免为每个元素分配一对; it fuses the filter - zip composition into a foldr2 .它将filter - zip组合融合到一个foldr2 The allocation of the list [1..] remains.列表[1..]的分配仍然存在。 Now you might not think this is so bad, but stream fusion, which is GHC's current technology, is a somewhat fragile optimization.现在您可能认为这并没有那么糟糕,但是 GHC 当前的技术流融合是一种有点脆弱的优化。 It's hard even for experts to predict exactly when it's going to work, just by looking at the code.即使是专家也很难仅仅通过查看代码来准确预测它何时起作用。 The more general point is that when it comes to a critical property like allocation, you don't want to rely on a fancy optimizer whose results you can't predict .更普遍的一点是,当涉及到像分配这样的关键属性时,你不想依赖一个你无法预测结果的花哨的优化器 As long as you can write your code in an equally readable way, you're much better off never introducing those allocations.只要您可以以同样可读的方式编写代码,最好永远不要引入这些分配。

For these reason I find Nefrubyr's solution using drop to be by far the most compelling.由于这些原因,我发现 Nefrubyr 使用drop的解决方案是迄今为止最引人注目的。 The only values that are allocated are exactly those cons cells (with : ) that must be part of the final answer.分配的唯一值正是必须作为最终答案一部分的那些 cons 单元格(带有: )。 Added to that, the use of drop makes the solution more than just easy to read: it is crystal clear and obviously correct.除此之外,使用drop使解决方案不仅易于阅读:它非常清晰,显然是正确的。

I don't have anything to test this with at work, but something like:我在工作中没有任何可以测试的东西,但类似于:

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

should work even on infinite lists.即使在无限列表上也应该工作。

(Edit: tested and corrected.) (编辑:测试和更正。)

Starting at the first element:从第一个元素开始:

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

Starting at the nth element:从第 n 个元素开始:

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

The compiler or interpreter will compute the step size (subtract 1 since it's zero based):编译器或解释器将计算步长(减 1,因为它是从零开始的):

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

The Haskell 98 Report: Arithmetic Sequences Haskell 98 报告:算术序列

MHarris's answer is great. MHarris 的回答很棒。 But I like to avoid using \\ , so here's my golf:但我喜欢避免使用\\ ,所以这是我的高尔夫:

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

摆脱mod替代解决方案:

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

I nuked my version of Haskell the other day, so untested, but the following seems a little simpler than the others (leverages pattern matching and drop to avoid zip and mod):前几天我对我的 Haskell 版本进行了核试验,因此未经测试,但以下内容似乎比其他内容简单一些(利用模式匹配和删除来避免 zip 和 mod):

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

Another way of doing it:另一种方法:

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

Yet Another:还有一个:

import Data.List

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

Use a view pattern!使用视图模式!

{-# LANGUAGE ViewPatterns #-}

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

Ugly version of Nefrubyr's answer preserved so comments make sense.保留了 Nefrubyr 答案的丑陋版本,因此评论是有道理的。

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

Explicit recursion is evil!显式递归是邪恶的! Use a library construct like map, filter, fold, scan, reproduce, iterate etc instead.改用诸如地图、过滤器、折叠、扫描、再现、迭代等的库结构。 Unless it makes the code drastically easier to read even to those au fait with the libraries, then it's just making your code less modular, more verbose and harder to reason about.除非它让那些熟悉库的人更容易阅读代码,否则它只会让你的代码不那么模块化,更冗长,更难以推理。

Seriously, the explicitly recursive versions need to be voted down to negative for a task as simple as this.说真的,对于像这样简单的任务,明确的递归版本需要被否决。 Now I guess I should say something constructive to balance my carping.现在我想我应该说一些建设性的东西来平衡我的吹毛求疵。

I prefer to use a map:我更喜欢使用地图:

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

rather than the ja's list comprehension so the reader doesn't have to worry about what i is.而不是 ja 的列表理解,因此读者不必担心 i 是什么。 But either way it's O(n^2) when it could be O(n), so maybe this is better:但无论哪种方式,当它可能是 O(n) 时它是 O(n^2),所以也许这更好:

every n = map (!!(n-1)) . iterate (drop n)

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

我会为自己感到骄傲,直到我看到 Greg 得到了大致相同的答案并且在我之前

A lot of answers here already use Data.List.unfoldr, but I wanted to bring up an interesting way of writing the somewhat annoying unfold that may be helpful in other contexts:这里的很多答案已经使用了 Data.List.unfoldr,但我想提出一种有趣的方式来编写可能在其他情况下有用的有点烦人的展开:

{-# LANGUAGE TupleSections #-}
import Data.List (unfoldr)
import Data.Maybe (listToMaybe)

every n = unfoldr f . drop (n - 1)
    where f xs = (,drop n xs) <$> listToMaybe xs

When the list is null, listToMaybe returns a Nothing , and fmap similarly will then be Nothing .当列表为空时, listToMaybe返回一个Nothingfmap同样将返回Nothing However, if a Just is produced, then the proper tuple is constructed by turning the head of the list into a tuple of the head and the rest of the values to use in the next iteration.但是,如果产生Just ,那么通过将列表的头部转换为头部的元组以及将在下一次迭代中使用的其余值来构造正确的元组。

A slightly silly version with foldr :一个带有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)

(This was in response to a comment asking for a solution without drop) (这是对要求不滴解决方案的评论的回应)

I couldn't see this solution, so:我看不到这个解决方案,所以:

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

works for finite and infinite list:适用于有限和无限列表:

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

This seems a slightly better use of unfold:这似乎更好地使用展开:

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

Or even better, use Data.List.Spit:或者甚至更好,使用 Data.List.Spit:

everyNth n = (map head) . chunksOf n

Old question but I'll post what I came up with:老问题,但我会发布我的想法:

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

I put the list argument before the Int so I can curry the function and a list then map that over a list of Int s if ever I need to.我将 list 参数放在Int之前,这样我就可以对函数和列表进行柯里化,然后在需要时将其映射到Int列表上。

It's more elegant to solve a related problem first: Keep every element whose index is divisible by n.先解决一个相关问题更优雅:保留每个索引可被n整除的元素。

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

And then to solve the example, use然后要解决该示例,请使用

everyNthFirst n = everyNth n . drop (n-1)

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

Data.List.HT from utility-ht has sieve :: Int -> [a] -> [a] .来自utility-ht Data.List.HTsieve :: Int -> [a] -> [a]

See documentation and source :请参阅文档来源

{-| 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

And a very functional one without apparent conditionals:一个非常实用的没有明显条件的:

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

(Note, this one takes every element whose index is a multiple of n. Which is slightly different from the original question, but easy to convert.) (请注意,此问题采用索引为 n 倍数的每个元素。这与原始问题略有不同,但易于转换。)

We can use a list comprehension:我们可以使用列表理解:

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

From all value-index pairs (x, i), take x if i is divisible by n.从所有值索引对 (x, i) 中,如果 i 可被 n 整除,则取 x。 Note that the index starts from one here.请注意,这里的索引从 1 开始。

My solution is:我的解决办法是:

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

It do not use zip but I can't tell about it's performances and memory profile.它不使用 zip,但我不知道它的性能和内存配置文件。

An uglier, and more limited version of the accepted answer已接受答案的更丑陋且更有限的版本

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

For "line golfing" it can be written like this:对于“线高尔夫”,它可以这样写:

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

(It's more limited because it has an unnecessary Eq a constraint.) (它更受限制,因为它有一个不必要的Eq a约束。)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM