Search code examples
functional-programmingocamllazy-evaluationlist-manipulation

Lazy "n choose k" in OCaml


As part of a bigger problem of enumerating a set, I need to write an OCaml function 'choose' which takes a list and outputs as the list of all possible sequences of size k made up of elements of that list (without repeating sequences which can be obtained from each other by permutation). The order they are put in the end list is not relevant.

For example,

choose 2 [1;2;3;4] = [[1;2];[1;3];[1;4];[2;3];[2;4];[3;4]]

Any ideas?

I would like to have the whole thing to be lazy, outputting a lazy list, but if you have a strict solution, that'll be very useful too.


Solution

  • Here is a strict and suboptimal version. I hope it is clear. It avoids duplicates by assuming there are no duplicates in the input list, and by generating only sublists that are in the same order as in the original list.

    The length computation could be factored by passing l's length as an argument of choose. That would make the code less readable but more efficient.

    For the lazy version, sprinkle "lazy" and "Lazy.force" on the code...

    let rec choose k l =
      if k = 0 
      then [ [] ]
      else
        let len = List.length l in
        if len < k
        then []
        else if k = len
        then [ l ]
        else
          match l with
          h :: t ->
              let starting_with_h =
                (List.map (fun sublist -> h :: sublist) (choose (pred k) t))
              in
              let not_starting_with_h = choose k t in
              starting_with_h @ not_starting_with_h
          | [] -> assert false
    ;;
      val choose : int -> 'a list -> 'a list list = <fun>
    
    # choose 3 [1; 2; 3; 4; 5; 6; 7] ;;                        
    - : int list list =
    [[1; 2; 3]; [1; 2; 4]; [1; 2; 5]; [1; 2; 6]; [1; 2; 7]; [1; 3; 4]; [1; 3; 5];
     [1; 3; 6]; [1; 3; 7]; [1; 4; 5]; [1; 4; 6]; [1; 4; 7]; [1; 5; 6]; [1; 5; 7];
     [1; 6; 7]; [2; 3; 4]; [2; 3; 5]; [2; 3; 6]; [2; 3; 7]; [2; 4; 5]; [2; 4; 6];
     [2; 4; 7]; [2; 5; 6]; [2; 5; 7]; [2; 6; 7]; [3; 4; 5]; [3; 4; 6]; [3; 4; 7];
     [3; 5; 6]; [3; 5; 7]; [3; 6; 7]; [4; 5; 6]; [4; 5; 7]; [4; 6; 7]; [5; 6; 7]]
    

    EDIT:

    A lazy_list_append as appears necessary from the comments below:

    type 'a node_t =             
          | Empty
          | Node of 'a * 'a zlist_t
    and 'a zlist_t = 'a node_t lazy_t
    
    let rec lazy_list_append l1 l2 =
      lazy 
        (match Lazy.force l1 with
          Empty -> Lazy.force l2 
        | Node (h, lt) ->
        Node (h, lazy_list_append lt l2))
    ;;