Pencarian Catatan Rekursif Tipe OCaml

Saya mencoba mencari nilai bidang secara rekursif dalam catatan yang merupakan jenis catatan rekursif.

Jenis catatan saya adalah

type node = {node_name:string; node_branch:node list}

Pertama, saya mencoba melintasi variabel seperti pohon dengan tipe ini:

let tree = {node_name="trunk"; node_branch=[{node_name="branch1L:1"; node_branch=[{node_name="branch2L:1"; node_branch=[]};
                                                                                  {node_name="foo_node"; node_branch=[]};];};
                                            {node_name="branch1L:2"; node_branch=[]}];}
    in
    let target = "foo_node" in
    print_newline();
    search_tree_for_name target tree;

Idenya adalah seperti ini:

                    trunk
            __________|_________
           |                    |
       branch1L:1           branch1L:2
    ______|_______
   |              |
branch2:1       foo_node

Jadi saya mencari record yang memiliki nilai field node_name = foo_node. Cara saya melintasinya, sekadar untuk membuktikan logika saya adalah:

let rec search_tree_for_name target_name in_tree =
  if in_tree.node_name = target_name then
    (* First check the node_name *)
      Format.printf "[%s] Target node_name found\n" in_tree.node_name
  else
    (* Then evaluate the branches *)
    begin
      match in_tree.node_branch with
      | [] ->
         Format.printf "[%s] Branches: 0; End of branch\n" in_tree.node_name
      | head :: tail ->
         Format.printf "[%s] Branches: %i\n" in_tree.node_name (List.length in_tree.node_branch);
         search_tree_for_name target_name head;
         List.iter (search_tree_for_name target_name ) tail;
    end

Ini mencetak:

[trunk] Branches: 2
[branch1L:1] Branches: 2
[branch2L:1] Branches: 0; End of branch
[foo_node] Target node_name found
[branch1L:2] Branches: 0; End of branch

Sekarang, yang sebenarnya saya inginkan hanyalah melihat apakah ada instance di mana node_name adalah yang saya cari, jadi saya hanya ingin mengembalikan boolean.

Yang saya coba adalah (saya membuat fungsi baru hanya untuk tujuan pengujian):

let rec check_tree_for_name target_name in_tree =
  if in_tree.node_name = target_name then
    begin
    (* First check the node_name *)
      Format.printf "[%s] Target node_name found\n" in_tree.node_name;
      true
    end 
  else
    (* Then evaluate the branches *)
    begin
      match in_tree.node_branch with
      | [] ->
         Format.printf "[%s] Branches: 0; End of branch\n" in_tree.node_name;
         false
      | head :: tail ->
         Format.printf "[%s] Branches: %i\n" in_tree.node_name (List.length in_tree.node_branch);
         check_tree_for_name target_name head;
         List.iter (check_tree_for_name target_name ) tail;
    end

Saya mendapatkan Kesalahan berikut pada fungsi saya, saya meneruskan ke List.iter:

Error: This expression has type node -> bool
       but an expression was expected of type node -> unit
       Type bool is not compatible with type unit 

Lalu saya mengubah blok pencocokan pola terakhir menjadi

  | head :: tail ->
     Format.printf "[%s] Branches: %i\n" in_tree.node_name (List.length in_tree.node_branch);
     if check_tree_for_name target_name head then true
     else List.iter (check_tree_for_name target_name ) tail;

Sehingga kondisi if memeriksa kembalinya pemanggilan fungsi dan meneruskannya. Sekarang saya memiliki satu kesalahan lebih sedikit tetapi saya masih memiliki kesalahan yang sama.

Pemahaman saya adalah bahwa List.iter akan meneruskan setiap elemen dari daftar yang diteruskan sebagai argumen terakhir ke fungsi yang diteruskan ke List.iter. Jadi ini harus mengulangi elemen daftar saya yang saya teruskan dan satu-satunya jalan keluar adalah jika bidang node_name cocok dengan apa yang saya cari atau jika itu adalah daftar kosong.

Perbedaan utama yang saya lihat antara kedua implementasi saya adalah implementasi pertama terus berjalan hingga semua node dievaluasi dan implementasi kedua berlangsung hingga saya menemukan apa yang saya cari.

Setiap saran akan dihargai dan jika mungkin penjelasan singkat tentang kesalahan saya atau setidaknya soroti kesalahan saya (saya merasa seperti mengambil banyak konsep pemrograman fungsional tetapi masih ada beberapa yang luput dari perhatian saya) ).


person Jesse    schedule 13.04.2017    source sumber
comment
Jawaban Lhooq benar, tetapi saya juga ingin menunjukkan bahwa fungsi yang Anda cari sebenarnya hanya satu kalimat: let rec check name tree = tree.name = name || List.exists (check name) tree.branches (nama disingkat agar sesuai dengan komentar)   -  person Andreas Rossberg    schedule 14.04.2017
comment
@AndreasRossberg Terima kasih untuk itu, senang melihat lebih banyak cara idiomatis dalam melakukan sesuatu   -  person Jesse    schedule 15.04.2017


Jawaban (1)


Nah, di cabang terakhir, jika saya mengerti dengan baik, Anda ingin tahu apakah salah satu node di tail memiliki nama yang bagus. Kalau begitu, gunakan saja List.exists :

| head :: tail ->
         Format.printf "[%s] Branches: %i\n" in_tree.node_name 
            (List.length in_tree.node_branch);
         check_tree_for_name target_name head ||
         List.exists (check_tree_for_name target_name ) tail

Dan itu akan berfungsi sebagaimana mestinya (perhatikan || antara check_tree... dan List.exists .... Jika check_tree ... mengembalikan true, Anda tidak perlu memeriksa sisa daftar.


Penjelasan kecil :

Di OCaml, semua cabang pencocokan pola harus memiliki tipe yang sama. Di sini, cabang pertama Anda bertipe boolean karena Anda mengakhirinya dengan false dan cabang kedua bertipe unit karena Anda mengakhirinya dengan List.iter .... List.iter bertipe ('a -> unit) -> 'a list -> unit dan menerapkan argumen pertamanya ke semua elemen dalam daftar yang diberikan sebagai argumen kedua. List.exists bertipe ('a -> bool) -> 'a list -> bool dan mengembalikan true jika satu elemen dalam daftar yang diberikan sebagai argumen kedua memenuhi predikat yang diberikan sebagai argumen pertama dan false jika tidak ada elemen tersebut.

Demikian pula, if cond then e1 else e2 seperti pencocokan pola sehingga e1 dan e2 harus memiliki tipe yang sama sehingga upaya kedua Anda sama salahnya dengan upaya pertama. ;-)

person Lhooq    schedule 13.04.2017
comment
Selain itu, perlu dicatat bahwa List.exists adalah operator pintasan: operator ini akan kembali segera setelah elemen valid ditemukan. Jika Anda ingin melihatnya sendiri: List.exists (fun x -> print_int x; x = 3) [1;2;3;4;5];; - person Richard-Degenne; 14.04.2017
comment
Terima kasih untuk ini, bagi saya itu sudah jelas tetapi patut untuk disebutkan. ;-) - person Lhooq; 14.04.2017
comment
Yap, itu untuk dibaca OP dan yang lainnya sebagai catatan tambahan. :) - person Richard-Degenne; 14.04.2017
comment
Lhooq Terima kasih atas penjelasannya! Itu masuk akal. Saya berpikir bahwa aliran program akan masuk ke dalam fungsi yang dipanggil oleh List.iter dan kemudian keluar. Saya tidak berpikir itu akan kembali. Tapi sekarang saya juga melihat di dokumentasi Daftar bahwa ia mengembalikan unit. Dan terima kasih @RichouHunter atas klarifikasinya, itu membantu! - person Jesse; 15.04.2017