Better way to write this function: list to matrix (= list of lists)

Is there a better way to write this function (i.e. in one line via folds)?

--              list -> rowWidth -> list of lists
listToMatrix :: [a] -> Int -> [[a]]
listToMatrix [] _ = []
listToMatrix xs b = [(take b xs)] ++ (listToMatrix (drop b xs) b)

4 answers

  • answered 2017-06-17 18:09 jberryman

    No, although I wish chunksOf was in the standard library. You can get that from here if you want: https://hackage.haskell.org/package/split-0.2.3.2/docs/Data-List-Split.html

    Note a better definition would be:

    listToMatrix xs b = (take b xs) : (listToMatrix (drop b xs) b)
    

    although this might compile to the same core. You could also use splitAt, though again this is likely to perform the same.

    listToMatrix xs b = let (xs,xss) = splitAt b xs in xs : listToMatrix xss
    

  • answered 2017-06-17 18:09 baxbaxwalanuksiwe

    Yes, there is a better way to write this function. But I don't think that making it one line is going to improve anything.

    Use the prepending operator (:) instead of list concatenation (++) for single-element prepending

    The expression [(take b xs)] ++ (listToMatrix (drop b xs) b) is inefficient. I don't mean inefficient in terms of performances, because the compiler probably optimizes that, but, here, you are constructing a list, and then calling a function on it ((++)) which is going to deconstruct it by pattern matching. You could instead build your list directly using the (:) data constructor, which allows you to prepend a single element to your list. The expression becomes take b xs : listToMatrix (drop b xs) b

    Use splitAt to avoid running through the list twice

    import Data.List (splitAt)
    
    listToMatrix :: [a] -> Int -> [[a]]
    listToMatrix xs b = row : listToMatrix remaining b
        where (row, remaining) = splitAt b xs
    

    Ensure the correctness of your data by using Maybe

    listToMatrix :: [a] -> Int -> Maybe [[a]]
    listToMatrix xs b
        | length xs `mod` b /= 0 = Nothing
        | null xs   = Just []
        | otherwise = Just $ row : listToMatrix remaining b
        where (row, remaining) = splitAt b xs
    

    You can even avoid checking every time if you have the right number of elements by defining a helper function:

    listToMatrix :: [a] -> Int -> Maybe [[a]]
    listToMatrix xs b
        | length xs `mod` b /= 0 = Nothing
        | otherwise = Just (go xs)
        where go [] = []
              go xs = row : go remaining
                  where (row, remaining) = splitAt b xs
    

    Ensure the correctness of your data by using safe types

    A matrix has te same number of elements in each row, whereas nested lists don't allow to ensure that kind of conditions. To be certain that every row has the same number of elements, you can either use a library such as matrix or hmatrix

  • answered 2017-06-17 18:09 Daniel Sanchez

    You can use this, the idea is to take all the chunks of the main list, it is a different approach but basicly do the same:

    Prelude> let list2Matrix n xs = map (\(x ,y)-> (take n) $ (drop (n*x)) y) $ zip [0..] $ replicate (div (length xs)  n) xs
    Prelude> list2Matrix 10 [1..100]
    [[1,2,3,4,5,6,7,8,9,10],[11,12,13,14,15,16,17,18,19,20],[21,22,23,24,25,26,27,28,29,30],[31,32,33,34,35,36,37,38,39,40],[41,42,43,44,45,46,47,48,49,50],[51,52,53,54,55,56,57,58,59,60],[61,62,63,64,65,66,67,68,69,70],[71,72,73,74,75,76,77,78,79,80],[81,82,83,84,85,86,87,88,89,90],[91,92,93,94,95,96,97,98,99,100]]
    

  • answered 2017-06-17 18:09 Redu

    Actually this is a nice case for unfolding. Data.List method unfoldr, unlike folding a list, creates a list to a from a seed value by applying a function to it up until this function returns Nothing. Until we reach the terminating condition that will return Nothing the function returns Just (a,b) where a is the current generated item of the list and b is the next value of the seed. In this particular case our seed value is the given list.

    import Data.List
    chunk :: Int -> [a] -> [[a]]
    chunk n = unfoldr (\xs -> if null xs then Nothing else Just (splitAt n xs))
    
    *Main> chunk 3 [1,2,3,4,5,6,7,8,9]
    [[1,2,3],[4,5,6],[7,8,9]]
    *Main> chunk 3 [1,2,3,4,5,6,7,8,9,10]
    [[1,2,3],[4,5,6],[7,8,9],[10]]