reset password
Author Message
rabbott
Posts: 1649
Posted 09:33 Nov 10, 2016 |

The following has been added to Project 9.

 

As the text noted, repeatedly summing the distances in a Path is inefficient if the paths get long. So let’s add a distance field to the Path data type. To help you develop a feeling for pattern matching and whether you like it, revise the Path data type in two ways.

Unnamed fields: data Path = Path [Step] Int

Named fields:     data Path = Path {steps :: [Step], dist :: Int}

Then revise the rest of the code so that it works with the modified Path definitions. When you use named fields, don’t use pattern matching on Paths elsewhere in the code. Use the field names as getters and setters instead. (Note that pattern matching works even when you have named fields. But for this project, don’t use pattern matching when you have named fields.)

Field names are used as setters when you create a new instance of a type. For example, the +> operation might be written as follows when using named fields.

(+>) :: Path -> Path -> Path

p1 +> p2 = Path {steps = steps p1 ++ steps p2,
                 dist  = dist p1 + dist p2}

When you are done, you will have two revised files for this project. They should be the same except for how Paths are treated. 

See the project description for more details.

Last edited by rabbott at 10:40 Nov 10, 2016.
lishenyu
Posts: 103
Posted 22:47 Nov 12, 2016 |

Hi professor,

I want to use splitOn, so I import Data.List.Split but winGhci gave me an error :can't find this module. is there any way to use it? or could you tell me some other function that does the same job as this function? thanks!

rabbott
Posts: 1649
Posted 22:52 Nov 12, 2016 |

Do you mean the function splitAt? It doesn't have to be imported. It's part of the Prelude.

lishenyu
Posts: 103
Posted 23:49 Nov 12, 2016 |

no, it's splitOn, which takes a string as delimeter for example, splitOn " "  "1 2 3 4 5" will return ["1","2","3","4","5"]. And I googled it, it says I need to import Data.Text . But winghici doesn't allow me to import it either.

rabbott
Posts: 1649
Posted 09:38 Nov 13, 2016 |

Here's one way to do it.

-- prefixMatch tests to see if its first param is a prefix of the second.
-- If so, it returns Just (length prefix). Otherwise it returns Nothing. 

prefixMatch :: Eq a => [a] -> [a] -> Maybe Int
prefixMatch prefix list 
  | prefix == take (length prefix) list = Just (length prefix)
  | otherwise = Nothing

 

splitOn :: Eq a => ([a] -> Maybe Int) -> [a] -> [[a]]
splitOn prefixMatch list =
  -- Find delim (Start, End) pairs. Assume first position is 0.
  let dStartEndPairs = (0,0):[(i,i+len) | i <- [0 .. length list], 
                                          Just len <- [prefixMatch (drop i list)]]
      startLengthPairs = makeSLPairs dStartEndPairs   
  in [(take len . drop start) list | (start, len) <- startLengthPairs] 

  where
  -- Make a list of (start, len) pairs for the delimited sequences.
  makeSLPairs :: [(Int, Int)] -> [(Int, Int)] 
  -- This is the final delimiter. Will always be
  -- at least one since we added 0 to the  list. 
  makeSLPairs [(_, dEnd)] = [(dEnd, length list - dEnd)]
  makeSLPairs ((_, dEnd1) : dse2@(dStart2, dEnd2) : dses) 
    -- The test for dStart2 >= dEnd1 eliminates cases in which, e.g., the 
    -- delim is "aa" and the list contains " ... aaa ... ". The first
    -- and second "a" will both be considered the start of a delimiter. 
    -- But dStart2 < dEnd1.
    | dStart2 >= dEnd1 = (dEnd1, dStart2-dEnd1) : (makeSLPairs (dse2 : dses)) 
  makeSLPairs (dse : _ : dses) = makeSLPairs (dse : dses)

-------------

> splitOn (prefixMatch "aa") "abaaadcaaaaefaagd"
["ab","adc","","ef","gd"]

 

If you use it, I'll expect you to explain how it works.

Is this for Project 8?

Last edited by rabbott at 08:04 Nov 14, 2016.
rabbott
Posts: 1649
Posted 10:09 Nov 13, 2016 |

Deleted

Last edited by rabbott at 07:59 Nov 14, 2016.
rabbott
Posts: 1649
Posted 13:17 Nov 13, 2016 |

This version is tail recursive both in collecting the elements into sequences and in collecting the sequences. Uses the same prefixMatch as above.

splitOn' :: Eq a => ([a] -> Maybe Int) -> [a] -> [[a]]
splitOn' prefixMatch list = splitOnAux list [] [] where
  
  -- The parameters to splitOnAux are:
  --   lst: the remaining elements in the original list.
  --   seq: the growing current sequence, in reverse order;
  --   seqs: the growing list of sequences, in reverse order;

  splitOnAux lst@(x:dses) seq seqs = 
    case prefixMatch lst of
    -- Found a delimiter. Move the current seq, reversed, from seq to seqs.
    Just len -> splitOnAux (drop len lst) [] (reverse seq:seqs)
    -- The usual case. Not the end of the sequence.         
    Nothing  -> splitOnAux dses (x:seq) seqs
  -- We're done. Reverse seqs.
  splitOnAux [] [] seqs  = reverse seqs
  -- End of input. Move the current seq, reversed, from seq to seqs.
  splitOnAux [] seq seqs = splitOnAux [] [] (reverse seq:seqs)

 

If you use it, I'll expect you to explain how it works.

Last edited by rabbott at 08:00 Nov 14, 2016.
rabbott
Posts: 1649
Posted 08:02 Nov 14, 2016 |

The code for the two versions of splitOn is available here.