I have a rectangular matrix with cases containing B or N. An example of matrix:
g0 = [[B,B,B,B,B,B,N],
[B,B,N,B,N,B,B],
[N,B,N,N,N,N,N],
[B,B,B,N,N,B,N],
[N,N,N,B,B,B,B],
[B,B,B,N,N,B,N]]
I have a type rectangle like [Int,Int,Int,Int] and a function that gets a smaller rectangular matrix from my matrix with this type. Here's the function but that's not the most important part:
getRectangle :: Rectangle -> Grille -> Grille -- cette fonction récupère la grille qui correspond au rectangle donné
getRectangle (i,j,l,c) g = transpose (getLigne (j,c,(nbLigne (transpose g0))) (transpose (getLigne (i,l,(nbLigne g0)) g0)))
--transpose get create a matrix with (n,m) = (lines,columns) in a matrix (m,n) and nbLigne return the number of lines (or columns when used with transpose) of a matrix.
getLigne :: (Int,Int,Int) -> Grille -> Grille
getLigne (i,l,0) g = []
getLigne (1,l,1) g = [head g]
getLigne (i,l,indice) [] = []
getLigne (i,l,indice) g
| indice == (i+l) = getLigne (i,l,(indice-1)) (init g) ++ [last g]
| indice == i = [last g]
| i < indice && indice < (i+l) = getLigne (i,l,(indice-1)) (init g) ++ [last g]
| otherwise = getLigne (i,l,(indice-1)) (init g)
Here's an example:
*Main> affiche (getRectangle (1,2,2,3) g0)
[B,B,B,B]
[B,N,B,N]
[B,N,N,N]
So, I have a tuple with (i,j,l,c). Knowing that 1<=i<i+l<=n and 1<=j<j+c<=m with n the number of lines of the matrix and m the number of columns.
To be clear, with a tuple (i,j,l,c), my function create a rectangle, from my matrix, formed with these cases: (i,j), (i+l,j), (i,j+c) and (i+l,j+c).
Now that I can create a single rectangle, I need to create all the possibles rectangles in any matrix. I don't have any clue on how I can do this since I feel like there is so many rectangles in a single matrix and cover all the cases seems very hard and long to me.
Maybe that I wasn't clear on some points, feel free to ask.
Salut :),
For combinations, I often work with the list monad.
Note that using the do notation like this is equivalent to working with list comprehensions
From a position you can deduce all the rectangles that can originate from a given point:
allRectsOriginatingFrom :: Point -> Grille -> [Rectangle]
allRectsOriginatingFrom (x, y) g
-- Si le point est dans ta grille...
| (x >= 1 && x <= width g) && (y >= 1 && y <= height g) = do
w <- [0 .. width g - x]
h <- [0 .. height g - y]
return (x, y, w, h)
-- Sinon y'a pas de rectangle possible
| otherwise = []
From there, your just have to map the function over all the possible positions on your grid:
allPointsOf :: Grille -> [Point]
allPointsOf g = do
x <- [1 .. width g]
y <- [1 .. height g]
return (x, y)
allRectsOf :: Grille -> [Rectangle]
allRectsOf g = do
pos <- allPointsOf g
allRectsOriginatingFrom pos g
And finally, mapping it with your getLigne function will get you every rectangle in your grid.
PS: Try to create datatypes instead of type aliases, it's better in my opinion (e.g. create a datatype like data Rectangle = Rectangle Int Int Int Int instead of type Rectangle = (Int, Int, Int, Int)).
Related
I'm trying to make what I think is called an Ulam spiral using Haskell.
It needs to go outwards in a clockwise rotation:
6 - 7 - 8 - 9
| |
5 0 - 1 10
| | |
4 - 3 - 2 11
|
..15- 14- 13- 12
For each step I'm trying to create coordinates, the function would be given a number and return spiral coordinates to the length of input number eg:
mkSpiral 9
> [(0,0),(1,0),(1,-1),(0,-1),(-1,-1),(-1,0),(-1,1),(0,1),(1,1)]
(-1, 1) - (0, 1) - (1, 1)
|
(-1, 0) (0, 0) - (1, 0)
| |
(-1,-1) - (0,-1) - (1,-1)
I've seen Looping in a spiral solution, but this goes counter-clockwise and it's inputs need to the size of the matrix.
I also found this code which does what I need but it seems to go counterclock-wise, stepping up rather than stepping right then clockwise :(
type Spiral = Int
type Coordinate = (Int, Int)
-- number of squares on each side of the spiral
sideSquares :: Spiral -> Int
sideSquares sp = (sp * 2) - 1
-- the coordinates for all squares in the given spiral
coordinatesForSpiral :: Spiral -> [Coordinate]
coordinatesForSpiral 1 = [(0, 0)]
coordinatesForSpiral sp = [(0, 0)] ++ right ++ top ++ left ++ bottom
where fixed = sp - 1
sides = sideSquares sp - 1
right = [(x, y) | x <- [fixed], y <- take sides [-1*(fixed-1)..]]
top = [(x, y) | x <- reverse (take sides [-1*fixed..]), y <- [fixed]]
left = [(x, y) | x <- [-1*fixed], y <- reverse(take sides [-1*fixed..])]
bottom = [(x, y) | x <- take sides [-1*fixed+1..], y <- [-1*fixed]]
-- an endless list of coordinates (the complete spiral)
mkSpiral :: Int -> [Coordinate]
mkSpiral x = take x endlessSpiral
endlessSpiral :: [Coordinate]
endlessSpiral = endlessSpiral' 1
endlessSpiral' start = coordinatesForSpiral start ++ endlessSpiral' (start + 1)
After much experimentation I can't seem to change the rotation or starting step direction, could someone point me in the right way or a solution that doesn't use list comprehension as I find them tricky to decode?
Let us first take a look at how the directions of a spiral are looking:
R D L L U U R R R D D D L L L L U U U U ....
We can split this in sequences like:
n times n+1 times
_^_ __^__
/ \ / \
R … R D … D L L … L U U … U
\_ _/ \__ __/
v v
n times n+1 times
We can repeat that, each time incrementing n by two, like:
data Dir = R | D | L | U
spiralSeq :: Int -> [Dir]
spiralSeq n = rn R ++ rn D ++ rn1 L ++ rn1 U
where rn = replicate n
rn1 = replicate (n + 1)
spiral :: [Dir]
spiral = concatMap spiralSeq [1, 3..]
Now we can use Dir here to calculate the next coordinate, like:
move :: (Int, Int) -> Dir -> (Int, Int)
move (x, y) = go
where go R = (x+1, y)
go D = (x, y-1)
go L = (x-1, y)
go U = (x, y+1)
We can use scanl :: (a -> b -> a) -> a -> [b] -> [a] to generate the points, like:
spiralPos :: [(Int, Int)]
spiralPos = scanl move (0,0) spiral
This will yield an infinite list of coordinates for the clockwise spiral. We can use take :: Int -> [a] -> [a] to take the first k items:
Prelude> take 9 spiralPos
[(0,0),(1,0),(1,-1),(0,-1),(-1,-1),(-1,0),(-1,1),(0,1),(1,1)]
The idea with the following solution is that instead of trying to generate the coordinates directly, we’ll look at the directions from one point to the next. If you do that, you’ll notice that starting from the first point, we go 1× right, 1× down, 2× left, 2× up, 3× right, 3× down, 4× left… These can then be seperated into the direction and the number of times repeated:
direction: > v < ^ > v < …
# reps: 1 1 2 2 3 3 4 …
And this actually gives us two really straightforward patterns! The directions just rotate > to v to < to ^ to >, while the # of reps goes up by 1 every 2 times. Once we’ve made two infinite lists with these patterns, they can be combined together to get an overall list of directions >v<<^^>>>vvv<<<<…, which can then be iterated over to get the coordinate values.
Now, I’ve always thought that just giving someone a bunch of code as the solution is not the best way to learn, so I would highly encourage you to try implementing the above idea yourself before looking at my solution below.
Welcome back (if you did try to implement it yourself). Now: onto my own solution. First I define a Stream data type for an infinite stream:
data Stream a = Stream a (Stream a) deriving (Show)
Strictly speaking, I don’t need streams for this; Haskell’s predefined lists are perfectly adequate for this task. But I happen to like streams, and they make some of the pattern matching a bit easier (because I don’t have to deal with the empty list).
Next, I define a type for directions, as well as a function specifying how they interact with points:
-- Note: I can’t use plain Left and Right
-- since they conflict with constructors
-- of the ‘Either’ data type
data Dir = LeftDir | RightDir | Up | Down deriving (Show)
type Point = (Int, Int)
move :: Dir -> Point -> Point
move LeftDir (x,y) = (x-1,y)
move RightDir (x,y) = (x+1, y)
move Up (x,y) = (x,y+1)
move Down (x,y) = (x,y-1)
Now I go on to the problem itself. I’ll define two streams — one for the directions, and one for the number of repetitions of each direction:
dirStream :: Stream Dir
dirStream = Stream RightDir $ Stream Down $ Stream LeftDir $ Stream Up dirVals
numRepsStream :: Stream Int
numRepsStream = go 1
where
go n = Stream n $ Stream n $ go (n+1)
At this point we’ll need a function for replicating each element of a stream a specific number of times:
replicateS :: Stream Int -> Stream a -> Stream a
replicateS (Stream n ns) (Stream a as) = conss (replicate n a) $ replicateS ns as
where
-- add more than one element to the beginning of a stream
conss :: [a] -> Stream a -> Stream a
conss [] s = s
conss (x:xs) s = Stream x $ appends xs s
This gives replicateS dirStream numRepsStream for the stream of directions. Now we just need a function to convert those directions to coordinates, and we’ve solved the problem:
integrate :: Stream Dir -> Stream Point
integrate = go (0,0)
where
go p (Stream d ds) = Stream p (go (move d p) ds)
spiral :: Stream Point
spiral = integrate $ replicateS numRepsStream dirStream
Unfortunately, it’s somewhat inconvenient to print an infinite stream, so the following function is useful for debugging and printing purposes:
takeS :: Int -> Stream a -> [a]
takeS 0 _ = []; takeS n (Stream x xs) = x : (takeS (n-1) xs)
Often times you want the performance of arrays over linked lists while having not conforming to the requirement of having rectangular arrays.
As an example consider an hexagonal grid, here shown with the 1-distance neighbors of cell (3, 3) in medium gray and the 2-distance neighbors in light gray.
Say we want an array that contains, for each cell, the indices of every 1- and 2-distance neighbor for that cell. One slight issue is that cells have a different amount of X-distance neighbors -- cells on the grid border will have fewer neighbors than cells closer to the grid center.
(We want an array of neighbor indices --- instead of a function from cell coordinates to neighbor indices --- for performance reasons.)
We can work around this problem by keeping track of how many neighbors each cell has. Say you have an array
neighbors2 of size R x C x N x 2, where R is the number of grid rows, C for columns, and N is the maximum number of 2-distance neighbors for any cell in the grid.
Then, by keeping an additional array n_neighbors2 of size R x C, we can keep track of which indices in neighbors2 are populated and which are just zero padding. For example, to retrieve the the 2-distance neighbors of cell (2, 5), we simply index into the array as such:
someNeigh = neighbors2[2, 5, 0..n_neighbors2[2, 5], ..]
someNeigh will be a n_neighbors2[2, 5] x 2 array (or view) of indicies, where someNeigh[0, 0] yields the row of the first neighbor, and someNeigh[0, 1] yields the column of the first neighbor and so forth.
Note that the elements at the positions
neighbors2[2, 5, n_neighbors2[2, 5]+1.., ..]
are irrelevant; this space is just padding to keep the matrix rectangular.
Provided we have a function for finding the d-distance neighbors for any cell:
import Data.Bits (shift)
rows, cols = (7, 7)
type Cell = (Int, Int)
generateNeighs :: Int -> Cell -> [Cell]
generateNeighs d cell1 = [ (row2, col2)
| row2 <- [0..rows-1]
, col2 <- [0..cols-1]
, hexDistance cell1 (row2, col2) == d]
hexDistance :: Cell -> Cell -> Int
hexDistance (r1, c1) (r2, c2) = shift (abs rd + abs (rd + cd) + abs cd) (-1)
where
rd = r1 - r2
cd = c1 - c2
How can we create the aforementioned arrays neighbors2 and n_neighbors2? Assume we know the maximum amount of 2-distance neighbors N beforehand. Then it is possible to modify generateNeighs to always return lists of the same size, as we can fill up remaining entries with (0, 0). That leaves, as I see it, two problems:
We need a function to populate neighbors2 which operates not every individual index but on a slice, in our case it should fill one cell at a time.
n_neighbors2 should be populated simultaneously as neighbors2
A solution is welcome with either repa or accelerate arrays.
Here's you picture skewed 30 degrees to the right:
As you can see your array is actually perfectly rectangular.
The indices of a neighborhood's periphery are easily found as six straight pieces around the chosen center cell, e.g. (imagine n == 2 is the distance of the periphery from the center (i,j) == (3,3) in the picture):
periphery n (i,j) =
-- 2 (3,3)
let
((i1,j1):ps1) = reverse . take (n+1) . iterate (\(i,j)->(i,j+1)) $ (i-n,j)
-- ( 1, 3)
((i2,j2):ps2) = reverse . take (n+1) . iterate (\(i,j)->(i+1,j)) $ (i1,j1)
-- ( 1, 5)
.....
ps6 = ....... $ (i5,j5)
in filter isValid (ps6 ++ ... ++ ps2 ++ ps1)
The whole neighborhood is simply
neighborhood n (i,j) = (i,j) : concat [ periphery k (i,j) | k <- [1..n] ]
For each cell/distance combination, simply generate the neighborhood indices on the fly and access your array in O(1) time for each index pair.
Writing out the answer from #WillNess in full, and incorporating the proposal from #leftroundabout to store indecies in a 1D vector instead, and we get this:
import qualified Data.Array.Accelerate as A
import Data.Array.Accelerate (Acc, Array, DIM1, DIM2, DIM3, Z(..), (:.)(..), (!), fromList, use)
rows = 7
cols = 7
type Cell = (Int, Int)
(neighs, nNeighs) = generateNeighs
-- Return a vector of indices of cells at distance 'd' or less from the given cell
getNeighs :: Int -> Cell -> Acc (Array DIM1 Cell)
getNeighs d (r,c) = A.take n $ A.drop start neighs
where
start = nNeighs ! A.constant (Z :. r :. c :. 0)
n = nNeighs ! A.constant (Z :. r :. c :. d)
generateNeighs :: (Acc (Array DIM1 Cell), Acc (Array DIM3 Int))
generateNeighs = (neighsArr, nNeighsArr)
where
idxs = concat [[(r, c) | c <- [0..cols-1]] | r <- [0..rows-1]]
(neighsLi, nNeighsLi, n) = foldl inner ([], [], 0) idxs
neighsArr = use $ fromList (Z :. n) neighsLi
nNeighsArr = use $ fromList (Z :. rows :. cols :. 5) nNeighsLi
inner (neighs', nNeighs', n') idx = (neighs' ++ cellNeighs, nNeighs'', n'')
where
(cellNeighs, cellNNeighs) = neighborhood idx
n'' = n' + length cellNeighs
nNeighs'' = nNeighs' ++ n' : cellNNeighs
neighborhood :: Cell -> ([Cell], [Int])
neighborhood (r,c) = (neighs, nNeighs)
where
neighsO = [ periphery d (r,c) | d <- [1..4] ]
neighs = (r,c) : concat neighsO
nNeighs = tail $ scanl (+) 1 $ map length neighsO
periphery d (r,c) =
-- The set of d-distance neighbors form a hexagon shape. Traverse each of
-- the sides of this hexagon and gather up the cell indices.
let
ps1 = take d . iterate (\(r,c)->(r,c+1)) $ (r-d,c)
ps2 = take d . iterate (\(r,c)->(r+1,c)) $ (r-d,c+d)
ps3 = take d . iterate (\(r,c)->(r+1,c-1)) $ (r,c+d)
ps4 = take d . iterate (\(r,c)->(r,c-1)) $ (r+d,c)
ps5 = take d . iterate (\(r,c)->(r-1,c)) $ (r+d,c-d)
ps6 = take d . iterate (\(r,c)->(r-1,c+1)) $ (r,c-d)
in filter isValid (ps6 ++ ps5 ++ ps4 ++ ps3 ++ ps2 ++ ps1)
isValid :: Cell -> Bool
isValid (r, c)
| r < 0 || r >= rows = False
| c < 0 || c >= cols = False
| otherwise = True
This can be by using the permute function to fill the neighbors for 1 cell at a time.
import Data.Bits (shift)
import Data.Array.Accelerate as A
import qualified Prelude as P
import Prelude hiding ((++), (==))
rows = 7
cols = 7
channels = 70
type Cell = (Int, Int)
(neighs, nNeighs) = fillNeighs
getNeighs :: Cell -> Acc (Array DIM1 Cell)
getNeighs (r, c) = A.take (nNeighs ! sh1) $ slice neighs sh2
where
sh1 = constant (Z :. r :. c)
sh2 = constant (Z :. r :. c :. All)
fillNeighs :: (Acc (Array DIM3 Cell), Acc (Array DIM2 Int))
fillNeighs = (neighs2, nNeighs2)
where
sh = constant (Z :. rows :. cols :. 18) :: Exp DIM3
neighZeros = fill sh (lift (0 :: Int, 0 :: Int)) :: Acc (Array DIM3 Cell)
-- nNeighZeros = fill (constant (Z :. rows :. cols)) 0 :: Acc (Array DIM2 Int)
(neighs2, nNeighs2li) = foldr inner (neighZeros, []) indices
nNeighs2 = use $ fromList (Z :. rows :. cols) nNeighs2li
-- Generate indices by varying column fastest. This assures that fromList, which fills
-- the array in row-major order, gets nNeighs in the correct order.
indices = foldr (\r acc -> foldr (\c acc2 -> (r, c):acc2 ) acc [0..cols-1]) [] [0..rows-1]
inner :: Cell
-> (Acc (Array DIM3 Cell), [Int])
-> (Acc (Array DIM3 Cell), [Int])
inner cell (neighs, nNeighs) = (newNeighs, n : nNeighs)
where
(newNeighs, n) = fillCell cell neighs
-- Given an cell and a 3D array to contain cell neighbors,
-- fill in the neighbors for the given cell
-- and return the number of neighbors filled in
fillCell :: Cell -> Acc (Array DIM3 Cell) -> (Acc (Array DIM3 Cell), Int)
fillCell (r, c) arr = (permute const arr indcomb neighs2arr, nNeighs)
where
(ra, ca) = (lift r, lift c) :: (Exp Int, Exp Int)
neighs2li = generateNeighs 2 (r, c)
nNeighs = P.length neighs2li
neighs2arr = use $ fromList (Z :. nNeighs) neighs2li
-- Traverse the 3rd dimension of the given cell
indcomb :: Exp DIM1 -> Exp DIM3
indcomb nsh = index3 ra ca (unindex1 nsh)
generateNeighs :: Int -> Cell -> [Cell]
generateNeighs d cell1 = [ (row2, col2)
| row2 <- [0..rows]
, col2 <- [0..cols]
, hexDistance cell1 (row2, col2) P.== d]
-- Manhattan distance between two cells in an hexagonal grid with an axial coordinate system
hexDistance :: Cell -> Cell -> Int
hexDistance (r1, c1) (r2, c2) = shift (abs rd + abs (rd + cd) + abs cd) (-1)
where
rd = r1 - r2
cd = c1 - c2
Mind this simple program:
zipWith f a b = snd $ mapAccumLOf each (\ (x:xs) a -> (xs, f a x)) (foldrOf each (:) [] b) a
toIndex shape pos = sumOf each $ zipWith (*) pos $ shape & partsOf each %~ scanl (*) 1
fromIndex shape idx = zipWith mod (shape & partsOf each %~ scanl div idx) shape
atPos shape pos = ix (toIndex shape pos)
main = do
let shape = [4,4]
let myTable = [
0,1,2,3,
4,5,6,7,
8,9,10,11,
12,13,14,15]
print $ myTable ^?! atPos shape [1,1] -- will print 5
The idea here is that atPos creates a lens just like ix, except that it converts a n-dimensional position on a grid to a flat index before indexing. For example, atPos [1,1,0] [4,4,4] reads at the index 5, and atPos [3,3,3] [4,4,4] reads at the index 63. The problem is that calling the "shape" argument all the time is inconvenient, so I created a type that recorded the shape:
data Grid f a = Grid {
_shape :: [Int],
_buffer :: f a
} deriving Show
makeLenses ''Grid
pget pos (Grid shape buffer) = buffer ^?! atPos shape pos
pmod pos f (Grid shape buffer) = buffer & atPos shape pos %~ f
main = do
let grid = Grid [4,4] [0..16]
print $ pget [1,1] grid
print $ pmod [1,1] (+100) grid
Now it is much more convenient, but since I'm not using lens anymore, I can't get use pget, pmod for different grids such as REPA.
My question is: is there any way to modify the indexes lens to work for n-dimensional indexes, so that you could, for example, view, modify and map REPA, Grid, Matrix etc.?
I need to solve a linear equation with matrix multiplication.
I am using Hugs, so I tried the following:
import Matrix(multiplyMat)
// more code follows
When I call this in the console, the console is telling me:
Can not find imported module "Memo".
But it is in the directory of all modules, so why is there an error?
The file is called Matrix.hs:
-- Some simple Hugs programs for manipulating matrices.
--
module Matrix where
import List
type Matrix k = [Row k] -- matrix represented by a list of its rows
type Row k = [k] -- a row represented by a list of literals
-- General utility functions:
shapeMat :: Matrix k -> (Int, Int)
shapeMat mat = (rows mat, cols mat)
rows :: Matrix k -> Int
rows mat = length mat
cols :: Matrix k -> Int
cols mat = length (head mat)
idMat :: Int -> Matrix Int
idMat 0 = []
idMat (n+1) = [1:replicate n 0] ++ map (0:) (idMat n)
-- Matrix multiplication:
multiplyMat :: Matrix Int -> Matrix Int -> Matrix Int
multiplyMat a b | cols a==rows b = [[row `dot` col | col<-b'] | row<-a]
| otherwise = error "incompatible matrices"
where v `dot` w = sum (zipWith (*) v w)
b' = transpose b
-- An attempt to implement the standard algorithm for converting a matrix
-- to echelon form...
echelon :: Matrix Int -> Matrix Int
echelon rs
| null rs || null (head rs) = rs
| null rs2 = map (0:) (echelon (map tail rs))
| otherwise = piv : map (0:) (echelon rs')
where rs' = map (adjust piv) (rs1++rs3)
(rs1,rs2) = span leadZero rs
leadZero (n:_) = n==0
(piv:rs3) = rs2
-- To find the echelon form of a matrix represented by a list of rows rs:
--
-- {first line in definition of echelon}:
-- If either the number of rows or the number of columns in the matrix
-- is zero (i.e. if null rs || null (head rs)), then the matrix is
-- already in echelon form.
--
-- {definition of rs1, rs2, leadZero in where clause}:
-- Otherwise, split the matrix into two submatrices rs1 and rs2 such that
-- rs1 ++ rs2 == rs and all of the rows in rs1 begin with a zero.
--
-- {second line in definition of echelon}:
-- If rs2 is empty (i.e. if null rs2) then every row begins with a zero
-- and the echelon form of rs can be found by adding a zero on to the
-- front of each row in the echelon form of (map tail rs).
--
-- {Third line in definition of echelon, and definition of piv, rs3}:
-- Otherwise, the first row of rs2 (denoted piv) contains a non-zero
-- leading coefficient. After moving this row to the top of the matrix
-- the original matrix becomes piv:(rs1++rs3).
-- By subtracting suitable multiples of piv from (suitable multiples of)
-- each row in (rs1++rs3) {see definition of adjust below}, we obtain a
-- matrix of the form:
--
-- <----- piv ------>
-- __________________
-- 0 |
-- . |
-- . | rs' where rs' = map (adjust piv) (rs1++rs3)
-- . |
-- 0 |
--
-- whose echelon form is piv : map (0:) (echelon rs').
--
adjust :: Num a => Row a -> Row a -> Row a
adjust (m:ms) (n:ns) = zipWith (-) (map (n*) ms) (map (m*) ns)
-- A more specialised version of this, for matrices of integers, uses the
-- greatest common divisor function gcd in an attempt to try and avoid
-- result matrices with very large coefficients:
--
-- (I'm not sure this is really worth the trouble!)
adjust' :: Row Int -> Row Int -> Row Int
adjust' (m:ms) (n:ns) = if g==0 then ns
else zipWith (\x y -> b*y - a*x) ms ns
where g = gcd m n
a = n `div` g
b = m `div` g
-- end!!
Your Matrix file exports nothing.
To export, write the header like this:
module Module (function, value, Type(Constructor))
The following function clearly has duplication between the two list comprehensions, but how can it be eliminated without increasing the total length of the code? I've got a sneaky feeling there's a nice abstraction lurking here but I just can't see it...
letterAt :: [Word] -> Int -> Int -> Maybe Char
letterAt wrds x y = listToMaybe $
[wtext !! (x - wx) |
Word wx wy wtext Across <- wrds,
wy == y && x >= wx && x < wx + length wtext] ++
[wtext !! (y - wy) |
Word wx wy wtext Down <- wrds,
wx == x && y >= wy && y < wy + length wtext]
Some background:
The function is taken from a crossword program. The crossword is represented as [Word], where
data Word = Word { startX :: Int,
startY :: Int,
text :: String,
sense :: Sense }
data Sense = Across | Down
The words where sense == Across start at position (startX, startY) and continue in the positive x direction, and those where sense == Down continue in the positive y direction. The aim of the function is to get the character at location (x, y) in a Just, or Nothing if there isn't a character there.
Feel free to point out any other sins against Haskell I've committed here, I've just started with the language and still trying to get to grips with it!
Here are some points about your code:
It is better to use filter when want to select certain elements of a list based on the predicate.
Since you just want the first element satisfying certain predicate you can use Data.List.find
Your conditions looks symmetric so you can define a transform function as
transform f x y (Word wx wy wtext Across) = f wtext wy wx y x
transform f x y (Word wx wy wtext Down) = f wtext wx wy x y
Now writing the code will require writing conditions only once
letterAt :: [Word] -> Int -> Int -> Maybe Char
letterAt wrds x y = (transform charValue x y) <$> find (transform condition x y) wrds
where
condition wtext wx wy x y = wx == x && y >= wy && y < wy + length wtext
charValue wtext wx wy x y = wtext !! (y-wy)
Satvik, thanks for your answer which got me thinking along the right lines. I separated out the condition and transformation functions as you suggested, then realized that it would be simpler to transform the data rather than the functions and put everything back into a list comprehension for readability:
letterAt :: [Word] -> Int -> Int -> Maybe Char
letterAt wrds x y = listToMaybe
[wtext !! x' | Word wx wy wtext sens <- wrds,
let (x', y') = case sens of
Across -> (x - wx, y - wy)
Down -> (y - wy, x - wx),
y' == 0, x' >= 0, x' < length wtext ]
You pointed out that it's better to use Data.find in this case.. is this for readability or efficiency? I'm guessing that because lists are lazy in Haskell, the above code would stop after the first item in the list comprehension was evaluated, is this right?