英和訓練

著作権は原文に属します

Learn Clojure - Hashed Collection ハッシュなコレクション

As described in the previous section, there are four key Clojure collection types: vectors, lists, sets, and maps. 前回の節で述べたように、Clojureには4つの重要なコレクション型がある。ベクタ、リスト、セット、マップである。Of those four collection types, sets and maps are hashed collections, designed for efficient lookup of elements. 4つの中でセットとマップとは、効率的な検索のために設計された、ハッシュを用いたコレクションである。

Sets セット

Sets are like mathematical sets - unordered and with no duplicates. セットは数学で言うセットつまり集合である。要素の間に順序はなく、同じ要素は重複しない。Sets are ideal for efficiently checking whether a collection contains an element, or to remove any arbitrary element. セットは、ある要素が含まれているか調べたり、ある要素を取り除く操作の効率において理想的である。

(def players #{"Alice", "Bob", "Kelly"})

Adding to a set セットへの追加

As with vectors and lists, conj is used to add elements. ベクタとリストと同じで、セットにもconjで要素を追加できる。

user=> (conj players "Fred")
#{"Alice" "Fred" "Bob" "Kelly"}

Removing from a set セットから要素を取り除く

The disj ("disjoin") function is used to remove one or more elements from a set. セットからいくつかの要素を取り除くためにはdisjoin(離す)という意味のdisjが使える。

user=> (disj players "Bob" "Sal")
#{"Alice" "Kelly"}

Checking containment 要素の存在の確認

user=> (contains? players "Kelly")
true

Sorted Sets 整列済みセット

Sorted sets are sorted according to a comparator function which can compare two elements. 整列済みセットは、2つの要素同士を比較する比較関数によって整列されているセットだ。By default, Clojure's compare function is used, which sorts in "natural" order for numbers, strings, etc. デフォルトでは、Clojurecompare関数が使われる。compare関数は、数字や文字列などを「自然な」順番で整列する。

user=> (conj (sorted-set) "Bravo" "Charlie" "Sigma" "Alpha")
#{"Alpha" "Bravo" "Charlie" "Sigma"}

A custom comparator can also be used with sorted-set-by. 関数sorted-set-byを使えば、任意の比較関数を与えて整列済みセットを作ることができる。

into

into is used for putting one collection into another. あるコレクションを別のコレクションと合わせるためにはinto関数が使われる。

user=> (def players #{"Alice" "Bob" "Kelly"})
user=> (def new-players ["Tim" "Sue" "Greg"])
user=> (into players new-players)
#{"Alice" "Greg" "Sue" "Bob" "Tim" "Kelly"}

into returns a collection of the same type as its first argument. intoは、与えられた第1引数と同じ型のコレクションを返す。

Maps マップ

Maps are commonly used for two purposes - to manage an association of keys to values and to represent domain application data. マップは主に2つの用途で広く使われる。1つは、キーから値への連想を管理するためである。もう1つは、ドメインのアプリケーションのデータ表現としてである。前者については他の言語ではしばしば、「辞書」あるいは「ハッシュマップ」という言葉で呼ばれる。

Creating a literal map リテラルによるマップの作成

Maps are represented as alternating keys and values surrounded by { and }. マップは、{}の間にキーと値とを交互に並べることで表される。

(def scores {"Fred"  1400
             "Bob"   1240
             "Angela" 1024})

When Clojure prints a map at the REPL, it will put ","s between each key/value pair. ClojureがREPLにマップを印字する時には、それぞれのキーと値のペアらの間に「,」が印字される。There are purely used for readability - commas are treated as whitespace in Clojure. これは純粋に可読性のためのものだ。カンマ文字はClojureにおいては空白文字として扱われる。Feel free to use them in case where they help you! 可読性が思われると思うどの場所でも自由にカンマを使うことができる。

;; same as the last one! 上のものと等しい
(def scores {"Fred" 1400, "Bob" 1240, "Angela" 1024})

Adding new key-value pairs キーと値のペアを追加する

New values are added to maps with the assoc (short for "associate") function: associate(関連づける)という意味の関数assocを使うことで、マップに新しい値を追加することができる。

user=> (assoc scores "Sally" 0)
{"Angela" 1024, "Bob" 1240, "Fred" 1400, "Sally" 0}

If the key used in assoc already exists, the value is replaced. assocに与えられたキーがすでにマップに存在していたら、かつて対応していた値は新しい値によって上書きされる。

user=> (assoc scores "Bob" 0)
{"Angela" 1024, "Bob" 0, "Fred" 1400}

Removing key-value pairs キーと値のペアを取り除く

The complementary operation for removing key-value pairs is dissoc ("dissociate"): assocと対になる、マップにキーと値のペアを追加する操作は、dissociate(関係をはずす)という意味のdissocだ。

user=> (dissoc scores "Bob")
{"Angela" 1024, "Fred" 1400}

Looking up by key キーによる検索

There are several ways to look up a value in a map. マップの中から値を取り出す方法はいくつかある。The most obvious is the function get: 最も分かりやすいのはget関数だ。

user=> (get scores "Angela")
1024

Wehn the map in question is being treated as a constant lookup table, its common to invoke the map itself, treating it as a function: 変更することがなく値を取り出すことだけにマップを使う場合は、マップそれ自体を関数として呼び出すことがよく行われる。

user=> (def directions {:north 0
                        :east 1
                        :south 2
                        :west 3})
#'user/directions

user=> (directions :north)
0

You should not directly invoke a map unless you can guarantee it will be non-nil: マップを関数として呼び出すことは、マップとするものの値が実際にnilではないことが確かな場合に限るべきだ。

user=> (def bad-lookup-map nil)
#'user/bad-lookup-map

user=> (bad-lookup-map :foo)
NullPointerException

Looking up with a default デフォルト値を用いた検索

If you want to do a lookup and fall back to a default value when the key is not found, specify the default as an extra parameter: あるキーでマップを探して、見つからなかった場合にはあるデフォルト値を返してほしい時には、追加の引数としてそのデフォルト値を与えればよい。

user=> (get scores "Sam" 0)
0
<200b>
user=> (directions :northwest -1)
-1

Using a default is also helpful to distinguish between a missing key and an existing key with a nil value. あるマップにおいて、あるキーに対応する値としてnilがある場合と、そのキー自体が見つからなかったのでnilが返ってきた場合とを区別したい時には、nilでない値をデフォルト値に指定するのが便利である。

Checking contains 含まれているか調べる

There are two other functions that are helpful in checking whether a map contains an entry. またもう2つ、マップにある要素が含まれているか調べるのに便利な関数がある。

user=> (contains? scores "Fred")
true

user=> (find scores "Fred")
["Fred" 1400]

The contains? function is a predicate for checking containment. 関数contains?は、要素が含まれていれば真を返す述語関数だ。The find function finds the key/value entry in a map, not just the value. find関数は、見つけた値を返すのではなく、キーと値の組を返す。

Keys or values キー全てと値全て

You can also get just the keys or just the values in a map: あるマップからそのキー全てだけや、値全てだけを得ることもできる。

user=> (keys scores)
("Fred" "Bob" "Angela")

user=> (vals scores)
(1400 1240 1024)

While maps are unordered, there is a guarantee that keys, vals, and other functions that walk in "sequence" order will always walk a particular map instance entries in the same order. マップの要素には順序はない。しかし、keysvalsなどの関数はシーケンスとしての順序で要素らを辿る。その場合には、あるマップのインスタンスについては、何度辿っても同じ順序で要素を辿ることが保証されている。

Building a map マップを作る

The zipmap function can be used to "zip" together two sequences (the keys and vals) into a map: zipmap関数を使うことで、2つのシーケンスそれぞれをキーらと値らとして閉じ合わせたマップを作ることができる。

user=> (def players #{"Alice" "Bob" "Kelly"})
#'user/players

user=> (zipmap players (repeat 0))
{"Kelly" 0, "Bob" 0, "Alice" 0}

There are a variety of other ways to build up a map using Clojure's sequence functions (which we have not yet discussed). マップを作り出すことのできるClojureのシーケンス関数は他にもたくさんある。(シーケンス関数についてはまだ説明していない。) Come back to these later! シーケンス関数を学んでから理解すればよいが、次のような方法もあることを紹介だけしておこう。

;; with map and into map関数とinto関数を用いた方法
(into {} (map (fn [player] [player 0]) players))

;; with reduce reduce関数による方法
(reduce (fn [m player]
          (assoc m player 0))
        {} ; initial value 初期値
        players)

Combining maps マップを組み合わせる

The merge function can be used to combine multiple maps into a single map: merge関数を使うと、複数のマップを合わせて1つのマップにできる。

user=> (def new-scores {"Angela" 300 "Jeff" 900})
#'user/new-scores

user=> (merge scores new-scores)
{"Fred" 1400, "Bob" 1240, "Jeff" 900, "Angela" 300}

We merged two maps here but you can pass more as well. ここでは2つのマップを統合したが、いくつでももっと多くを統合できる。

If both maps contain the same key, the rightmost one wins. 両方のマップに同じキーが存在したら、最も右のものが優先される。Alternately, you can use merge-with to supply a function to invoke when ther is a conflict: その代わりに、衝突が起きた場合に用いる関数を指定できるmerge-withを使うこともできる。

user=> (def new-scores {"Fred" 550 "Angela" 900 "Sam" 1000})
#'user/new-scores

user=> (merge-with + scores new-scores)
{"Sam" 1000, "Fred" 1950, "Bob" 1240, "Angela" 1924}

In the case of a confilict, the function is called on both values to get the new value. 衝突が起きた時には、一方ともう一方の値を関数が呼ばれてその返り値が結果となる。

Representing application domain information アプリケーションドメインの情報を表す

When we need to represent many domain information with the same set of fields known in advance, you can use a map with keyword keys. 既知のフィールドの組を持つ多量のドメイン情報を表したい時には、キーワードをキーとするマップを使うことができる。

(def person
  {:first-name "Kelly"
   :last-name "Keen"
   :age 32
   :occupation "Programmer"})

Field accessors フィールドのアクセサ

Since this is a map, the ways we've already discussed for looking up a value by key also work: これはマップであるから、キーによって値を取り出すすでに説明した方法は使える。

user=> (get person :occupation)
"Programmer"

user=> (person :occupation)
"Programmer"

But really, the most common way to get field values for this use is by invoking the keyword. しかし、この場合に実際に最も使われる方法は、キーワードを呼び出す方法だ。Just like with maps and sets, keywors are also functions. マップとセットと同じく、キーワードも関数である。When a keyword is invoked, it looks itself up in the associative data structure that it was passed. キーワードは、呼び出されると、引数与えられた連想的データ構造を、自分自身をキーとして検索する。

user=> (:occupation person)
"Programmer"

Keyword invocation also takes an optional default value: キーワードによる呼び出しは、追加の引数としてデフォルト値を取ることができる。

user=> (:favorite-color person "beige")
"beige"

Updating fields フィールドの更新

Since this is a map, we can just use assoc to add or modify fields: これはマップだから、assocを使ってフィールドを追加したり変更したりできる。

user=> (assoc person :occupation "Baker")
{:age 32, :last-name "Keen", :first-name "Kelly", :occupation "Baker"}

Removing a field フィールドを取り除く

Use dissoc to remove fields: フィールドを取り除くのはdissocだ。

user=> (dissoc person :age)
{:last-name "Keen", :first-name "Kelly", :occupation "Programmer"}

Nested entities ネストしたエンティティ

It is common to see entities nested within other entities: あるエンティティの中に別のエンティティが(ネストして)存在することは普通である。

(def company
  {:name "WidgetCo"
   :address {:street "123 Main St"
             :city "Springfield"
             :state "IL"}})

You can use get-in to access fields at any level inside nested entities: get-inを使うことで、ネストしたエンティティの任意の深さのフィールドにアクセスできる。

user=> (get-in company [:address :city])
"Springfield"

You can also use assoc-in or update-in to modify nested entities: また、ネストしたエンティティを変更するにはassoc-inupdate-inが使える。

user=> (assoc-in company [:address :street] "303 Broadway")
{:name "WidgetCo",
 :address
 {:state "IL",
  :city "Springfield",
  :street "303 Broadway"}}

Records レコード

An alternative to using maps is to create a "record". マップを使うのではなく「レコード」で代替することもできる。Records are designed specifically for this use case and generally have better performance. レコードはこの用途専門に設計されており、一般にパフォーマンスでまさる。In addition, they have a named "type" which can be used for polymorphic behavior (more on that later). さらに、レコードはそれぞれ特有の名前の「型」を持つため、多態な振る舞い(後述)を実現するのに使える。

Records are defined with the list of field names for record instances. レコードはレコードのインスタンスが持つフィールドの名前らのリストによって定義される。These will be treated as keyword keys in each record instance. それらフィールド名は、レコードの各インスタンスにおいて、キーワード型のキーとなる。

;; Define a record structure レコード構造を定義
(defrecord Person [first-name last-name age occupation])

;; Positional constructor - generated 引数の位置でフィールドを指定してインスタンスを生成する
(def kelly (->Person "Kelly" "Keen" 32 "Programmer"))

;; Map constructor - generated マップによってフィールド値を与えてインスタンス生成する
(def kelly (map->Person
             {:first-name "Kelly"
              :last-name "Keen"
              :age 32
              :occupation "Programmer"}))

Records are used almost exactly the same as maps, with the caveat that they cannot be invoked as a function like maps. レコードの操作はマップとほぼ同じだ。しかしマップと違い、レコードを関数として呼び出すことはできない。

user=> (:occupation kelly)
"Programmer"

原文: https://clojure.org/guides/learn/hashed_colls