0

Suppose I have a list comprehension that returns a list of sequences, where the elements chosen depend on each other (see example below). Is there a way to (conveniently) program the number of elements and their associated conditions based on an earlier computation? For example, return type [[a,b,c]] or [[a,b,c,d,e]] depending on another value in the program? Also, are there other/better ways than a list comprehension to formulate the same idea?

(I thought possible, although cumbersome and limited, to write out a larger list comprehension to start with and trim it by adding to s a parameter and helper functions that could make one or more of the elements a value that could easily be filtered later, and the associated conditions True by default.)

s = [[a, b, c, d] | a <- list, someCondition a, 
                    b <- list, b /= a, not (someCondition b), 
                    otherCondition a b,
                    c <- list, c /= a, c /= b, not (someCondition c),
                    otherCondition b c,
                    d <- list, d /= a, d /= b, d /= c,
                    someCondition d, someCondition (last d),
                    otherCondition c d]
גלעד ברקן
  • 23,602
  • 3
  • 25
  • 61
  • Could you post your actual code? – Dietrich Epp Feb 14 '13 at 11:04
  • @krshekhar: The original formatting was correct. – Dietrich Epp Feb 14 '13 at 11:08
  • @DietrichEpp ...sure, it's here: http://stackoverflow.com/questions/14843466/simplest-way-to-solve-a-maze-without-mutability/14866570#14866570 ...it computes sequences to solve a maze but the length of the path is based on another computation. – גלעד ברקן Feb 14 '13 at 11:10
  • 7
    Side note: Don't write `someCondition a == True`, write just `someCondition a`. Similarly, `not (someCondition b)` instead of `someCondition b == False`. It's more readable that way. – Daniel Fischer Feb 14 '13 at 11:45
  • @DietrichEpp sorry I miss understood that. – शेखर Feb 14 '13 at 14:48
  • @DanielFischer indeed, I read that a kitten dies every time you compare an expression to a Boolean literal. – pat Feb 14 '13 at 16:14
  • @DanielFischer ...thank you for illuminating that. I have been using that form in other languages but failed to apply it, apparently, when reading about list comprehensions. – גלעד ברקן Feb 14 '13 at 17:11
  • Hey, maybe you'll be interested in this: https://github.com/quchen/articles/blob/master/loeb-moeb.md . Even if not, it's pretty fun to think about. – utdemir Dec 12 '14 at 16:17

4 Answers4

4

The question is incredibly difficult to understand.

Is there a way to (conveniently) program the number of elements and their associated conditions based on an earlier computation?

The problem is "program" is not really an understandable verb in this sentence, because a human programs a computer, or programs a VCR, but you can't "program a number". So I don't understand what you are trying to say here.

But I can give you code review, and maybe through code review I can understand the question you are asking.

Unsolicited code review

It sounds like you are trying to solve a maze by eliminating dead ends, maybe.

What your code actually does is:

  1. Generate a list of cells that are not dead ends or adjacent to dead ends, called filtered

  2. Generate a sequence of adjacent cells from step 1, sequences

  3. Concatenate four such adjacent sequences into a route.

Major problem: this only works if a correct route is exactly eight tiles long! Try to solve this maze:

[E]-[ ]-[ ]-[ ] 
             |
[ ]-[ ]-[ ]-[ ]
 |
[ ]-[ ]-[ ]-[ ]
             |
[ ]-[ ]-[ ]-[ ]
 |
[ ]-[ ]-[ ]-[E]

So, working backwards from the code review, it sounds like your question is:

How do I generate a list if I don't know how long it is beforehand?

Solutions

You can solve a maze with a search (DFS, BFS, A*).

import Control.Monad

-- | Maze cells are identified by integers
type Cell = Int

-- | A maze is a map from cells to adjacent cells
type Maze = Cell -> [Cell]

maze :: Maze
maze = ([[1],     [0,2,5],     [1,3],   [2],
         [5],     [4,6,1,9],   [5,7],   [6,11],
         [12],    [5,13],      [9],     [7,15],
         [8,16],  [14,9,17],   [13,15], [14,11],
         [12,17], [13,16,18],  [17,19], [18]] !!)

-- | Find paths from the given start to the end
solve :: Maze -> Cell -> Cell -> [[Cell]]
solve maze start = solve' [] where
  solve' path end =
    let path' = end : path
    in if start == end 
       then return path'
       else do neighbor <- maze end
               guard (neighbor `notElem` path)
               solve' path' neighbor

The function solve works by depth-first search. Rather than putting everything in a single list comprehension, it works recursively.

  1. In order to find a path from start to end, if start /= end,

  2. Look at all cells adjacent to the end, neighbor <- maze end,

  3. Make sure that we're not backtracking over a cell guard (negihbor `notElem` path),

  4. Try to find a path from start to neighbor.

Don't try to understand the whole function at once, just understand the bit about recursion.

Summary

If you want to find the route from cell 0 to cell 19, recurse: We know that cell 18 and 19 are connected (because they are directly connected), so we can instead try to solve the problem of finding a route from cell 0 to cell 18.

This is recursion.

Footnotes

The guard,

someCondition a == True

Is equivalent to,

someCondition a

And therefore also equivalent to,

(someCondition a == True) == True

Or,

(someCondition a == (True == True)) == (True == (True == True))

Or,

someCondition a == (someCondition a == someCondition a)

The first one, someCondition a, is fine.

Footnote about do notation

The do notation in the above example is equivalent to list comprehension,

do neighbor <- maze end
   guard (neighbor `notElem` path)
   solve' path' neighbor

The equivalent code in list comprehension syntax is,

[result | neighbor <- maze end,
          neighbor `notElem` path,
          result <- solve' path' neighbor]
Dietrich Epp
  • 205,541
  • 37
  • 345
  • 415
  • ...Thank you for taking the time to review my code and explain how do notation is equivalent to list comprehension. What a wonderfully succinct example of a recursive maze solver! Could you please show me how to implement it? I tried passing different parameters to start (with and without a specific 'end') but received error messages each time. – גלעד ברקן Feb 14 '13 at 16:04
  • ...just a side note, looks like you missed something when you wrote, "Major problem: this only works if a correct route is exactly eight tiles long!" Firstly, my solver is flexible and allows for overlap in the two-cell sequences when building the path so, trimming duplicates, the path could be 8 or less as it is now evaluated. And secondly, my question is EXACTLY about how to make the path length (even) more flexible and still use combinations rather than recursion to solve it. That was the point of my self-guided exercise. – גלעד ברקן Feb 14 '13 at 17:33
  • ...Dietrich, perhaps instead of (or in addition to) trying to understand my question by reviewing the code, you could've tried asking me to explain my question better to you. In fact, because of that, you failed to answer my question, and gave us your own solution to a different problem. My question was not about solving a maze, it was about understanding how to make a list comprehension (or a similar process that computes combinations without recursion) more flexible. – גלעד ברקן Feb 14 '13 at 21:17
  • 1
    You never mentioned recursion in your question, not even once, so how am I supposed to know that you want an answer that does not use recursion? I gave you a list comprehension as asked, and it depends on "previous values" by accumulating them in a function parameter. – Dietrich Epp Feb 14 '13 at 23:01
  • ..it took me some time to learn/understand your recursive solution and I have to tell you - to me it seems brilliant and it taught me a lot, thank you! – גלעד ברקן Feb 16 '13 at 17:52
2

Is there a way to (conveniently) program the number of elements and their associated conditions based on an earlier computation? For example, return type [[a,b,c]] or [[a,b,c,d,e]] depending on another value in the program?

I suppose you want to encode the length of the list (or vector) statically in the type signature. Length of the standard lists cannot be checked on type level.

One approach to do that is to use phantom types, and introduce dummy data types which will encode different sizes:

newtype Vector d = Vector { vecArray :: UArray Int Float }

-- using EmptyDataDecls extension too
data D1
data D2
data D3

Now you can create vectors of different length which will have distinct types:

vector2d :: Float -> Float -> Vector D2
vector2d x y = Vector $ listArray (1,2) [x,y]

vector3d :: Float -> Float -> Float -> Vector D3
vector3d x y z = Vector $ listArray (1,3) [x,y,z]

If the length of the output depends on the length of the input, then consider using type-level arithmetics to parametrize the output. You can find more by googling for "Haskell statically sized vectors".

A simpler solution is to use tuples, which are fixed length. If your function can produce either a 3-tuple, or a 5-tuple, wrap them with an Either data type: `Either (a,b,c) (a,b,c,d,e).

Community
  • 1
  • 1
sastanin
  • 40,473
  • 13
  • 103
  • 130
1

Looks like you're trying to solve some logic puzzle by unique selection from finite domain. Consult these:

The way this helps us is, we carry our domain around while we're making picks from it; and the next pick is made from the narrowed domain containing what's left after the previous pick, so a chain is naturally formed. E.g.

p43 = sum [ fromDigits [v0,v1,v2,v3,v4,v5,v6,v7,v8,v9]
            | (dom5,v5) <- one_of [0,5] [0..9]   -- [0..9] is the
            , (dom6,v6) <- pick_any dom5         --   initial domain
            , (dom7,v7) <- pick_any dom6          
            , rem (100*d5+10*d6+d7) 11 == 0 
            ....

-- all possibilities of picking one elt from a domain
pick_any :: [a] -> [([a], a)]
pick_any []     = [] 
pick_any (x:xs) = (xs,x) : [ (x:dom,y) | (dom,y) <- pick_any xs]

-- all possibilities of picking one of provided elts from a domain
--                           (assume unique domains, i.e. no repetitions)
one_of :: (Eq a) => [a] -> [a] -> [([a], a)]
one_of ns xs = [ (ys,y) | let choices = pick_any xs, n <- ns,
                          (ys,y) <- take 1 $ filter ((==n).snd) choices ]

You can trivially check a number of elements in your answer as a part of your list comprehension:

s = [answer | a <- .... , let answer=[....] , length answer==4 ]

or just create different answers based on a condition,

s = [answer | a <- .... , let answer=if condition then [a,b,c] else [a]]
Community
  • 1
  • 1
Will Ness
  • 70,110
  • 9
  • 98
  • 181
  • ...Thank you for your answer and suggestions. It looks like the references will take some study for me. I tried to test the last suggestions in your comments but failed (I tried "[answer | a <- [1..10] , let answer=[a] , length answer==4 ]" and got "[]"). Could you show me a one-liner example with it? -- say I wanted a <- [1..10], and length answer==4 – גלעד ברקן Feb 14 '13 at 16:11
  • @groovy your 1st test: yes, length of `[a]` is 1, and so (1==4) is `False`. That is the point of testing in list comprehensions: to weed out the non-conformants. I've updated the answer, using better names for the functions. – Will Ness Feb 14 '13 at 16:16
  • @groovy if you put there `[ ans | ... , let ans=[a,b,c,d]]`, then the length of that `ans` is 4! You wrote it as such. If you create it by other means, then `[ ans | ..., length ans==4]` will only contain such `ans` that have length 4 (all the others will be skipped over). That's basic list comprehension in Haskell. :) – Will Ness Feb 14 '13 at 16:19
  • oh, and the I could put ...,length ans===someValueComputedElsewhere...? – גלעד ברקן Feb 14 '13 at 16:20
  • @groovy yes, you can use any variable that is defined at that point, in the test. – Will Ness Feb 14 '13 at 16:22
1

You have Data.List.subsequences

You can write your list comprehension in monadic form (see guards in Monad Comprehensions):

(Explanation: The monad must be an instance of MonadPlus which supports failure.

guard False makes the monad fail evaluating to mzero., subsequent results are appended with mplus = (++) for the List monad.)

import Control.Monad (guard)

myDomain = [1..9]   -- or whatever

validCombinations :: [a] -> [[a]]
validCombinations domainList = do
        combi <- List.subsequences domainList
        case combi of
                [a,b] -> do
                        guard (propertyA a && propertyB b)
                        return combi

                [a,b,c] -> do
                        guard (propertyA a && propertyB b && propertyC c)
                        return combi

                _ -> guard False

main = do
         forM_ (validCombinations myDomain) print

Update again, obtaining elements recursively, saving combinations and checks

import Control.Monad

validCombinations :: Eq a => Int -> Int -> [a] -> [(a -> Bool)] -> [a] -> [[a]]
validCombinations indx size domainList propList accum = do

    elt <- domainList   -- try all domain elements

    let prop = propList!!indx
    guard $ prop elt               -- some property
    guard $ elt `notElem` accum    -- not repeated 

    {-
    case accum of
        prevElt : _ -> guard $ some_combined_check_with_previous elt prevElt
        _ -> guard True
        -}

    if size > 1 then do
         -- append recursively subsequent positions

         other <- validCombinations (indx+1) (size-1) domainList propList (elt : accum)

         return $ elt : other
    else
         return [elt]

myDomain = [1..3] :: [Int]

myProps = repeat (>1)

main = do
           forM_ (validCombinations 0 size myDomain myProps []) print 
   where
      size = 2 

result for size 2 with non trivial result:

    [2,3]
    [3,2]
Gabriel Riba
  • 6,698
  • 2
  • 17
  • 19
  • I believe it is more efficient to test as soon as possible, whatever possible, to weed out the selections. Your code will pick triples in full, and then test them in full. This usually takes (much) more time than first picking pairs, testing, and then picking the third elt and testing some more (from my Prolog experience with solving puzzles). :) – Will Ness Feb 14 '13 at 16:51
  • @Gabriel ...That's awesome! but what if I wanted the size of [a,b...] to be determined by a value computed elsewhere in the program so the size could be anything from two to 100, for example, without writing each one out as a case for combi? – גלעד ברקן Feb 14 '13 at 16:52
  • @Will ...oh, I think you may have misunderstood...the full path in my solver is made of lists strung together (each list contains two cells). It's how many of them to string together that I am struggling with. Will the maze path be 4,5..10, pairs of cells long? (My conditions allow them to overlap so the path could collapse and end up with either an even or odd length) – גלעד ברקן Feb 14 '13 at 17:03
  • I know (for Will Ness) that there are better solutions, but I only wanted to show Monad Comprehensions. – Gabriel Riba Feb 14 '13 at 17:04
  • GabrielRiba yes, thank you. @groovy then you need to look at the first answer with recursive solver. List comprehension is something you write by hand in your code, it's of rigid structure. Study that answer with the recursive solution. – Will Ness Feb 14 '13 at 17:06
  • @Will ...the recursive answer is indeed wonderfully succinct but my purpose was exactly NOT to write a recursive solver. I specifically wanted to solve it by calculating combinations, and was struggling with how to program it to tackle different sized mazes/paths. – גלעד ברקן Feb 14 '13 at 17:15
  • @GabrielRiba ...the update looks good, but if I understand it right, I would still need to write each 'when' statement by hand. I am hoping for a way for the program to do it by writing some kind of a general case, that would evaluate based on 'len'. – גלעד ברקן Feb 14 '13 at 17:23
  • @GabrielRiba ..Thank you for taking the time to try and help me... I would need to study more to understand it...if I understand correctly, 'propList' would contain a list of the conditions to satisfy. If so, would those still need to be entered manually? My question is about finding a way to generalize those as well. I guess to answer that, we would have to talk about the conditions specifically. – גלעד ברקן Feb 14 '13 at 21:08