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

后端 未结 23 1278
再見小時候
再見小時候 2020-12-04 23:58

More specifically, how do I generate a new list of every Nth element from an existing infinite list?

E.g. if the list is [5, 3, 0, 1, 8, 0, 3, 4, 0, 93, 211, 0

相关标签:
23条回答
  • 2020-12-05 00:30

    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. Note that the index starts from one here.

    0 讨论(0)
  • 2020-12-05 00:31

    All the solutions using zip and so on do tons of unnecessary allocations. 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; it fuses the filter-zip composition into a foldr2. The allocation of the list [1..] remains. Now you might not think this is so bad, but stream fusion, which is GHC's current technology, is a somewhat fragile optimization. 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. The only values that are allocated are exactly those cons cells (with :) that must be part of the final answer. Added to that, the use of drop makes the solution more than just easy to read: it is crystal clear and obviously correct.

    0 讨论(0)
  • 2020-12-05 00:32

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

    I was going to feel proud of myself, until I saw that Greg got about the same answer and before me

    0 讨论(0)
  • 2020-12-05 00:36

    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.

    0 讨论(0)
  • 2020-12-05 00:37

    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 Ints if ever I need to.

    0 讨论(0)
  • 2020-12-05 00:37

    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.)

    0 讨论(0)
提交回复
热议问题