Search code examples
haskell

Haskell: Increment elements of a list by cumulative length of previous lists


Here is the list of lists: [[1,2,3],[1,2,3,4],[1,2,3]]

How can I increment each element of the second list by the length of the first list, and increment the third list by the length of the first list + second list? The first list should remain unchanged.

Intended output: [[1,2,3],[4,5,6,7],[8,9,10]]

Since the first list has length 3, the second list is generated by [1+3, 2+3, 3+3, 4+3].

Since the first list + second list combined have length 7, the third list is generated by [1+7, 2+7, 3+7].

Ideally it should work with any number of lists.

So far, I've had slight sucess using this:

scanl1 (\xs ys -> [y + length xs | y <- ys]) [[1,2,3],[1,2,3,4],[1,2,3]]

which outputs: [[1,2,3],[4,5,6,7],[5,6,7]]


Solution

  • There are many ways to solve this. A simple recursion is one approach:

    lst :: [[Int]]
    lst = [[1,2,3],[1,2,3,4],[1,2,3]]
    
    incrLength :: [[Int]] -> Int -> [[Int]] -> [[Int]]
    incrLength [] _ result = result
    incrLength (x:xs) amount result =
       incrLength xs (amount + length x) (result ++ [map (+amount) x])
    

    (Edit: it is more efficient to use (:) in this function. See @amalloy comment below. The result then has to be reversed.

    incrLength :: [[Int]] -> Int -> [[Int]] -> [[Int]]
    incrLength [] _ result = reverse result
    incrLength (x:xs) amount result =
       incrLength xs (amount + length x) (map (+amount) x : result)
    

    End Edit)

    Another approach is to use scanl. We use length to get the length of the inner lists, then accumulate using scanl.

     map length lst                      -- [3,4,3]
     scanl (+) 0 $ map length lst        -- [0,3,7,10]
     init $ scanl (+) 0 $ map length lst -- [0,3,7]
    

    Then we zip the lst and the accumulated value together, and map one over the other.

     incrLength' :: [[Int]] -> [[Int]]
     incrLength' lst = 
          [map (+ snd y) (fst y) | y <- zip lst addlst]
        where 
           addlst =init $scanl (+) 0 $ map length lst 
    
    main = do
        print $ incrLength lst 0 []    -- [[1,2,3],[4,5,6,7],[8,9,10]]