cycle
Haskell関数のアナログを実装したいと思います。
リスト要素を明示的に渡すと、それは些細なことのように思えます:
let cycle a b c =
let rec l = a::b::c::l in
l
cycle 1 2 3
再帰リストを生成します1, 2, 3, 1...
しかし、別の通常のリストに基づいて再帰リストを生成するにはどうすればよいでしょうか?
let cycle lst = ...
使用法
cycle [1;2;3]
cycle
Haskell関数のアナログを実装したいと思います。
リスト要素を明示的に渡すと、それは些細なことのように思えます:
let cycle a b c =
let rec l = a::b::c::l in
l
cycle 1 2 3
再帰リストを生成します1, 2, 3, 1...
しかし、別の通常のリストに基づいて再帰リストを生成するにはどうすればよいでしょうか?
let cycle lst = ...
使用法
cycle [1;2;3]
ML のような熱心な言語では、ストリームを使用する必要があります。例えば
# let cycle = Stream.from (fun n -> Some (List.nth [1;2;3] (n mod 3)));;
val cycle : int Stream.t = <abstr>
# Stream.npeek 10 cycle;;
- : int list = [1; 2; 3; 1; 2; 3; 1; 2; 3; 1]
私の知る限り、OCaml は、言語の安全でない部分に踏み込みたくない限り、この種のコーディングには向いていません。
cycle
言語の安全な部分にこだわって (ただし、第 7 章の拡張機能を使用して)、長さ 3 までのリストに対して機能する (あまり印象的ではない) バージョンを次に示します。
let cycle = function
| [] -> []
| [x] -> let rec res = x :: res in res
| [x; y] -> let rec res = x :: q and q = y :: res in res
| [x; y; z] -> let rec res = x :: t and t = y :: v and v = z :: res in res
| _ -> failwith "list too long"
これを任意の長さに拡張する方法は簡単にわかりますが、任意の長さに拡張することはできません。
関数を使用したセッションは次のとおりです。
# #use "cyc.ml";;
val cycle : 'a list -> 'a list = <fun>
# cycle [1;2;3];;
- : int list =
[1; 2; 3; 1; 2; 3; 1; 2; 3; 1; 2; 3; 1; 2; 3; 1; 2; 3; 1; 2; 3; 1; 2; 3; 1;
2; 3; 1; 2; 3; 1; 2; 3; 1; 2; 3; 1; 2; 3; 1; 2; 3; 1; 2; 3; 1; 2; 3; 1; 2;
3; 1; 2; 3; 1; 2; 3; 1; 2; 3; 1; 2; 3; 1; 2; 3; 1; 2; 3; 1; 2; 3; 1; 2; 3;
1; 2; 3; 1; 2; 3; 1; 2; 3; 1; 2; 3; 1; 2; 3; 1; 2; 3; 1; 2; 3; 1; 2; 3; 1;
2; 3; 1; 2; 3; 1; 2; 3; 1; 2; 3; 1; 2; 3; 1; 2; 3; 1; 2; 3; 1; 2; 3; 1; 2;
3; 1; 2; 3; 1; 2; 3; 1; 2; 3; 1; 2; 3; 1; 2; 3; 1; 2; 3; 1; 2; 3; 1; 2; ...]
とにかく、これが私にできる最善のことです。お役に立てば幸いです。
このような再帰リストを作成する唯一の方法は、Obj
モジュールを使用することです。
let cycle lst = match lst with
| [] -> []
| _ ->
let rec get_last_cell = function
| [] -> assert false
| _::[] as last -> last
| _::tl -> (get_last_cell tl)
in
let new_lst = List.map (fun x -> x) lst in
let last_cell = get_last_cell new_lst in
Obj.set_field (Obj.repr last_cell) 1 (Obj.repr new_lst);
new_lst
let cycle lst = match lst with
| [] -> []
| hd::tl ->
let rec loop cell lst =
match lst with
| [] -> ()
| hd::tl ->
let new_cell = [hd] in
let new_cell_obj = Obj.repr new_cell in
let cell_obj = Obj.repr cell in
Obj.set_field new_cell_obj 1 (Obj.field cell_obj 1);
Obj.set_field cell_obj 1 new_cell_obj;
loop new_cell tl
in
let rec cyc_lst = hd::cyc_lst in
loop cyc_lst tl;
cyc_lst
アイデアは非常に簡単です。
cyc_lst
要素が 1 つだけの再帰リストを作成します。cyc_lst
ます。例
cycle [1;2]
再帰リストを作成しますcyc_lst
。メモリ内では自己再帰的なコンス セルとして表されます。
let rec cyc_lst = hd::cyc_lst .--------. | | | | | | +---+-|-+ `->| 1 | * | | +---+---+
new_cell
2 を唯一の要素として使用して作成する
let new_cell = [hd] セル new_cell .--------. | | | | | | +---+-|-+ +---+---+ `->| 1 | * | | | | 2 | X | +---+---+ +---+---+
new_cell
テール ポインターを最初のセルに設定する
Obj.set_field new_cell_obj 1 (Obj.field cell_obj 1) セル new_cell .--------.--------------. | | | | | | | | +---+-|-+ +---+-|-+ `->| 1 | * | | | | 2 | * | | +---+---+ +---+---+
テールcell
ポインタをnew_cell
Obj.set_field cell_obj 1 new_cell_obj セル new_cell -----------------------。 | | | | | | +---+---+ +---+-|-+ `->| 1 | *------>| 2 | * | | +---+---+ +---+---+
このようなリスト操作で GC が問題ないことを願っています。そうでない場合はお知らせください。
別の回答のようにストリーム、または遅延リストが必要です。
type 'a llist = LNil | LCons of 'a * 'a llist Lazy.t
let cycle = function
| [] -> invalid_arg "cycle: empty list"
| hd::tl ->
let rec result =
LCons (hd, lazy (aux tl))
and aux = function
| [] -> result
| x::xs -> LCons (x, lazy (aux xs)) in
result
そのように定義することもできます
# let cycle items =
let buf = ref [] in
let rec next i =
if !buf = [] then buf := items;
match !buf with
| h :: t -> (buf := t; Some h)
| [] -> None in
Stream.from next;;
val cycle : 'a list -> 'a Stream.t = <fun>
utop # let test = cycle [1; 2; 3];;
val test : int Stream.t = <abstr>
utop # Stream.npeek 10 test;;
- : int list = [1; 2; 3; 1; 2; 3; 1; 2; 3; 1]
これは次のとおりです。