I'd start off by formatting your solution like this:
fun lookSay [] = []
| lookSay (x::xs) =
let
fun helper(y, z::zs, count) =
if y = z
then helper(y, zs, count + 1)
else (count, y) :: lookSay zs
in
helper(x, xs, 1)
end
I've used x, y and z rather than l and l' because I find that l can be hard to read: I can't easily see if it's a lowercase L, an uppercase i, or a 1. Since we're actually using the constant 1 also, this adds to the confusion.
I've also done some variable renaming: You have one pair of l and ls in the scope of the case-of, and another identically named pair of l and ls inside the helper function that shadow the outer variables. While this works, it is extremely confusing the have different things in close proximity named the same.
Similarly I renamed acc into count to clarify what it accumulates.
As molbdnilo hinted, your problem lies in a missing pattern match. The ML compiler should warn you of this:
! Toplevel input:
! ..........helper(y, z::zs, count) =
! if y = z
! then helper(y, zs, count + 1)
! else (count, y) :: lookSay zs
! Warning: pattern matching is not exhaustive
Which hints that helper should also have a [] pattern.
As for the strategy of this solution: The inner helper function does two things that necessitates an inner helper function. First, it has an extra argument for the "current" item, which I called y, and second, it has the accumulating argument, which I called count. Peculiarly, helper calls lookSay rather than itself.
But only the second of these is truly necessary, since you can also use the head of the list argument to keep the "current" item:
fun lookSay xs =
let
fun go [] _ = []
| go (x::y::zs) count =
if x = y
then go (x::zs) (count + 1)
else (count + 1, x) :: go (y::zs) 0
in
go xs 0
end
This strategy looks at the first two elements of a list at once, x and y, and if they're equal, it discards one but increments count. If x and y are not equal, this marks the end of the sequence of xs, so a (count + 1, x) element is emitted and go can call itself recursively on y::zs.
I've preserved the bug that is also present in your example. In my version molbdnilo's hint can be rephrased as "Think about how the go function handles lists with exactly one element."
I think that to make this function slightly more useful, you want to actually emit a plain list:
fun concatMap f xs =
List.concat (List.map f xs)
fun flatten pairs =
concatMap (fn (n, x) => [n, x]) pairs
fun lookSay xs =
let
fun go [] _ = []
| go (x::y::zs) i =
if x = y
then go (x::zs) (i+1)
else (i+1,x) :: go (y::zs) 0
in flatten (go xs 0) end
Assuming that the bug gets fixed, this should give you:
- lookSay [1, 1, 1, 2, 2, 3];
> val it = [3, 1, 2, 2, 1, 3] : int list