-1
-- 1. Graph structure: nodes and adjacency matrix (i.e. the edges) 
data Node = A | B | C | D | E | F deriving (Show,Eq,Ord)

adj :: (Node,Node) -> Bool
adj p = case p of
  (A,B) -> True
  (A,C) -> True  
  (B,C) -> True
  (B,F) -> True
  (C,D) -> True
  (D,E) -> True
  (E,B) -> True
  (E,F) -> True
  (F,A) -> True
  (_,_) -> False

type Path = [Node]

-- 2. Auxiliary functions
adjacentNodes :: Node -> [Node] -> [Node]
adjacentNodes n ns = filter (\x -> adj(n,x)) ns

allNodes :: [Node]
allNodes = [A,B,C,D,E,F]

choice :: ([a],[a]) -> [a]
choice = uncurry (++)

-- 3. To do
addtoEnd :: Path -> [Node] -> [Path]
addtoEnd p ns = undefined

hCycles :: Node -> [Path]
hCycles n = undefined

I have got this code (it was given to us, I can't change it or the types) and need to define the function hCycles using the list monad (and the do notation). hCycles is supposed to compute the Hamiltonian Cycles for any generic node of the graph in the image.

The thing is I'm not quite sure how to do that with the list monad... Despite that, I think I have a first version of the function:

hCycles :: Node -> [Path]
hCycles n = do 
            p <- [[n]]
            nextNode <- adjacentNodes n allNodes
            if n == nextNode
            then [p]
            else addtoEnd p allNodes

Still the if/else case has a weird behaviour and, since hCycles isn't called again, I don't even think it's recursive... How can I fix that?

graph

  • 3
    that's quite a big exercise - what did you try so far? `hCycles node` is supposed to return all paths with first and last element in the list being `node` with all nodes in the path but none other than `node` twice? – Random Dev Jun 01 '21 at 15:21
  • It looks like this has little to do with monads or do notation. You should start by thinking about the algorithm you want to use for computing such cycles. (Then, it's possible that some non-deterministic choice in that algorithm can be implemented using the list monad.) – chi Jun 01 '21 at 15:35
  • @Carsten that's exactly it – Cris Teller Jun 01 '21 at 15:38
  • @chi The thing is I'm not quite sure where to start, I know that the first and last nodes are the same and that no other node can appear twice... Maybe start building a list (path) with the nodes visited and, each time one is added, check to see if it isn't the second time it happens? – Cris Teller Jun 01 '21 at 15:41
  • Your `addtoEnd` is unnescessarily slow. Use `:` to prepend `a` instead of appending. You don't care which way round the path is. – Paul Johnson Jun 01 '21 at 15:48
  • @PaulJohnson the project says this "For each node a in ns, if a is not already in p the function creates a new path by adding to the end of p the element a." so I assumed `:` won't do that – Cris Teller Jun 01 '21 at 15:51

2 Answers2

1

In the list monad a line of the form:

x <- f y

expects f to return a list. x is going to get instantiated with each value of the list in turn, so the rest of the do clause will be run with each of those values.

You will see that adjacentNodes returns a list of nodes. So starting from n you can consider each node that it connects to like this:

nextNode <- adjacentNode n allNodes

Write this function:

steps :: [Nodes] -> Path -> [Path]
steps _ [] = fail "Need a node to start with."
steps ns path@(n:rest) = do
   nextNode <- adjacentNode n ns
   guard $ not $ elem nextNode path   -- Stop if we've already visited this node.
   return $ nextNode : path

You can imagine this as the algorithm for finding a single path, which (thanks to the list monad) magically finds all the possible paths. This isn't the whole answer, but it should give you enough to get started with.

(Note: I haven't actually tested this code.)

Paul Johnson
  • 17,438
  • 3
  • 42
  • 59
  • I already have that function `addtoEnd` (don't know how I didn't paste in the post, but I corrected it now). Still I'm not quite sure how to start, all I do know is that the first and last nodes are the same and that no other node can appear twice... Maybe start building a list (path) with the nodes visited and, each time one is added, check to see if it isn't the second time it happens? – Cris Teller Jun 01 '21 at 15:43
  • @CrisTeller OK, now make it recursive. Add the starting node as an extra argument which is just passed down the recursion and use it to check if the `nextNode` is the starting node. If it is then terminate the recursion, otherwise call `steps` again with the extended path. – Paul Johnson Jun 01 '21 at 15:47
  • @PaulJohnson this is a school project so technically I can't change/add the function arguments – Cris Teller Jun 01 '21 at 15:52
  • I added my attempt for `hCycles` to the question – Cris Teller Jun 01 '21 at 21:09
  • @CrisTeller No, you need to change `addToEnd` to include whatever arguments you see fit. If it hasn't reached the starting node then just put a call to `addToEnd` at the end of `addToEnd`. Read https://en.wikibooks.org/wiki/Haskell/Recursion for more clues. Then `hCycles` is just a call to `addToEnd` with the starting values of the extra arguments. – Paul Johnson Jun 02 '21 at 07:19
1

Hi I guess it's enough time to give you some version that will solve your problem:

hCycles :: Node -> [Path]
hCycles n = 
    filter isValidPathLength $ map (n:) $ go [] (adjacentNodes n allNodes)
    where
    isValidPathLength path =
        length path == length allNodes + 1
    -- note: go will only care about a path to n 
    -- but will take care of not visiting nodes two-times
    go _ [] = [] -- fail if there is no node left to discover
    go visited toVisit = do
        cur <- toVisit
        if cur == n then
            pure [n] -- found n
        else do
            let neighboursToVisit = filter (`notElem` visited) $ adjacentNodes cur allNodes
            pathToEnd <- go (cur:visited) neighboursToVisit
            pure $ cur:pathToEnd

I noticed your adj does not fit your picture so I changed it to

adj :: (Node,Node) -> Bool
adj p = case p of
  (A,B) -> True
  (A,C) -> True  
  (B,C) -> True
  (B,F) -> True
  (C,D) -> True
  (D,E) -> True
  (E,B) -> True
  (E,F) -> True
  (F,A) -> True
  (_,_) -> False

(yours seem to not be a directed graph)

with this you'll get:

> hCycles A
[[A,B,C,D,E,F,A],[A,C,D,E,B,F,A]]

Some notes:

I did not care about performance here (for example there are better data-structures to manage visited then a list) - this one does a brute-force deep-first-search - if you want you can adapt this to BFS - it's a nice exercise IMO (one you might want to get rid of the do notation stuff though ... hey you asked for it)

Random Dev
  • 51,810
  • 9
  • 92
  • 119
  • Yes, I fount it strange as well and confirmed with my teacher and `adj` was not correct (I edited in the question as well). Thank you so much for you explanation, it was very clear! – Cris Teller Jun 02 '21 at 14:43