How to define a rotates function

后端 未结 8 1949
佛祖请我去吃肉
佛祖请我去吃肉 2021-01-11 14:47

How to define a rotates function that generates all rotations of the given list?

For example: rotates [1,2,3,4] =[[1,2,3,4],[2,3,4,1],[3,4,1,2],[4,1,2,3]]

相关标签:
8条回答
  • 2021-01-11 15:06

    The following

    shift :: [a] -> Int -> [a]
    shift l n = drop n l  ++ take n l
    
    allRotations :: [a] -> [[a]]
    allRotations l = [ shift l i | i <- [0 .. (length l) -1]]
    

    yields

    > ghci
    Prelude> :l test.hs
    [1 of 1] Compiling Main             ( test.hs, interpreted )
    Ok, modules loaded: Main.
    *Main> allRotations [1,2,3,4]
    [[1,2,3,4],[2,3,4,1],[3,4,1,2],[4,1,2,3]]
    

    which is as you expect.

    I think this is fairly readable, although not particularly efficient (no memoisation of previous shifts occurs).


    If you care about efficiency, then

    shift :: [a] -> [a]
    shift [] = []
    shift (x:xs) = xs ++ [x]
    
    allRotations :: [a] -> [[a]]
    allRotations l = take (length l) (iterate shift l)
    

    will allow you to reuse the results of previous shifts, and avoid recomputing them.

    Note that iterate returns an infinite list, and due to lazy evaluation, we only ever evaluate it up to length l into the list.


    Note that in the first part, I've extended your shift function to ask how much to shift, and I've then a list comprehension for allRotations.

    0 讨论(0)
  • 2021-01-11 15:17

    I would prefer the following solutions, using the built-in functions cycle and tails:

    rotations xs = take len $ map (take len) $ tails $ cycle xs where
        len = length xs 
    

    For your example [1,2,3,4] the function cycle produces an infinite list [1,2,3,4,1,2,3,4,1,2...]. The function tails generates all possible tails from a given list, here [[1,2,3,4,1,2...],[2,3,4,1,2,3...],[3,4,1,2,3,4...],...]. Now all we need to do is cutting down the "tails"-lists to length 4, and cutting the overall list to length 4, which is done using take. The alias len was introduced to avoid to recalculate length xs several times.

    0 讨论(0)
  • 2021-01-11 15:17

    I think it will be something like this (I don't have ghc right now, so I couldn't try it)

    shift (x:xs) = xs ++ [x]
    
    rotateHelper xs 0 = []
    rotateHelper xs n = xs : (rotateHelper (shift xs) (n - 1))
    
    rotate xs = rotateHelper xs  (length xs)
    
    0 讨论(0)
  • 2021-01-11 15:19
    shift (x:xs)  =  xs ++ [x]
    rotates xs    =  take (length xs) $ iterate shift xs
    

    iterate f x returns the stream ("infinite list") [x, f x, f (f x), ...]. There are n rotations of an n-element list, so we take the first n of them.

    0 讨论(0)
  • 2021-01-11 15:21

    The answers given so far work fine for finite lists, but will eventually error out when given an infinite list. (They all call length on the list.)

    shift :: [a] -> [a]
    shift xs = drop 1 xs ++ take 1 xs
    
    rotations :: [a] -> [[a]]
    rotations xs = zipWith const (iterate shift xs) xs
    

    My solution uses zipWith const instead. zipWith const foos bars might appear at first glance to be identical to foos (recall that const x y = x). But the list returned from zipWith terminates when either of the input lists terminates.

    So when xs is finite, the returned list is the same length as xs, as we want; and when xs is infinite, the returned list will not be truncated, so will be infinite, again as we want.

    (In your particular application it may not make sense to try to rotate an infinite list. On the other hand, it might. I submit this answer for completeness only.)

    0 讨论(0)
  • 2021-01-11 15:22

    Another way to calculate all rotations of a list is to use the predefined functions tails and inits. The function tails yields a list of all final segments of a list while inits yields a list of all initial segments. For example,

    tails [1,2,3] = [[1,2,3], [2,3], [3],   []]
    
    inits [1,2,3] = [[],      [1],   [1,2], [1,2,3]]
    

    That is, if we concatenate these lists pointwise as indicated by the indentation we get all rotations. We only get the original list twice, namely, once by appending the empty initial segment at the end of original list and once by appending the empty final segment to the front of the original list. Therefore, we use the function init to drop the last element of the result of applying zipWith to the tails and inits of a list. The function zipWith applies its first argument pointwise to the provided lists.

    allRotations :: [a] -> [[a]]
    allRotations l = init (zipWith (++) (tails l) (inits l))
    

    This solution has an advantage over the other solutions as it does not use length. The function length is quite strict in the sense that it does not yield a result before it has evaluated the list structure of its argument completely. For example, if we evaluate the application

    allRotations [1..]
    

    that is, we calculate all rotations of the infinite list of natural numbers, ghci happily starts printing the infinite list as first result. In contrast, an implementation that is based on length like suggested here does not terminate as it calculates the length of the infinite list.

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