tags:

views:

202

answers:

2

Hi guys, I'm struggling with Haskell programming.

I've got the list below and I want to make it stack on each other, so it'd be like 3 x 4 pixel image. eg:

pixel image

and how can I change the value of the first row or second ... eg: say like I want to make it darker or whiter (0 represents black and 255 represents white)

type Pixel = Int
type Row = [Pixel]
type PixelImage = [Row]
print :: PixelImage 
print = [[208,152,240,29],[0,112,255,59],[76,185,0,152]]

The code I've got here does not stack the list and I don't know how to stack it.

Please help, I'm really struggling with this.

Thanks in advance!

+7  A: 

Use a name other than print for your image to avoid collisions with the Prelude's print.

type Pixel = Int
type Row = [Pixel]
type PixelImage = [Row]

img :: PixelImage 
img = [[208,152,240,29],[0,112,255,59],[76,185,0,152]]

This is an inefficient representation, but it will do for a learning exercise.

You could print a PixelImage with the rows stacked on top of one another with a few imports at the top of your source and an I/O action:

import Control.Monad
import Data.List
import Text.Printf

printImage :: PixelImage -> IO ()
printImage img =
  forM_ img $ putStrLn . intercalate "  " . map (printf "%3d")

That may look intimidating, but everything there is familiar. The word order is just a little funny. For each Row in the PixelImage (forM_, a lot like a for loop in other languages) we print (with putStrLn) the list of Pixel values separated by two spaces (thanks to intercalate) and left-padded with spaces to make uniform 3-character fields (printf).

With the image from your question, we get

ghci> printImage img
208  152  240   29
  0  112  255   59
 76  185    0  152

Haskell lists are immutable: you cannot modify one in-place or destructively. Instead, think of it in terms of making a different list that's identical to the original except for the specified row.

modifyRow :: PixelImage -> (Row -> Row) -> Int -> PixelImage
modifyRow img f i = map go (zip [0..] img)
  where go (j,r) | i == j    = f r
                 | otherwise = r

This gives your function a chance to fire for each Row in the PixelImage. Say you want to zero out a particular row:

ghci> printImage $ modifyRow img (map $ const 0) 0
  0    0    0    0
  0  112  255   59
 76  185    0  152

Reversing a row is

ghci> printImage $ modifyRow img reverse 0
 29  240  152  208
  0  112  255   59
 76  185    0  152

In another language, you might write img[2] = [1,2,3,4], but in Haskell it's

ghci> modifyRow img (const [1..4]) 2
[[208,152,240,29],[0,112,255,59],[1,2,3,4]]

That usage isn't terribly evocative, so we can defined setRow in terms of modifyRow, a common technique in functional programming.

setRow :: PixelImage -> Row -> Int -> PixelImage
setRow img r i = modifyRow img (const r) i

Nicer:

ghci> printImage $ setRow img [4,3,2,1] 1
208  152  240   29
  4    3    2    1
 76  185    0  152

Maybe you want to scale the pixel values instead.

scaleRow :: (RealFrac a) => PixelImage -> a -> Int -> PixelImage
scaleRow img x i = modifyRow img f i
  where f = let clamp z | z < 0     = 0
                        | z > 255   = 255
                        | otherwise = truncate z
              in map (clamp . (x *) . fromIntegral)

For example:

ghci> printImage $ scaleRow img 0.5 1
208  152  240   29
  0   56  127   29
 76  185    0  152

Adding scaleImage to apply a scaling factor to each Pixel in a PixelImage means a bit of refactoring to avoid repeating the same code in multiple places. We'd like to be able to use

scaleImage :: (RealFrac a) => a -> PixelImage -> PixelImage
scaleImage x = map $ scaleOneRow x

to get, say

ghci> printImage $ scaleImage 3 img 
255  255  255   87
  0  255  255  177
228  255    0  255

This means scaleOneRow should be

scaleOneRow :: (RealFrac a) => a -> Row -> Row
scaleOneRow x = map (clamp . (x *) . fromIntegral)

which promotes clamp to a toplevel function on Pixel values.

clamp :: (RealFrac a) => a -> Pixel
clamp z | z < 0     = 0
        | z > 255   = 255
        | otherwise = truncate z

This in turn simplifies scaleRow:

scaleRow :: (RealFrac a) => PixelImage -> a -> Int -> PixelImage
scaleRow img x i = modifyRow img (scaleOneRow x) i
Greg Bacon
@Gbacon: This help alot! Thank you so much!
James1
@Gbacon: sorry, how if i want to scale the whole image like from the first pixel of the first row to the last pixel of the last row? i'm very bad at this haskel. please...
James1
@james1 See updated answer.
Greg Bacon
@Gbacon: You are the best, thanks so much!
James1
@james1 You're welcome. I'm glad it helps!
Greg Bacon
A: 

Here's a simple list manipulation for you.

import Prelude

get :: Int -> [a] -> a
get 0 (x : xs) = x
get i (x : xs) = get (i - 1) xs
get i [] = error "get: i is not in list"

ghci

*Main> get 1 (get 2 ["what", "is", "up", "man?"])
'p'

To set stuff you can use

set :: Int -> a -> [a] -> [a]
set 0 t (x : xs) = (t : xs)
set i t (x : xs) = [x] ++ (set (i - 1) t xs)
set i _ [] = error "set: i is not in list"

with a command like

set 2 (set 1 't' $ get 2 ["what", "is", "up", "man?"]) ["what", "is", "up", "man?"]

I hope that helps.

BT
`get` is equal to `flip (!!)`, by the way.
yatima2975
@yatima - I know, but the guy didn't even seem to know simple list manipulations. Better in my opinion to to show somebody at that stage the code then just hand off a pre-built method.
BT