web-dev-qa-db-ja.com

Clojureでベクターの先頭に追加する慣用的な方法は何ですか?

リストの前に付けるのは簡単です:

user=> (conj '(:bar :baz) :foo)
(:foo :bar :baz)

ベクトルへの追加は簡単です:

user=> (conj [:bar :baz] :foo) 
[:bar :baz :foo]

ベクターを取得しながら、ベクターに(慣用的に)追加する方法を教えてください。ベクトルではなくseqを返すため、これは機能しません。

user=> (cons :foo [:bar :baz])     
(:foo :bar :baz)

これはいです(IMVHO):

user=> (apply vector (cons :foo [:bar :baz])) 
[:foo :bar :baz]

注:基本的には、追加して追加できるデータ構造が必要です。大きなリストに追加するとパフォーマンスが大幅に低下するため、ベクトルについて考えました。

54
0x89

ベクトルは、先頭に追加するようには設計されていません。 O(n) prepend:

user=> (into [:foo] [:bar :baz])
[:foo :bar :baz]

あなたが望むのは、おそらく finger tree です。

71
kotarak

私はこの質問が古いことを知っていますが、差分リストについては誰も何も言いませんでしたし、あなたは本当にあなたが追加して追加することができるものが本当に欲しいと言っているので、差分リストがあなたを助けるかもしれないように聞こえます。それらはClojureで人気があるようには見えませんが、実装が非常に簡単で、フィンガーツリーよりもはるかに複雑ではないため、今すぐ(さらにテストして)小さな差分リストライブラリを作成しました。これらはO(1)時間(プリペンドまたはアペンド)で連結されます。差分リストをリストに戻す変換はO(n)を要するはずです。たくさんの連結をします。もしあなたがたくさんの連結をしていないなら、リストに固執するだけですよね?:)

この小さなライブラリの関数は次のとおりです。

dl:差分リストは、実際には引数と独自のコンテンツを連結し、結果のリストを返す関数です。差分リストを作成するたびに、データ構造のように機能する小さな関数を作成しています。

dlempty:差分リストはその内容を引数に連結するだけなので、空の差分リストは恒等関数と同じものです。

ndl:差分リストの機能により、nilを呼び出すだけで差分リストを通常のリストに変換できるため、この関数は実際には必要ありません。ただの便宜のためです。

dlcons:リストの先頭に項目を構成します-完全に必要というわけではありませんが、構成は十分に一般的な操作であり、1行だけです(すべての関数と同様)。

dlappend:は、2つの差分リストを連結します。その定義が最も楽しいと思います-それをチェックしてください! :)

そして今、ここにその小さなライブラリがあります-O(1)データ構造を追加/追加する5つの1行関数。悪くない、え?ああ、Lambda Calculusの美しさ。 ..

(defn dl
  "Return a difference list for a list"
  [l]
  (fn [x] (concat l x)))

; Return an empty difference list
(def dlempty identity)

(defn undl
  "Return a list for a difference list (just call the difference list with nil)"
  [aDl]
  (aDl nil))

(defn dlcons
  "Cons an item onto a difference list"
  [item aDl]
  (fn [x] (cons item (aDl x))))

(defn dlappend
  "Append two difference lists"
  [dl1 dl2]
  (fn [x] (dl1 (dl2 x))))

あなたはこれで実際にそれを見ることができます:

(undl (dlappend (dl '(1 2 3)) (dl '(4 5 6))))

返されるもの:

(1 2 3 4 5 6)

これも同じものを返します:

((dl '(1 2 3)) '(4 5 6))

違いリストを楽しんでください!

更新

理解するのがより難しいかもしれないが、私はより良いと思ういくつかの定義があります:

(defn dl [& elements] (fn [x] (concat elements x)))
(defn dl-un [l] (l nil))
(defn dl-concat [& lists] (fn [x] ((apply comp lists) x)))

これにより、次のように言うことができます。

(dl-un (dl-concat (dl 1) (dl 2 3) (dl) (dl 4)))

どちらが返されます

(1 2 3 4)
17
Bill Burdick

ユーザーが指のツリーの答えの下にあるコメントで言ったように、RRBツリーを実装する clojure/core.rrb-vector libを使用できます。

RRB-TreesはClojureのPersistentVectorsに基づいて構築され、対数時間の連結とスライスを追加します。 ClojureScriptは、vector-of関数がないことを除いて、同じAPIでサポートされています。

このライブラリはそれに値すると思うので、私はこれを別の回答として投稿することにしました。 ClojureScriptをサポートし、 MichałMarczyk によって管理されています。これは、Clojureコミュニティ内でさまざまなデータ構造の実装に関する仕事でよく知られています。

2
Rafał Cieślak

準クォートを恐れないのであれば、このソリューションは実際にはかなりエレガントです(「エレガント」の定義によっては):

> `[~:foo ~@[:bar :baz]]

[:foo :bar :baz]

私は実際に実際のコードでときどきこれを使用します。宣言構文により、見やすくなりました。

1
drcode

便利な機能を使用することをお勧めします Tupeloライブラリに組み込まれています 。例えば:

(append [1 2] 3  )   ;=> [1 2 3  ]
(append [1 2] 3 4)   ;=> [1 2 3 4]

(prepend   3 [2 1])  ;=> [  3 2 1]
(prepend 4 3 [2 1])  ;=> [4 3 2 1]

比較すると、生のClojureでは、間違いを犯しやすいです。

; Add to the end
(concat [1 2] 3)    ;=> IllegalArgumentException
(cons   [1 2] 3)    ;=> IllegalArgumentException
(conj   [1 2] 3)    ;=> [1 2 3]
(conj   [1 2] 3 4)  ;=> [1 2 3 4]

; Add to the beginning
(conj     1 [2 3] ) ;=> ClassCastException
(concat   1 [2 3] ) ;=> IllegalArgumentException
(cons     1 [2 3] ) ;=> (1 2 3)
(cons   1 2 [3 4] ) ;=> ArityException
1
Alan Thompson