3

我是 OCaml 新手。我在 OCaml 中编写了一个简单的程序来生成公平和平方数(一个回文数和另一个回文数的平方,更多细节在这里:https ://code.google.com/codejam/contest/2270488/dashboard# s=p2 ) 如下:

更新 1:优化算法(在我的笔记本电脑上大约需要 20 秒):

open Printf;;

let rec _is_palindrome s i j =
  i >= j || 
  (s.[i] = s.[j] && 
      _is_palindrome s (i + 1) (j - 1))
;;

let is_palindrome s =
  let sl = String.length s in
    sl > 0 && (_is_palindrome s 0 (sl - 1))
;;

let rec del_zeros s =
  let sl = String.length s in
    if (sl < 1) then
      s
    else
      (if s.[0] = '0' then
         del_zeros (String.sub s 1 (sl - 1))
       else
         s
      )
;;

let c2i c =
  Char.code c - Char.code '0'
;;

let i2c i =
  Char.chr (i + Char.code '0')
;;

(* only for finding fair and square numbers *)
let square s =
  let slen = String.length s in
    if slen < 1 then
      ""
    else
      let reslen = 2 * slen in
      let t = ref 0 in
        t := 0;
        (* fast check *)
        (let i = reslen/2 in
           for j = (slen - 1) downto 0 do
             if (i - 1 - j) >= 0 && (i - 1 - j) < slen then
               t := !t + (c2i s.[j]) * (c2i s.[i - 1 - j]);
           done;
           if !t > 9 then
             (* jump out *)
             raise (Invalid_argument "carry");
        ); 
        (let res = String.make reslen '0' in
           (* do the square cal now *)
           for i = (reslen - 1) downto 1 do
             t := 0;
             for j = (slen - 1) downto 0 do
               if (i - 1 - j) >= 0 && (i - 1 - j) < slen then
                 t := !t + (c2i s.[j]) * (c2i s.[i - 1 - j]);
             done;
             if !t > 9 then
               (* jump out *)
               raise (Invalid_argument "carry");
             res.[i] <- i2c !t;
           done;
           del_zeros res
        );
;;

let rec check_fs fsns p =
  try let sq = square p in
    if (is_palindrome sq) then
      sq :: fsns
    else
      fsns
  with Invalid_argument "carry" ->
    fsns
;;

(* build the fair and square number list *)
(* dfs *)
let rec create_fair_square_nums fsns p sum max_num_digs =
  let l = String.length p in
    if l > max_num_digs || sum > 9 then
          fsns
    else
      let fsns = create_fair_square_nums fsns ("0" ^ p ^ "0") sum max_num_digs in
      let fsns = create_fair_square_nums fsns ("1" ^ p ^ "1") (sum + 1) max_num_digs in
      let fsns = create_fair_square_nums fsns ("2" ^ p ^ "2") (sum + 4)  max_num_digs in
      let fsns = create_fair_square_nums fsns ("3" ^ p ^ "3") (sum + 9) max_num_digs in
      let fsns = check_fs fsns p in
        fsns
;;

let rec print_fsns fsns =
  List.iter (fun s -> printf "%s " s) fsns;
  printf "\n"
;;

let num_str_cmp s1 s2 =
  let len1 = String.length s1 in
  let len2 = String.length s2 in
    match (len1 - len2) with
      | 0 ->
          String.compare s1 s2
      | cmp -> cmp
;;

(* works *)

let max_dig = 51;;

let fsns = 
  let fsns = create_fair_square_nums [] "" 0 max_dig in
  let fsns = create_fair_square_nums fsns "0" 0 max_dig in
  let fsns = create_fair_square_nums fsns "1" 1 max_dig in
  let fsns = create_fair_square_nums fsns "2" 4 max_dig in
    create_fair_square_nums fsns "3" 9 max_dig
;;

let fsns = List.sort num_str_cmp fsns;;

print_fsns fsns;;

我的原始代码(天真的解决方案,太慢了):

open Printf;;

let rec _is_palindrome s i j =
  if i < j then
    if s.[i] = s.[j] then
      _is_palindrome s (i + 1) (j - 1)
    else
      false
  else
    true
;;

let is_palindrome s =
  if (String.length s < 1) then
    false
  else
    _is_palindrome s 0 ((String.length s) - 1)
;;

let rec del_zeros s =
  let sl = String.length s in
    if (sl < 1) then
      s
    else
      (if s.[0] = '0' then
         del_zeros (String.sub s 1 (sl - 1))
       else
         s
      )
;;

let c2i c =
  Char.code c - Char.code '0'
;;

let i2c i =
  Char.chr (i + Char.code '0')
;;

(* only positive number *)
let square s =
  (* including the carry dig *)
  let slen = String.length s in
  let res = (
    if slen > 0 then
      let reslen = 2 * slen in
      let res = String.make reslen '0' in
      let t = ref 0 in
        for i = (reslen - 1) downto 1 do
          t := c2i (res.[i]);
          for j = (slen - 1) downto 0 do
            if (i - 1 - j) >= 0 && (i - 1 - j) < slen then
              (t := !t + (c2i s.[j]) * (c2i s.[i - 1 - j]);
          (* printf "%d, %d: %d\n" j (i - 1 - j) !t; *) )
          done;
          (* printf "%d: %d\n" i !t; *)
          if !t > 9 then
            (res.[i - 1] <- 
             Char.chr (Char.code res.[i - 1] + (!t / 10));
             t := !t mod 10
            );
          res.[i] <- i2c !t;
        done;
        res;
        else
          ""
  ) in
    (* printf "square %s -> %s\n" s res; *)
    del_zeros res
;;

let extend_palindrome new_ps n = 
  ("0" ^ n ^ "0") :: 
  ("1" ^ n ^ "1") :: 
  ("2" ^ n ^ "2") :: 
  ("3" ^ n ^ "3") :: 
  new_ps
;;

let rec extend_palindromes new_ps ps = 
  match ps with
    | [] -> new_ps
    | h :: t -> 
        let new_ps = extend_palindrome new_ps h in
          extend_palindromes new_ps t
;;

let rec check_fs fsns ps =
  match ps with
    | [] -> fsns
    | h :: t -> 
        let sq = square h in
          if (is_palindrome sq) then
            check_fs (sq :: fsns) t
          else 
            check_fs fsns t
;;

(* build the fair and square number list *)
let rec create_fair_square_nums fsns ps max_num_digs =
  match ps with
    | h :: t ->
        if String.length h > max_num_digs then
          fsns
        else
          let ps = extend_palindromes [] ps in
          let fsns = check_fs fsns ps in
            create_fair_square_nums fsns ps max_num_digs
    | [] ->
        raise (Invalid_argument "fsn should not be []")
;;

let rec print_fsns fsns =
  List.iter (fun s -> printf "%s " s) fsns;
  printf "\n"
;;

let num_str_cmp s1 s2 =
  let len1 = String.length s1 in
  let len2 = String.length s2 in
    match (len1 - len2) with
      | 0 ->
          String.compare s1 s2
      | cmp -> cmp
;;

(* works *)

let max_dig = 50;;

let fsns = 
  let fsns0 = create_fair_square_nums [] [""] max_dig in
  let fsns1 = create_fair_square_nums [] ["0"; "1"; "2"; "3"] max_dig in
    (* print_fsns fsns0;
    print_fsns fsns1; *)
    ["0"; "1"; "4"; "9"] @ fsns0 @ fsns1
;;

(* print_fsns fsns;; *)

let fsns = List.sort num_str_cmp fsns;;

print_fsns fsns;;

此代码生成 10^100 以内的公平和平方数。

此代码在性能方面应该存在一些(或许多)问题。在我杀死它之前,它运行了 30 多分钟。当 max_dig = 14 时,它会很快完成(< 1 分钟)。

欢迎任何关于改进此代码的建议或批评。

4

2 回答 2

4

这可能是许多其他问题中的一个(在您的用例中可能可以忽略不计,您应该分析一下以找出答案),但此代码片段已经存在算法问题:

let rec del_zeros s =
  let sl = String.length s in
    if (sl < 1) then
      s
    else
      (if s.[0] = '0' then
         del_zeros (String.sub s 1 (sl - 1))
       else
         s
      )
;;

String.sub 在长度参数中是线性的(在内存中,因此是时间),所以整个函数是二次的:del_zeros (String.make 50_000 '0')可能会很慢。

为了有效地编写此代码,您应该将保留的字符收集在列表累加器中,不断累积总长度,最后创建一个大小合适的字符串并将这些字符写入其中。

作为近似值,使用的自然代码Buffer已经相当有效(这是我建议为通常的应用程序编写的,但如果这是关键路径的一部分,则可能不在算法竞赛中):

let del_zeros s =
  let buf = Buffer.create (String.length s) in
  for i = 0 to (String.length s) - 1 do
    if s.[i] <> '0' then Buffer.add_char buf s.[i]
  done;
  Buffer.contents buf
于 2013-04-15T13:13:17.193 回答
3

在处理大整数时,您可以使用Big_int比您自定义的 square 实现更快的模块(它还可以为您节省大量编写代码的时间)。

if a then b else false此外,在你可以简单地写的地方写是不好的风格a && b

于 2013-04-15T15:28:12.307 回答