OCaml 99 问题笔记(Logic and Codes 部分)

2024/09/02 FP OCaml 共 9195 字,约 27 分钟

笔记

开始搞得有点像编译原理了哦,很神秘。也不是很像。

41. Truth Tables for Logical Expressions (2 Variables)

Let us define a small “language” for boolean expressions containing variables:

# type bool_expr =
  | Var of string
  | Not of bool_expr
  | And of bool_expr * bool_expr
  | Or of bool_expr * bool_expr;;
type bool_expr =
    Var of string
  | Not of bool_expr
  | And of bool_expr * bool_expr
  | Or of bool_expr * bool_expr

A logical expression in two variables can then be written in prefix notation. For example, (a ∨ b) ∧ (a ∧ b) is written:

# And (Or (Var "a", Var "b"), And (Var "a", Var "b"));;
- : bool_expr = And (Or (Var "a", Var "b"), And (Var "a", Var "b"))

Define a function, table2 which returns the truth table of a given logical expression in two variables (specified as arguments). The return value must be a list of triples containing (value_of_a, value_of_b, value_of_expr).

# table2 "a" "b" (And (Var "a", Or (Var "a", Var "b")));;
- : (bool * bool * bool) list =
[(true, true, true); (true, false, true); (false, true, false);
 (false, false, false)]

额啊,题好长。还好是固定参数的,好像 OCaml 函数只能固定参数数量。

表驱动,懂?太对劲了是个锤子的表驱动。

let table2 one two exp = 
	let rec eval exp (fir, sec)= match exp with
		| Var e -> if e = one then fir else sec
		| Not e -> not (eval e (fir, sec))
		| And (e1, e2) -> (eval e1 (fir, sec)) && (eval e2 (fir, sec))
		| Or (e1, e2) -> (eval e1 (fir, sec)) || (eval e2 (fir, sec))
	in
	List.map (fun (a,b) -> (a, b, (eval exp (a, b))) )  [(true, true); (true, false); (false, true); (false, false)];;

val table2 : string -> 'a -> bool_expr -> (bool * bool * bool) list = <fun>

我的函数签名还不太一样,第二个参数没有限制,这是怎么做到的,问题是第一个参数的类型限制又是什么时候加上的呢?有什么是依赖 string 的吗?OCaml 的类型推断,真的很神秘。

# let rec eval2 a val_a b val_b = function
    | Var x -> if x = a then val_a
               else if x = b then val_b
               else failwith "The expression contains an invalid variable"
    | Not e -> not (eval2 a val_a b val_b e)
    | And(e1, e2) -> eval2 a val_a b val_b e1 && eval2 a val_a b val_b e2
    | Or(e1, e2) -> eval2 a val_a b val_b e1 || eval2 a val_a b val_b e2
  let table2 a b expr =
    [(true,  true,  eval2 a true  b true  expr);
     (true,  false, eval2 a true  b false expr);
     (false, true,  eval2 a false b true  expr);
     (false, false, eval2 a false b false expr)];;
val eval2 : string -> bool -> string -> bool -> bool_expr -> bool = <fun>
val table2 : string -> string -> bool_expr -> (bool * bool * bool) list =
  <fun>

42. Truth Tables for Logical Expressions

Generalize the previous problem in such a way that the logical expression may contain any number of logical variables. Define table in a way that table variables expr returns the truth table for the expression expr, which contains the logical variables enumerated in variables.

这下任意数量参数了。得搞个 map 了。

写了我好久,这种过程较为复杂的情况,声明式的语言就优势一点。好吧,可能也没啥优势,就是我不熟 OCaml 的库函数,而且还在用闭包啥的导致情况有点复杂。

说起来,OCaml 的 Map 还是有序的,fold 文档里说 where k1 ... kN are the keys of all bindings in m (in increasing order) 这么看底层是什么情况呢,链表吗。

let table vars exp= let module VarTable = Map.Make(String) in
	let rec get_input l before= match l with
		| [] -> [before]
		| h::t -> (get_input t ((h, true)::before)) @ (get_input t ((h, false)::before))
	in
	let rec into_map l m= match l with 
		| [] -> m
		| (name, value)::t -> into_map t (VarTable.add name value m)
	in
	let var_maps = List.map (fun e -> into_map e VarTable.empty) (get_input vars []) in 

	let rec eval inner_exp var_map= match inner_exp with
		| Var e -> VarTable.find e var_map
		| Not e -> not (eval e var_map)
		| And (e1, e2) -> (eval e1 var_map) && (eval e2 var_map)
		| Or (e1, e2) -> (eval e1 var_map) || (eval e2 var_map)
	in
	List.map (fun m -> ((VarTable.fold (fun k v acc-> acc@ [(k,v)]) m []), (eval exp m))) var_maps;;

val table :
  String.t list -> bool_expr -> ((String.t * bool) list * bool) list = <fun>

哦,List.assoc 这么牛的吗,当 map 用了,我还老老实实用 map 了,不过 List.assoc 估计是 O(n) 的。

# (* [val_vars] is an associative list containing the truth value of
     each variable.  For efficiency, a Map or a Hashtlb should be
     preferred. *)

  let rec eval val_vars = function
    | Var x -> List.assoc x val_vars
    | Not e -> not (eval val_vars e)
    | And(e1, e2) -> eval val_vars e1 && eval val_vars e2
    | Or(e1, e2) -> eval val_vars e1 || eval val_vars e2

  (* Again, this is an easy and short implementation rather than an
     efficient one. *)
  let rec table_make val_vars vars expr =
    match vars with
    | [] -> [(List.rev val_vars, eval val_vars expr)]
    | v :: tl ->
         table_make ((v, true) :: val_vars) tl expr
       @ table_make ((v, false) :: val_vars) tl expr

  let table vars expr = table_make [] vars expr;;
val eval : (string * bool) list -> bool_expr -> bool = <fun>
val table_make :
  (string * bool) list ->
  string list -> bool_expr -> ((string * bool) list * bool) list = <fun>
val table : string list -> bool_expr -> ((string * bool) list * bool) list =
  <fun>

43. Gray Code

An n-bit Gray code is a sequence of n-bit strings constructed according to certain rules. For example,

n = 1: C(1) = ['0', '1'].
n = 2: C(2) = ['00', '01', '11', '10'].
n = 3: C(3) = ['000', '001', '011', '010', '110', '111', '101', '100'].

Find out the construction rules and write a function with the following specification: gray n returns the n-bit Gray code.

# gray 1;;
- : string list = ["0"; "1"]
# gray 2;;
- : string list = ["00"; "01"; "11"; "10"]
# gray 3;;
- : string list = ["000"; "001"; "011"; "010"; "110"; "111"; "101"; "100"]

查了下

2.1.1 产生的基本规律原则和标准做法

其实就是3个步骤,

第一步,改变最右边的位元值;

第二步,改变右起第一个为1的位元的左边位元;

第三步,第四步重复第一步和第二步,直到所有的格雷码产生完毕(换句话说,已经走了(2^n) - 1 步)。

我能不能倒过来生成呢,倒着遍历不是很方便。也就是:

第一步,改变最左边的位元值;

第二步,改变左起第一个为1的位元的右边位元;

第三步,第四步重复第一步和第二步,直到所有的格雷码产生完毕(换句话说,已经走了(2^n) - 1 步)。

中途有哪一步把顺序搞反了,头疼。好吧,没有,我又憨憨了。

递归导致的顺序混乱是真的头晕。最后 fold 的时候又有点混乱。

let gray n = 
	let rec init_it remain = if remain>0 then "0"::init (remain-1) else [] in
	let init = init_it n in
	let pow a b = int_of_float ((float_of_int a) ** (float_of_int b)) in
	let total = pow 2 n -1 in
	let rec next step now acc = 
		if total = List.length acc then acc
		else if step = 1 then
			match now with 
				| [] ->raise Not_found
				| h::t -> let next_step = ((if h="1" then "0" else "1") :: t) in
					next 2 next_step (next_step::acc)
			else if step = 2 then 
				let rec step_two l= match l with
					| f::s::t -> if f = "1" then (f::(if s="1" then "0" else "1")::t) 
						else (f::(step_two (s::t)))
					| _ -> raise Not_found
				in
				let next_step = step_two now in
				next 1 next_step (next_step::acc)
			else raise Not_found
	in
	List.map (fun e -> List.fold_right (fun a acc-> acc^a) e "") (init :: List.rev (next 1 init []));;

val gray : int -> string list = <fun>

流汗了,你怎么这么短。

看起来有个比较神奇的生成方法。没太看懂,呃呃。

# let gray n =
    let rec gray_next_level k l =
      if k < n then
        (* This is the core part of the Gray code construction.
         * first_half is reversed and has a "0" attached to every element.
         * Second part is reversed (it must be reversed for correct gray code).
         * Every element has "1" attached to the front.*)
        let (first_half,second_half) =
          List.fold_left (fun (acc1,acc2) x ->
              (("0" ^ x) :: acc1, ("1" ^ x) :: acc2)) ([], []) l
        in
        (* List.rev_append turns first_half around and attaches it to second_half.
         * The result is the modified first_half in correct order attached to
         * the second_half modified in reversed order.*)
        gray_next_level (k + 1) (List.rev_append first_half second_half)
      else l
    in
      gray_next_level 1 ["0"; "1"];;
val gray : int -> string list = <fun>

44. Huffman Code

First of all, consult a good book on discrete mathematics or algorithms for a detailed description of Huffman codes (you can start with the Wikipedia page)!

We consider a set of symbols with their frequencies. For example, if the alphabet is "a",…, "f" (represented as the positions 0,…5) and respective frequencies are 45, 13, 12, 16, 9, 5:

瞄了一眼答案,流汗了,有点长。

至少,我需要一个树。癌,好麻烦。

OCaml 有空指针吗?是有 Option 的。

我甚至不会把值简单地从 type 里取出来,一定得 match 一下,我服了。

猜对了,左树 0 右树 1,还行啊。

    type 'a tree_node = 
            | Nil
            | Tree_node of 'a * 'a tree_node * 'a tree_node;;

    type 'a tree_node = Nil | Tree_node of 'a * 'a tree_node * 'a tree_node
let huffman l = 
        let init_nodes = List.map (fun (name,fre) -> Tree_node ((name,fre), Nil, Nil) ) l in
        let extract_value node = match node with
            | Nil -> raise Not_found
            | Tree_node ((name,fre),l,r) -> fre
        in
        let extract_all node = match node with
            | Nil -> raise Not_found
            | Tree_node ((name,fre),l,r) -> ((name,fre),l,r)
        in
        let rec stack nodes = 
        	let sorted_nodes = List.sort (fun a b -> (extract_value a) - (extract_value b)) nodes in
                match sorted_nodes with
                	| [] -> raise Not_found
                	| f::s::t -> stack ((Tree_node (("", (extract_value f) + (extract_value s)), f, s))::t)
                	| h::t -> h
        in
        let rec assign node bits = 
        	let ((name,fre),l,r) = extract_all node in if name != "" then [(name, bits)] else (assign l (bits^"0")) @ (assign r (bits^"1"))
        in
        match (stack init_nodes) with
        	| root -> assign root ""
        	| _ -> raise Not_found;;

Line 23, characters 11-12:
Warning 11 [redundant-case]: this match case is unused.

val huffman : (string * int) list -> (string * string) list = <fun>

标准答案整了个优先队列,搞个堆而不是 list 应该就差不多了。

# (* Simple priority queue where the priorities are integers 0..100.
     The node with the lowest probability comes first. *)
  module Pq = struct
    type 'a t = {data: 'a list array; mutable first: int}
    let make() = {data = Array.make 101 []; first = 101}
        let add q p x =
      q.data.(p) <- x :: q.data.(p);  q.first <- min p q.first
          let get_min q =
      if q.first = 101 then None else
        match q.data.(q.first) with
        | [] -> assert false
        | x :: tl ->
           let p = q.first in
           q.data.(q.first) <- tl;
           while q.first < 101 && q.data.(q.first) = [] do
             q.first <- q.first + 1
           done;
           Some(p, x)
  end
    type tree =
    | Leaf of string
    | Node of tree * tree
      let rec huffman_tree q =
    match Pq.get_min q, Pq.get_min q with
    | Some(p1, t1), Some(p2, t2) -> Pq.add q (p1 + p2) (Node(t1, t2));
                                    huffman_tree q
    | Some(_, t), None | None, Some(_, t) -> t
    | None, None -> assert false
      (* Build the prefix-free binary code from the tree *)
  let rec prefixes_of_tree prefix = function
    | Leaf s -> [(s, prefix)]
    | Node(t0, t1) ->  prefixes_of_tree (prefix ^ "0") t0
                     @ prefixes_of_tree (prefix ^ "1") t1
                       let huffman fs =
    if List.fold_left (fun s (_, p) -> s + p) 0 fs <> 100 then
      failwith "huffman: sum of weights must be 100";
    let q = Pq.make () in
    List.iter (fun (s, f) -> Pq.add q f (Leaf s)) fs;
    prefixes_of_tree "" (huffman_tree q);;
module Pq :
  sig
    type 'a t = { data : 'a list array; mutable first : int; }
    val make : unit -> 'a t
    val add : 'a t -> int -> 'a -> unit
    val get_min : 'a t -> (int * 'a) option
  end
type tree = Leaf of string | Node of tree * tree
val huffman_tree : tree Pq.t -> tree = <fun>
val prefixes_of_tree : string -> tree -> (string * string) list = <fun>
val huffman : (string * int) list -> (string * string) list = <fun>

第一个 advanced 啊,感觉,,,不如 gray code 那道麻烦。

总结

不是,等于说我一整天就写了两道题?是不是有点弱智了。

也不对,下午搞了会事务性的工作,还好,稍微有点弱智。

这几道题都有点强度啊,哈人。

文档信息

Search

    Table of Contents