3

How to create a function to zip and unzip two lists as tupled lists in Standard ML?

Example:

unzip [[1,4],[2,5],[3,6]] -> [1,2,3] [4,5,6]

zip [1,2,3] [0,2,4] -> [[1,0],[2,2],[3,4]]
Ionuț G. Stan
  • 176,118
  • 18
  • 189
  • 202
jetpackman
  • 53
  • 1
  • 7

4 Answers4

2

I figured out what I was doing wrong. Here's the code:

fun zip nil nil = nil
  | zip nil l = l
  | zip l nil = l
  | zip (h::t) (k::l) = [h,k]::(zip t l)
fun mapcan(f,nil) = nil | mapcan(f,h::t) = (f h)@(mapcan(f,t))
fun unzip (l) = if (l = nil) then nil else [(map head l),(mapcan tail l)]

Unzipping is slightly more difficult. We need map functions that select the first and second elements of a two-element list over the zipped list. Since the problem is somewhat under-specified by the example, we will put the rest of the longer list into the first list. To avoid problems with the empty tails for the shorter list, we use the mapcan function that appends the tail lists.

jetpackman
  • 53
  • 1
  • 7
2

It is usually not a good idea to use head and tail, but instead to use pattern matching. You can encode unzip a bit more elegantly as follows:

fun unzip l = 
  case l
    of nil => (nil, nil)
     | (a,b)::tl => 
        let val (l1, l2) = unzip tl
        in (a::l1, b::l2) end

Also as one of the commenters above mentioned, zip and unzip typically work on pairs of lists, and lists of pairs respectively.

Matt
  • 4,029
  • 3
  • 20
  • 37
0

There is absolutely no need for the lexical scoping introduced by the let statement. By defining the projection functions, one can actually get a far more concise and elegant representation :

fun fst p =
   case p of 
      (x,_) => x

fun snd p =
   case p of
      (_,y) => y

fun unzip lp =
   case lp of 
      [] => ([], [])
    | (x,y) :: lp' => (x :: (fst (unzip lp')), y :: (snd (unzip lp')))

There is good reason why this works, which is that Type-Inference from the SML compiler is strong enough to deduce the types of the terms from the case statements and the cons statements. The projection functions are deduced before, and the CSP for the Type-Constraints is solvable. IMO, this is far more elegant than the solutions presented before with the let statement.

Compiler

0

As as addendum to Matt's answer, this can be done in a tail-recursive fashion by passing an accumulator.

fun unzip lst = 
  let
    fun unzip' [] (fst, snd) = (List.rev fst, List.rev snd)
      | unzip' ((a, b)::tl) (fst, snd) = unzip' tl (a::fst, b::snd)
  in
    unzip' lst ([], [])
  end;

Alternatively, using continuation passing style, we avoid having to reverse the resulting lists.

fun id x = x

fun unzip_cps [] k = k ([], [])
  | unzip_cps ((a, b)::tl) k = 
      unzip_cps tl (fn (x, y) => k (a::x, b::y))

fun unzip lst = unzip_cps lst id
Chris
  • 26,361
  • 5
  • 21
  • 42