views:

292

answers:

6

This is not homework. I'm learning Standard ML on my own. I know a bit of Scheme, too, so this question ought to be answerable in either language.

My self-imposed assignment is to write a function that constructs a list of integers from 1 to n. For example, list(7) should return [1,2,3,4,5,6,7]. An O(n) solution would be ideal.

It's easy to construct a list in reverse (i.e., [n,n-1,..,1]) in linear time:

fun list 1 = 1::nil
|   list n = n::list(n-1);

My attempt to construct a list going forward is O(n^2) because the append operation is linear.

fun list 1 = 1::nil
|   list n = list(n-1) @ n::nil;

My next attempt was to build a list from the end to the front (right to left) by starting with the nil, attaching n to the front, and recursing backwards to 1. But it didn't work at all.

fun list n = (if n = 1
              then 1
              else list(n-1) :: n) :: nil;

Something makes me think I need a helper function that builds un-terminated lists to be used in the recursion, but I'm stumped.

+3  A: 

One classic approach is to build it in reverse order, then reverse it. That's two times O(n), which is of course just as O(n).

unwind
Had to look up how to do a linear-time reverse. Once I got that to work, the rest was easy.
Barry Brown
+3  A: 

Here's a solution:

fun list n =
  let
    fun f 1 m = m::nil
    |   f n m = m::f (n-1) (m+1)
  in
    f n 1
  end;
TrayMan
I think you meant m::f (n-1) (m+1) instead of m::list (n-1) (m+1)
Chris Conway
Maybe it's just me, but I cringe whenever I see a non-tail-recursive function that can be easily rewritten to be tail-recursive.
ephemient
I just wanted to show the most obvious solution based on what Barry already had. I suppose I should've noted that a tail recursive solution is possible, but Chris Conway already did that.
TrayMan
It's only a tiny cringe ;) If this weren't for educational purposes, I would suggest `fun list n = List.tabulate (n, fn x => x + 1)` as the best implementation. Actually, maybe I'll still do that...
ephemient
A: 
(define (iota n)
  (let f ((i n)(a '())
    (if (zero? i)
        (reverse a)
        (f (- i 1) (cons i a)))))
leppie
Using SRFI-1's unfold-right: (define (iota n) (unfold-right zero? 1- 1- n)) (where 1- is defined the usual way: (define (1- x) (- x 1)))
Chris Jester-Young
Build in reverse order then reverse: (define (iota n) (reverse (unfold zero? 1- 1- n)))
Chris Jester-Young
+3  A: 

Here's a version using a helper function and a tail-recursion-enabling accumulator:

fun list n =
  let
    fun aux i acc = 
      if i > 0
      then aux (i-1) (i::acc)
      else acc
  in
    aux n nil
  end;
Chris Conway
Wont that be the result in reverse?
leppie
Nope. Try it out.
Chris Conway
This is nearly identical to my first answer, although I flipped the argument order of the helper so that partial evaluation worked out nicely. If I had realized that earlier, I probably wouldn't have written mine :)
ephemient
+4  A: 

Using the Basis Library,

fun list n = List.tabulate (n, fn x => x + 1)

With a simple accumulator,

val list =
    let fun list' k 0 = k
          | list' k n = list' (n::k) (n-1)
    in list' nil end

This builds a list starting from the tail end. If you think of the reductions,

   list 5
=> list' nil 5
=> list' (5::nil) 4
=> list' (4::5::nil) 3
=> list' (3::4::5::nil) 2
=> list' (2::3::4::5::nil) 1
=> list' (1::2::3::4::5::nil) 0
=> [1, 2, 3, 4, 5]

Alternatively,

Something makes me think I need a helper function that builds un-terminated lists to be used in the recursion, but I'm stumped.

A representation of an unterminated list is a function which takes a list and returns a list: for example, to represent 10::_, you could use fn x => 10::x.

fun list n =
    let fun list' m k = if m > n then k nil else
                        list' (m+1) (fn x => k (m::x))
    in list' 1 (fn x => x) end

Once again, if you think of the reductions,

   list 5
=> list' 1 (fn x => x)
=> list' 2 (fn x => (fn x => x) (1::x))
=> list' 3 (fn x => (fn x => (fn x => x) (1::x)) (2::x))
=> list' 4 (fn x => (fn x => (fn x => (fn x => x) (1::x)) (2::x)) (3::x))
=> list' 5 (fn x => (fn x => (fn x => (fn x => (fn x => x) (1::x)) (2::x)) (3::x)) (4::x))
=> list' 6 (fn x => (fn x => (fn x => (fn x => (fn x => (fn x => x) (1::x)) (2::x)) (3::x)) (4::x)) (5::x))
=> (fn x => (fn x => (fn x => (fn x => (fn x => (fn x => x) (1::x)) (2::x)) (3::x)) (4::x)) (5::x)) nil
=> (fn x => (fn x => (fn x => (fn x => (fn x => x) (1::x)) (2::x)) (3::x)) (4::x)) (5::nil)
=> (fn x => (fn x => (fn x => (fn x => x) (1::x)) (2::x)) (3::x)) (4::5::nil)
=> (fn x => (fn x => (fn x => x) (1::x)) (2::x)) (3::4::5::nil)
=> (fn x => (fn x => x) (1::x)) (2::3::4::5::nil)
=> (fn x => x) (1::2::3::4::5::nil)
=> [1, 2, 3, 4, 5]

In this case, the algorithm can be structured such that an ordinary data structure suffices for the accumulator, but using a continuation as an accumulator is a very powerful and useful technique that should not be overlooked.

ephemient
You'll notice that both methods here are tail-recursive. That's another thing that accumulators are useful for. Think about how an optimizing compiler might use an accumulator to make non-tail-recursive functions into tail-recursive functions, and see if you can structure your algorithms to lend themselves towards this goal.
ephemient
+2  A: 

With list problems like these, it is often easier to solve a more general problem.

How do I build a list containing the integers i such that n <= i <= m, in order?

The solution has a base case and an induction step:

  • If n > m, the list is empty.

  • If n <= m, the solution is to write n followed by the solution to the problem n+1 <= i <= m.

This view leads quickly to clear, concise ML code (tested):

fun range n m = if n > m then [] else n :: range (n+1) m
fun list n = range 1 n
Norman Ramsey
That's exactly where I was going with this problem. I figured if I could get it to work for 1..n, I could eventually get it working for m..n. As it is, with ephemient's help, I've created a function that creates a list from the first, second, and last element, similar to one of the ways Haskell allows users to construct lists.
Barry Brown
You could even take advantage of partial evaluation and write `val list = range 1` :)
ephemient
@ephemient: Sweet. (But I think you mean partial *application*.)
Norman Ramsey