Tim4497
Tim4497

Reputation: 380

Add specific element to List when Condition is True

I want to add a tuple if a bit is set in a 8 bit long number (e.g 146).

My code looks like this and Haskell is just printing to the first true expression:

returnPossibleMoves stone = if testBit (look stone) 0 then [(0,-1)] else [(0,0)] ++
                            if testBit (look stone) 1 then [(1,-1)] else [(0,0)] ++
                            if testBit (look stone) 2 then [(1,0)] else [(0,0)] ++
                            if testBit (look stone) 3 then [(1,1)] else [(0,0)] ++
                            if testBit (look stone) 4 then [(0,1)] else [(0,0)] ++
                            if testBit (look stone) 5 then [(-1,1)] else [(0,0)] ++
                            if testBit (look stone) 6 then [(-1,0)] else [(0,0)] ++
                            if testBit (look stone) 7 then [(-1,-1)] else [(0,0)]

with look stone = 146 -> 10010010

So my return is just: [(0,0),(1,-1)]

Also is it possible to get rid of the else?

Upvotes: 1

Views: 94

Answers (2)

Daniel Wagner
Daniel Wagner

Reputation: 153102

Just for fun, here's the expensive but beautiful way:

intSin :: Int -> Int
intSin n = round (sin (pi*fromIntegral n/4))

returnPossibleMoves stone =
    [ (intSin i, intSin (i-2))
    | i <- [0..7]
    , testBit (look stone) i
    ]

Why expensive? Because sin and floating-point multiply+divide is almost certainly more expensive than a simple lookup-table approach, as described in other answers. But this way is, I think, much more descriptive of the meaning of the function... at least to somebody who knows the geometrical interpretation of sin.

Of course, you can also make intSin quite efficient, and keep the explanatory name and the explanatory implementation of returnPossibleMoves; for example:

intSin :: Int -> Int
intSin x
    | x .&. 3 == 0 = 0
    | otherwise = 1 - shiftR (x .&. 4) 1

Upvotes: 0

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 477170

You can fix the code by adding brackets here. Note that the else case should just yield an empty list. For example:

returnPossibleMoves stone =
    (if testBit (look stone) 0 then [(0,-1)] else []) ++
    (if testBit (look stone) 1 then [(1,-1)] else []) ++
    (if testBit (look stone) 2 then [(1,0)] else []) ++
    (if testBit (look stone) 3 then [(1,1)] else []) ++
    (if testBit (look stone) 4 then [(0,1)] else []) ++
    (if testBit (look stone) 5 then [(-1,1)] else []) ++
    (if testBit (look stone) 6 then [(-1,0)] else []) ++
    (if testBit (look stone) 7 then [(-1,-1)] else [])

That being said, it does not look very elegantly. You can use zip here to make 2-tuples of where you combine the moves with the bit to test, and then filter your list. Finally, you can use map to unpack the 2-tuples, and retain the first element. For example:

returnPossibleMoves stone = map snd (filter (testBit (look stone) . fst) (zip [0..] moves))
    where moves = [(0,-1), (1,-1), (1,0), (1,1), (0,1), (-1,1), (-1,0), (-1,-1)]

or with list comprehension, as @chi suggests:

returnPossibleMoves stone = [move | (i,move) <- zip [0..] moves, testBit (look stone) i]
    where moves = [(0,-1), (1,-1), (1,0), (1,1), (0,1), (-1,1), (-1,0), (-1,-1)]

So here the result is:

Prelude Data.Bits> returnPossibleMoves 146
[(1,-1),(0,1),(-1,-1)]

(if we set look to id).

This makes sense since for 146 the second, fifth, and eighth bit are set, and thus we return the second ((1,-1)), fifth ((0,1)), and eighth ((-1,-1)) element.

Upvotes: 3

Related Questions