web-dev-qa-db-ja.com

F#のcons演算子(::)

F#の::演算子は、常に要素をリストの先頭に追加します。リストに追加する演算子はありますか? @演算子を使用していると思います

[1; 2; 3] @ [4]

1つの要素を追加するよりも効率が悪くなります。

43
Max

他の人が言ったように、あまり意味がないので、そのような演算子はありません。効率が悪いと気づきやすいので、いいことだと思います。実際には、演算子は必要ありません-通常、同じものを書くためのより良い方法があります。

典型的なシナリオ:要素を最後に追加する必要があると考えることができる典型的なシナリオは非常に一般的であり、それを説明するのに役立つかもしれないと思います。

アキュムレータパラメータを使用して関数の末尾再帰バージョンを作成している場合、最後に要素を追加する必要があるようです。たとえば、リストのfilter関数の(非効率的な)実装は次のようになります。

let filter f l = 
  let rec filterUtil acc l =
    match l with 
    | [] -> acc
    | x::xs when f x -> filterUtil (acc @ [x]) xs
    | x::xs -> filterUtil acc xs
  filterUtil [] l

各ステップでは、1つの要素をアキュムレータ(結果として返される要素を格納する)に追加する必要があります。このコードは、accリストの最後に要素を追加する代わりに、::演算子を使用するように簡単に変更できます。

let filter f l = 
  let rec filterUtil acc l =
    match l with 
    | [] -> List.rev acc                        // (1)
    | x::xs when f x -> filterUtil (x::acc) xs  // (2)
    | x::xs -> filterUtil acc xs
  filterUtil [] l

(2)では、アキュムレータの前に要素を追加しています。関数が結果を返そうとしているときに、リストを逆にします(1)。これは、要素を1つずつ追加するよりもはるかに効率的です。

48
Tomas Petricek

F#のリストは、単一リンクで不変です。これは、前面へのconsingがO(1)(要素を作成して既存のリストを指すようにする)であるのに対し、背面へのconsingはO(N)(リスト全体が複製。既存の最終ポインタを変更することはできません。まったく新しいリストを作成する必要があります)。

「1つの要素を後ろに追加する」必要がある場合は、たとえば.

l @ [42]

それを行う方法ですが、これはコードのにおいです。

28
Brian

2つの標準リストを追加するコストは左側のリストの長さに比例です。特に、

xs @ [x]

xsに比例しますnotは一定のコストです。

一定時間の追加によるリストのような抽象化が必要な場合は、John Hughesの関数表現を使用できます。これをhlistと呼びます。私はOCaml構文を使用しようとしますが、これはF#に十分近いと思います。

type 'a hlist = 'a list -> 'a list   (* a John Hughes list *)
let empty : 'a hlist = let id xs = xs in id
let append xs ys = fun tail -> xs (ys tail)
let singleton x = fun tail -> x :: tail
let cons x xs = append (singleton x) xs
let snoc xs x = append xs (singleton x)
let to_list : 'a hlist -> 'a list = fun xs -> xs []

つまり、リストを「残りの要素」から「最終的なリスト」までの関数として機能的に表現するということです。これは、要素を確認する前にリスト全体を作成する場合に最適です。それ以外の場合は、追加の線形コストに対処するか、別のデータ構造を完全に使用する必要があります。

14
Norman Ramsey

@演算子[...]を使用する方が、1つの要素を追加するよりも効率が悪いと思います。

もしそうなら、それはごくわずかな違いになります。単一のアイテムを追加することと、リストを最後に連結することはどちらもO(n)操作です。実際のところ、@は行う必要がありますが、単一項目の追加関数ではできません。

5
sepp2k

たぶん、あなたは別のデータ構造を使いたいでしょう。 fsharpx にダブルエンドキュー(または短い "Deques")があります。それらについての詳細は http://jackfoxy.com/double-ended-queues-for-fsharp で読むことができます。

3
forki23

効率性(または欠如)は、リストを繰り返し処理して最終的な要素を見つけることに由来します。そのため、[4]を使用して新しいリストを宣言することは、最も重要なシナリオを除いてすべて無視できます。

2
Brian Agnew

リストの代わりに両端キューを使用してみてください。私は最近、4バージョンの両端キュー(岡崎のスペル)をFSharpx.Coreに追加しました(NuGetから入手できます。ソースコードは FSharpx.Core.Datastructures )。 dequeusの使用に関する私の記事を参照してください F#の両端キュー

F#チームにcons演算子::とアクティブパターン判別子をヘッド/テールシグネチャを持つ他のデータ構造で使用できるようにすることを提案しました。

0
Jack Fox