Skip to content

Latest commit

 

History

History
1542 lines (1209 loc) · 77.6 KB

ch03-1.md

File metadata and controls

1542 lines (1209 loc) · 77.6 KB

coll? sequential? seq? 간의 차이 참고

Collections and Data Structures

  • Maps, vectors, sets, lists 는 clojure 가 제공하는 기본 데이터 구조이다.각 형태는 아래와 같다.
'(a b :name 12.5) ;; list
['a 'b :name 12.5] ;; vector
{:name "Chas" :age 31} ;; map
#{1 2 3} ;; set
{Math/PI "~3.14"
 [:composite "key"] 42
 nil "nothing"} ;; another map
#{{:first-name "chas" :last-name "emerick"}
 {:first-name "brian" :last-name "carper"}
 {:first-name "christophe" :last-name "grand"}} ;; a set of maps

괄호 용어 정의

  • 이 데이터 구조 카테고리는 Ruby, Python과 유사하다. 그러나 Clojure 데이터 구조는 구별되는 특성이 있다.
  1. 그들은 가장 먼저 추상화의 관점에서 사용되는 세부사항 구현
  2. 그들은 불변과 영구적인 것에서 함수형 프로그래밍에서 필수적이다.

각각의 데이터 구조와 특성을 점진적으로 살펴볼 것이다. 그것의 데이터 구조와 당신의 Clojure 응용프로그램을 디자인 할 수 있다.

Abstractions over Implementations(구현을 통한 추상화)

  • 10개의 데이터 구조에서 10개의 기능보다 한 개의 데이터 구조에서 100개의 기능을 갖는게 더 낫다. (Alan J. Perlis in the foreword to Structure and Interpretation of Computer Programs)

Clojure의 입장은 하나의 추상화에서 작동하는 100가지 기능을 갖고 있어 더 낫다. 특정 구현으로 Clojure의 수집 추상화의 우위는 파이썬과 루비에 작업의 다형성과 자바 인터페이스의 사용을 병행하지만, Clojure는 더 강력한 효과를 얻을 수 있다.

단계를 설정하기 위해, 앞서 언급한 벡터 일부 작업을 살펴 보자:

(def v [1 2 3])
;= #'user/v
(conj v 4)
;= [1 2 3 4]
(conj v 4 5)
;= [1 2 3 4 5]
(seq v)
;= (1 2 3)

seq는 항상 연속적인 출력을 실행하고 conj는 새 값을 제공된 collection에 추가한다. 이것은 일반적인 설명이나, maps에서도 같은 동작을 한다.

(def m {:a 5 :b 6})
;= #'user/m
(conj m [:c 7])
;= {:a 5, :c 7, :b 6}
(seq m)
;= ([:a 5] [:b 6])

집합

(def s #{1 2 3})
;= #'user/s
(conj s 10)
;= #{1 2 3 10}
(conj s 3 4)
;= #{1 2 3 4}
(seq s)
;= (1 2 3)

리스트

(def lst '(1 2 3))
;= #'user/lst
(conj lst 0)
;= (0 1 2 3)
(conj lst 0 -1)
;= (-1 0 1 2 3)
(seq lst)
;= (1 2 3)

분명히 seq와 conj는 운영하는 컬렉션의 유형을 통해 다형성이다. conj는 벡터에 대한 값을 추가, 목록 앞에 값을 추가한다. 또는 list에 값을 추가한다. 집합에서는 이미 존재하지 않는 세트에 값을 추가한다. seq는 벡터를 통해 직관적인 연속보기를 제공하거나 설정하거나 목록 및 벡터 쌍으로 표현되는 map의 키 / 값 쌍, 이상 연속 보기를 산출하여 맵에 제공된다.

그것은 Clojure의 본질이며, 내장된 보조 기능의 상단의 근접한 API들이다. 사용자 관점에서, "핵심" 기능과 도움 기능을 구별하고, 아무것도 개발자 조기에 새로운 도움 기능을 전문으로하거나 인터페이스 및 유형은 주어진 작업에 지원하는 사이에 잘못된 선택이 되지 않도록 한다.

예를 들어, into는 seq와 conj의 상단에 구현되고 seq와 conj를 보조하여 어떤 값들에서 자동으로 작업하는 into를 의미한다.

(into v [4 5])
;= [1 2 3 4 5]
(into m [[:c 7] [:d 8]])
;= {:a 5, :c 7, :b 6, :d 8}
(into #{1 2} [2 3 4 5 3 3 2])
;= #{1 2 3 4 5}
(into [1] {:a 1 :b 2})  ①  
;= [1 [:a 1] [:b 2]]

① map의 seq가 key/value 쌍의 시퀀스이므로 conj하는 것은 그 쌍의 구조에 벡터에 유지된다.

대조적으로, 자바 map java.util의 프레임워크 내에서조차 컬렉션이 아니며, 파이썬 각 단절된 동작 자체 세트를 가지고 구체적인 데이터 구조에 의존한다. 루비는 목록 및 해시, 반복을 지원하는 .each 메서드를 제공하지만, Clojure는 명시적으로 특정 기능에 의존하는 기능 대신 통합 추상화(시퀀스, 프로토콜, 컬렉션, 인터페이스 등)를 권장한다.

Abstraction Dilemmas(추상화 딜레마)

작은 인터페이스(추상화의 특정 타입)를 갖는 자바에서 일반적이다. 그러나 동시에 편의 기능들을 필요로 한다. 이들은 인터페이스(구현을 더 어렵게 하는)에 추가되고 추상화된 클래스에서 구현되지 않은 핵심 메서드로 구현된다. 이것은 상속에 묶여진 재사용된 코드를 남긴다. 자바는 사용자가 구현된 두개의 인터페이스에 새로운 클래스를 개발하는 것을 필요로 할 때, 단일 상속을 지원한다. 자바는 존재하는 두 추상 클래스로부터 구현된 것을 재사용하지 못하므로 재구현 매서드를 선택해야 한다.

그러므로 자바에서 추상화의 주요 도구의 주된 딜레마가 있다. 많은 개발자들은 악마와 깊은 푸른 바다 사이에 남겨졌다. Scylla 와 Charybdis - 다양한 구현 유지보수와 최소한의 코드 재사용 사이에서 선택하거나 취약한 API를 사용해야할 수도 있다.

작고 넓게 지원되는 추상화는 충분히 강조되지 않는 것이 Clojure의 디자인 원리 중 하나이다. HTTP를 병렬로 보는 것은 쉬운데, 많은 Clojure의 데이터 구조들은 자바 컬렉션 API의 인터페이스만을 판독 전용 부분을 구현하는 것같이 다수의 상이한 추상화를 지원한다. 추상화는 반드시 도움이 되기 위해 전체 적합성을 필요로 하지 않는다.

Clojure 데이터 형에는 일곱가지 추상화가 있다.

  • Collection
  • Sequence
  • Associative
  • Indexed
  • Stack
  • Set
  • Sorted

다음 페이지에서는 각 추상화의 API를 정의하는 작업의 의미를 살펴볼 것이다. 추상화의 용어들에서 Clojure 데이터 구조를 어떻게 사용할지에 대해 배울 것이다.

Collection

Clojure의 모든 데이터 구조는 일반적으로 collection에 추상화된다. Collection은 핵심 collection 함수의 set과 함께 사용할 수 있는 값이다.

  • conj 는 collection으로 아이템 하나를 추가한다.
  • seq는 collection에서 하나의 시퀀스를 반환한다.
  • count는 collection에서 아이템의 갯수를 반환한다.
  • empty는 제공된 collection의 같은 타입의 빈 인스턴스를 반환한다.
  • =은 하나나 다른 또하나와 비교한 collection의 동일성을 결정한다.(동일함은 컬렉션의 주제를 넘어 미묘해질 수 있습니다)

이러한 기능은 모든에 운영되고 collection의 구체적인 유형과 관련하여 다형성이다. 또 다른 방법은, 각 동작은 각각의 데이터 구조 구현의 제약 일관된 의미를 제공했다.

우리는 이미 seq을 미리봤지만, 그것으로 Clojure의 다른 가장 광범위한 추상화, 순서의 관문으로 우리는, 89 페이지의 "시퀀스"에 대해 더 많은 것을 배울 수 있습니다.

마찬가지로, 우리는 conj 항목을 추가 벡터에 적용 보았다; map(키 / 값 연관을 추가); 및 set들, 주어진 값의 구성원을 보장한다. conj 중 하나는 보장하지만, 항상 효율적으로 대상 collection에 주어진 값을 추가 할 것입니다. 이것은 conj가 목록에 항목을 앞에 추가하는 구현을 함으로써 처음에 놀라운 효과에 이른다.

(conj '(1 2 3) 4)
;= (4 1 2 3)
(into '(1 2 3) [:a :b :c])
;= (:c :b :a 1 2 3)

다른 작업을 수행하면 목록, 당신이 큰 데이터 세트로 작업하는 경우 비용이 많이 드는 작업을 통과 필요합니다. 넓은 점은 conj의 특정 행동을 주제 콜렉션의 지역 특성에 의존한다는 것입니다.

empty. empty는 생소한 개념이다. 많은 경우에, 당신은 데이터 구조의 구체적인 유형은 사용자가 작업을하는지 알고 있어야합니다, 따라서 당신은 단순히 직접 문제의 구조를 만들 수 있습니다. empty는 그 패턴의 탈옥과 일반적으로 당신이 함수에 인수로 받은 같은 하나로서 주어진 인스턴스와 같은 유형의 컬렉션을, 함께 작업 할 수 있습니다. 예를 들어, 우리는 순차적 컬렉션 값 쌍을 교환하는 기능을 쓸 수 있다.

(defn swap-pairs
 [sequential]
 (into (empty sequential)
 (interleave
 (take-nth 2 (drop 1 sequential))
 (take-nth 2 sequential))))
(swap-pairs (apply list (range 10)))
;= (8 9 6 7 4 5 2 3 0 1)
(swap-pairs (apply vector (range 10)))
;= [1 0 3 2 5 4 7 6 9 8]

그 swap-pairs'의 리턴 타입은 인수와 동일하다는데 주목하라 : 리스트를 제공할 때 리스트, 벡터를 제공할 때 벡터. 우리의 호출자가 제공하는 것과 이 (conj와 seq에 의해 강화)으로의 다형성 (polymorphic) 덕분에, 우리는 우리가 같은 구체적인 유형이 될 것입니다 보장 할 수 있는 빈 데이터 구조를 얻을 수 있도록 empty의 의미입니다.(java.util.List와 같은 일반적이 타입을 수용하는 헬퍼 메소드를 비교 하지만 자신의 반환 형식 또는 그들도 똑같이 일반적인 뭔가를 반환 만 보증을 전문으로 해야합니다.)

이것은 순차 유형에서만 작동하지는 않습니다; 당신은 map에있는 모든 값을 통해 해당 기능을 매핑 할 수있는 기능을 고려한다. 당신이 제공하고 있는 map는 무엇 분류 ... 또는되어 있지 않은 경우? 문제 없습니다, 그냥 다른 제공된 map의 정렬을 보장을 유지 주어진 구체적인 유형이 경우,map의 새로운 인스턴스 일 empty 사용하지 :

(defn map-map
 [f m]
 (into (empty m)
 (for [[k v] m] ①
 [k (f v)])))

① for는 지능형 폼의 리스트이며 Python에서 지능형 리스트와 유사하다. 이것은 [k (f v)]벡터 쌍의 레이지 시퀀서를 만들고 map 인자 m에서의 엔트리로부터 key/value 쌍 [k v] 인자 분해이다.

(map-map inc (hash-map :z 5 :c 6 :a 0))
;= {:z 6, :a 1, :c 7}
(map-map inc (sorted-map :z 5 :c 6 :a 0))
;= {:a 1, :c 7, :z 6}

정렬된, 정렬되지 않은. 호출자는 그것을 위해 허용하는 반환에서 얻은 값의 타입을 결정하여 얻는다.

count. count는 당신이 의도하는 Collection에서 엔트리의 수를 지시한다.

(count [1 2 3])
;= 3
(count {:a 1 :b 2 :c 3})
;= 3
(count #{1 2 3})
;= 3
(count '(1 2 3))
;= 3

count는 (우리는 다음에 살펴보겠지만, 정의되지 않은 길이의) 시퀀스를 제외한 모든 collection에 효율적 연산을 보장한다. count는 String, maps, collections, arrays와 같은 Clojure collection 이 아닌 자바 타입에서 유용하게 동작한다.

Sequences

시퀀스 추상화는 값의 어떤 소스를 넘어 연속 값을 볼 수 있는 방법을 제공한다. 흔히 "seqs"라고 부르는 시퀀스는 컬렉션 추상화로 지원된 기본이외에 기능들의 몇가지에 관련된다.

  • seq는 그 인자를 넘어 시퀀스를 생성한다.
  • first, rest, next는 시퀀스들을 소비하는 방법을 제공한다.
  • lazy-seq는 표현식을 계산하는 결과인 lazy sequence를 생성한다.

타입의 집합은 연속적이다. - 즉, seq하고, 포함된 것들이 유효한 값이다.

  • 모든 Clojure collection 타입들
  • 모든 Java collection들(예. java.util.*)
  • 모든 Java maps
  • 모든 Strings을 포함한 java.lang.CharSequences
  • java.lang.Iterable을 구현하는 타입
  • Arrays
  • nil (i.e., null as returned from Java methods)
  • Clojure의 clojure.lang.Seqable 인터페이스를 구현하는 것들

종합적으로 과도한 것이 모든 것을 증명; 작은 부분 집합은 충분합니다.

(seq "Clojure")
;= (\C \l \o \j \u \r \e)
(seq {:a 5 :b 6})
;= ([:a 5] [:b 6])
(seq (java.util.ArrayList. (range 5)))
;= (0 1 2 3 4)
(seq (into-array ["Clojure" "Programming"]))
;= ("Clojure" "Programming")
(seq [])
;= nil
(seq nil)
;= nil

nil 의 seq또는 빈 컬렉션은 nil이다. 이것은 (not (empty? some-collection)) 와 동일한 조건을 필요로 하는 곳을 포함해 많은 경우에 편리합니다.

시퀀스 작업을 하는 많은 기능들은 그 인자들에서 암시적으로 seq를 호출합니다. 예를 들어, 우리는 그냥 map 또는 set와 함께 사용 seq 호출에서 이 String을 wrapping할 필요가 없습니다.

(map str "Clojure")
;= ("C" "l" "o" "j" "u" "r" "e")
(set "Programming")
;= #{\a \g \i \m \n \o \P \r}
당신이 다른 사람의 시퀀스 기능의 상단에 이를 구축하는 경우 자신의 기능을 무료로 이 동작을 얻을 것이다. 당신이 lazy-seq를 사용하는 경우, 그것은 당신의 책임은 seq가 편리한 특성을 유지하기 위해 당신의 인수에 seq을 호출하는 것입니다.

이송 및 처리 시퀀스는 여러 가지 방법으로 수행하고, Clojure의 표준 라이브러리는 clojure.core에서 순서를 조작하고 생성하는 기능의 많은 수십 포함 할 수있다. 그러나 가장 기본적인 first, rest, next는 아래와 같다:

(first "Clojure")
;= \C
(rest "Clojure")
;= (\l \o \j \u \r \e)
(next "Clojure")
;= (\l \o \j \u \r \e)

first와 rest는 아주 명확하다. 정의하고 그들의 "rest 인자들"로, Clojure에서 가변 기능을 사용하는 방법을 기억한다면 후자는 당신에게 익숙한 것 같습니다. 그러한 기능 아래에 놓여 나머지입니다. rest와 next의 차이는 있지만 그렇게 분명하지 않다 : 우리가 위에서 보는 바와 같이 그 결과, 대부분의 값에 대해 동일합니다. 그들은 단지 0 또는 1의 값을 포함 시퀀스의 처리에 차이가 있습니다 :

Example 3-1. rest versus next

(rest [1])
;= ()
(next [1])
;= nil
(rest nil)
;= ()
(next nil)
;= nil

당신이 볼 수 있듯이, 결과 시퀀스가 비어 있으면 다음이 nil를 반환하는 반면, 나머지는 항상 빈 시퀀스를 반환합니다. 즉, 이것은 항상 값에 대한 사실 일수 있습니다.

(= (next x)
 (seq (rest x)))

작은 차이는 하나이지만, 시퀀스 전체가 느리게 실현되는 것이 가능하 게한다. 우리는 약간의 지연 시퀀스 및 lazy-seq에 대해 알아 보겠습니다.

시퀀스들은 "seqs"(아마도 seq라 불리는 키 시퀀스 생산 함수의 자연적인 결과) 로 불릴 것이고, 우리는 지연 시퀀스와 lazy-seq의 작은 차이를 배울 것이다.

Sequences are not iterators(반복자가 아닌 시퀀스)

다음과 같은 코드가 있다.

(doseq [x (range 3)]
 (println x))
; 0
; 1
; 2

그것은 여기에 doseq 폼 range에서 반환 된 연속 수집을 통해 걷고 일부 반복자에서 X 값을 도출하고 있다고 생각하는 것이 합리적 일 수 있습니다. 이 X는 (range 3)의 seq에서 연속 값으로 바인딩되는 사실이지만, 그 seq는 Clojure의의 다른 같은 불변의 지속적인 모음입니다.

(let [r (range 3)
 rst (rest r)]
 (prn (map str rst))
 (prn (map #(+ 100 %) r))
 (prn (conj r -1) (conj rst 42)))
; ("1" "2")
; (100 101 102)
; (-1 0 1 2) (42 1 2)

"유도된"첫 seq는 "부모"seq 연구가하는 것처럼 작동하고, 모두 시퀀스 및 모음에 대한 사용할 수있는 기능의 폭을 사용하여 별도에 작동 할 수 있습니다. 특히, 그것 또는 그것의 소스에 영향을 주지 않고 seq 위에 기능을 매핑 할 수있다 값, 그 "자손"또는 부모의. 이러한 특성 중에 다시 소비 될 수 없는, 일단 소비 안전하게 체크 포인트 다른 반복자 위한 기초로서 이용 될 수없는 상태 mutably 반복자 및 발전기에 의해 공유되지 않는다.(뮤터블하지 않은 seq에 대한 설명)

Sequences are not lists(리스트가 아닌 시퀀스)

처음에는, seq는 목록과 유사하다 : 시퀀스는 비어 있거나 헤드 값과 꼬리가 있는 자체 시퀀스입니다. 또한,리스트는 자신의 시퀀스이다. 그러나, 그들은 몇 가지 중요한면에서 상당히 다르다.

시퀀스의 길이를 얻기 비용이 발생 시퀀스의 내용은 느리게 계산하는 실제로 관련된 값이 액세스되는 경우에만 실현 될 수있다. 지연 시퀀스에 대한 값을 생산하고 계산은, 그 값의 무제한 진행을 생산하기 위해 선택 따라서 이 가능한 시퀀스가 무한하기 때문에 셀 수 없도록 만들어지는게 가능.

반면,리스트는 그 길이를 추적, 그래서 하나의 수를 점점 저렴, 일정 시간 작업입니다. Seqs들이 느리게 제조 할 수 있기 때문에, 동일한 보장을 제공 할 수 없으며 잠재적으로 무한한 수 있다. 따라서, 서열의 개수를 획득 할 수있는 유일한 방법은 그 전체를 강제로 통과된다. 카운트가 완전히 실현 목록 대 지연 시퀀스에 적용 할 때이 효과를 볼 수 있습니다.

(let [s (range 1e6)]
 (time (count s)))
; "Elapsed time: 147.661 msecs"
;= 1000000
(let [s (apply list (range 1e6))]
 (time (count s)))
; "Elapsed time: 0.03 msecs"
;= 1000000

range는 필요한 경우에만 생성되는 숫자의 지연 시퀀스를 반환합니다. 지연 시퀀스를 카운트하는 서열의 모든 값들을 계산하기 위해 생성되어야하기 때문에 완전히 실현되도록 한 방법이다. 여기에, 우리는 우리가 타이밍있는 활동의 범위 내에서 그 실현을 포함하고 있습니다.

리스트는 항상 자신의 크기를 추적, 그래서 하나의 수를 얻는 것은 항상 즉시 반환한다.

Creating seqs(시퀀스 만들기)

당신은 우리가 명시 적으로 지금까지 어떤 seqs을 만들지 않은 한 것을 알 수 있습니다. seqs 다른 컬렉션을 통해 단지 순차적 전망임을 감안할 때,이 말이; 일반적으로 시퀀스를 수집하여, 명시적 seq을 통해 또는 암시적으로 인수(들)에 seq을 호출 (map과 같은) 다른 함수를 통해 생성된다. 그러나 시퀀스를 만드는 두 가지 방법이 있습니다: cons와 list이다.

cons는 두 개의 인수를 받아 새로운 seq의 해드로 제공되는 값과 또 다른 collection에 꼬리로 제공될 seq이다.

(cons 0 (range 1 5))
;= (0 1 2 3 4)

cons를 컬렉션의 구체적인 타입에 관계없이 꼬리 collection의 "앞에 추가"의 시퀀스로 생각할 수 있다, 그러므로 conj와 구별된다:

(cons :a [:b :c :d])
;= (:a :b :c :d)

리스트는 시퀀스 뒤에 헤드 값의 임의의 개수와 seqs 제조 단지 헬퍼이다. 그래서,이 두 표현식은 동일합니다.

(cons 0 (cons 1 (cons 2 (cons 3 (range 4 10)))))
;= (0 1 2 3 4 5 6 7 8 9)
(list* 0 1 2 3 (range 4 10))
;= (0 1 2 3 4 5 6 7 8 9)

cons와 list 함수들은 매크로를 쓸 때 일반적으로 많이 사용된다 - seqs와 list는 동일하다. 그리고 list나 seq로 값을 앞에 붙이는게 필요하면-레이지 시퀀스의 다음 단계에 붙일 때 다음과 같이 확인 한다.

Lazy seqs(지연 seqs)

값은 소비자가 액세스를 시도 할 때 필요에 따라 수행되는 연산의 결과로서 생성되는 경우 일련의 내용이 느리게 평가하는 것이 가능하다. 이러한 각각의 값은 항상 한 번만 계산됩니다.지연 시퀀스를 액세스하는 프로세스를 실현이라고;지연 시퀀스의 모든 값이 계산 된 경우, 이는 시퀀스가 완전히 실현되었다고 말한다.

당신은 lazy-seq로 연속적인 값을 계산하는 어떠한 표현도 수용하는 매크로인 지연 시퀀스를 만들수 있다. 작은 예가 있다.

(lazy-seq [1 2 3])
;= (1 2 3)

즉, 여기에 지연 시퀀스로 시작하는 완전한 형태의 데이터 구조에서 그 값을 "실현"한다하는 한, 매우 흥미로운 아니다. 더 흥미로운 건 게으르게 임의의 정수의 시퀀스를 생성하는 순서는 다음과 같습니다.

(defn random-ints
 "Returns a lazy seq of random integers in the range [0,limit)."
 [limit]

 (lazy-seq
 (cons (rand-int limit)  ①
 (random-ints limit))))  ②
(take 10 (random-ints 50))
;= (32 37 8 2 22 41 19 27 34 27)

① 우리는 (rand-int limit) 의 해드에 의해 정의된 지연 seq를 리턴한다.

② 그리고 이 같은 random-ints 함수를 재귀 호출에 의해 생성된 지연 시퀀스인 꼬리이다.

시퀀스를 인쇄하는 것은 이제 이것을 증명하자 realized. 것으로 그 내용을 강제하기 때문에 -이 느리게 만 여기에 그 전체가 도시있어 이것의 결과는 매우 특별한 보이지 않을 수도 있지만, seq 자릿수 시퀀스가 생성되고, 랜덤 정수로부터 반환 랜덤 정수를 조금 수정하는 것은 무엇인가에 값이 실현 될 때마다 인쇄

(defn random-ints
 [limit]
 (lazy-seq
 (println "realizing random number")  ①
 (cons (rand-int limit)
 (random-ints limit))))
(def rands (take 10 (random-ints 50))) ②
;= #'user/rands
(first rands)                                   ③
; realizing random number
;= 39
(nth rands 3)
; realizing random number
; realizing random number
; realizing random number
;= 44
(count rands)                                 ④
; realizing random number
; realizing random number
; realizing random number
; realizing random number
; realizing random number
; realizing random number
;= 10
(count rands)                                  ⑤
;= 10

① 각 시간 지연 식 시퀀스가 평가된다 (구체적인 값 cons의 선두를 찾고 의해 생성 될 때마다 발생한다), 우리는 메시지를 출력합니다.

② 우리는 출력되지 않으므로 지연 시퀀스를 정의하고, force로 전체적인 평가를 한다. 출력되지 않는 것을 알 수 있습니다; 시퀀스는 전적으로 지연이며, 아직 값을 생성하지 않았습니다.

③ 그 가치의 생산은 지연 시퀀스 형태로 정의 된 어떤 계산이나 논리에 기초하여 force를 통해 해드 값에 대한 시퀀스를 요구. 우리는 임의의 숫자와 예상대로 대응하는 출력 메시지가 표시됩니다.

④ 우리가 지연 seq의 뒷부분에 값을 요청하는 경우, 모든 이전의 값은 반드시 실현된다 여기에, 우리는 카운트가 그것의 크기를 확인할 수 있도록 전체 시퀀스가 실현 될 force를 알 수있다.

⑤ (또는, 유사하게, seq의 nth 값을 요구한다면) 다시 계산을 요구하는 각 값을 재 계산이 필요하지 않습니다. 실현되면, 지연 seqs의 값이 유지됩니다.

우리가 처음 var에 지연 시퀀스를 정의 할 때, 그 내용은 단순히 존재하지 않는다.

[그림]

우리는 첫번째 값을 액세스하려고 시도하면, 그 값의 결과는 반영되고 반복 될 필요가 없이 생성됨 :

[그림]

cons 와 list* :머리는 구체적인 값이지만, 꼬리는 지연-seq은 우리가 대응하는 값에 액세스하여 호출되지 않습니다가 제공되는 표현에서 생성하는 기능에 서스펜드된 계산에 의해 전적으로 정의된다. 이러한 기능들이 최종 인수로 제공되는 (잠재적으로 지연) 시퀀스의 평가를 강요하지 않습니다. 이것은 이러한 기능을하게 전화 또는 계산을 일시 중단 지연-SEQ 다음 일반적인 패턴은 하나 이상의 구체적인 시퀀스 값과 cons 나 list* 호출의 결과를 반환하는 것입니다 지연 seqs를 구축하는 핵심 도우미, 그 지연 시퀀스의 나머지를 생성합니다.

random-int는 실제로 당신이 함께 표준 라이브러리 Clojure의 함수 몇 가지를 구성에서 바로 얻을 수 있습니다 기능의 매우 가난한, 지나치게 복잡한 구현 : 지연 seq를 생성하는 반복 랜덤 정수가 이미 사용 rand-int를, 그 각 값에 대해 주어진 함수를 호출하여 가져온 값을 포함

(repeatedly 10 (partial rand-int 50))
;= (47 19 26 14 18 37 44 13 41 38)

그것은 lazy-seq 에 제공되는 표현 그냥 단순히 데이터의 작은 비트에 임의의 숫자 또는 작업 같은 수치 트릭에 한정 아무것도 -대해 할 수있는 reemphasizing 가치가있다. 시퀀스에서 값에 적합한 모든 순수 기능 구현 계산은 공정한 게임이다.

반복의 단일 인수에 임의의 숫자의 무한 지연 시퀀스를 반환합니다. 그것은 Clojure의 무한 지연 시퀀스 작업하기 드문 일이 아니다. 투명하게 (잠재적으로 무한) 지연 시퀀스를 다룰 수 있는 Clojure에서의 표준 라이브러리와 Clojure의 커뮤니티 라이브러리간의 두 기능을 고려할 바디가 있다.

편리하게, map, for, filter, drop 같은 표준 라이브러리 등의 핵심 시퀀스 처리 기능을 모두 가지고 가고, 지연 시퀀스 복귀를 삭제하고 기본 seqs의 게으름에 영향을주지 않고 필요에 따라 적층 될 수있다. 이러한 시설 감안할 때, 그것은 file-seq과 마찬가지로, 큐의 데이터 지연 처리로 일반 사물에서, 값의 처리 순서 등 많은 문제를 특성화하고 다양한 소스에서 데이터의 지연 소비 및 변환 검색을 병렬화하는 것은 매우 일반적입니다 line-seq 및 xml-seq.

각 값은 일부 I / O 또는 생성하는 계산의 상당한 양을 요구하면 지연 시퀀스에 대한 예시의 실현을 강제하는 정도를 최소화하는 것이 매우 조심해야 할 것이다 경우가있다. 다음과 나머지의 차이가 중요한 곳이다. 다음 항상 nil이 대신 빈 시퀀스를 반환하는 예 3-1에서 기억 하는가? 이 비어 있지 않은 꼬리 SEQ에 대한 다음 확인하기 때문에 만 가능합니다; 이 검사는 반드시 그 비어 있지 테일 SEQ의 머리의 전위를 강제로 실현 :

(def x (next (random-ints 50)))
; realizing random number
; realizing random number

반면에, 나머지는 "맹목적으로"하여 피하는, 주어진 시퀀스의 꼬리를 반환합니다. 해드를 실현하기 때문에 지연이 극대화 된다:

(def x (rest (random-ints 50)))
; realizing random number

순차적인 인자분해는 항상 next를 사용하고, rest를 사용하지 않는다. 따라서, 지연 seq를 인자 분해 하는 것은 항상 꼬리의 해드 값을 나타낸다:

(let [[x & rest] (random-ints 50)])
; realizing random number
; realizing random number
;= nil

만약 지연 시퀀스의 완전한 구현을 강제 할 때, 다른 한편으로는, 시간이있다. 당신은 당신이 순서의 각 값이 생성 될 때의 배치 내용을 원하는 경우 시퀀스의 내용을 유지, 또는 dorun을 사용하는 경우 이러한 경우에, 당신은 doall를 사용한다 :

(dorun (take 5 (random-ints 50)))
; realizing random number
; realizing random number
; realizing random number
; realizing random number
; realizing random number
;= nil

아니 낭비처럼 보일 수있는 지연 시퀀스의 내용을 유지하지만, 지연 SEQ의 계산은 측면 초래하는 경우 이것은 매우 유용 할 수 있습니다, 당신은 그 부수 작용이 발생하는 것을 보장에만 관심이 있습니다. 예를 들어, 파일의 지연 시퀀스 (file-seq을 통해)를 가지고, 당신은 하나 또는 여러 기능이 그 지연 순서를 통해 매핑으로 매우 편리 것을 특징, 그들 작업의 일부 설정을 수행해야합니다. 이어서, dorun은 실제로 그러한 연산을 적용하지만, 불필요하게 수행된 기능에 관련된 함수의 값을 리턴하지 않는다.

일반적으로, seqs에서 동작 기능에 대한 문서들은 지연 seqs을 생산하거나 force에 대한 명시적입니다.

(doc iterate)
; -------------------------
; clojure.core/iterate
; ([f x])
;   Returns a lazy sequence of x, (f x), (f (f x)) etc.
;   f must be free of side-effects
(doc reverse)
; -------------------------
; clojure.core/reverse
; ([coll])
;   Returns a seq of the items in coll in reverse order. Not lazy.

분명히 iterate는 지연이지만, reverse는 아니다. iterate에 제공되는 기능은 "부수작용이 없어야한다"고 권고하지만, 약간의 설명이 있다.

코드 정의 지연 시퀀스 부수작용을 최소화한다. 우리는 이미 76 페이지의 "순수 함수"에서 순수한 함수의 많은 장점을 알게되지만 그 특성-부수 작용 코드의 함정-은 지연 시퀀스의 맥락에서 강조된다.

그들이 액세스 할 때 지연 seqs의 값이 실현되고가 정의하지 않을 경우 때문에 그 값의 생산과 관련된 부수작용이 일어날 경우, 경우에 그들이 이제까지 일어날 때 추적을 잃고 매우 간단합니다! 예를 들어, 지연 SEQ이 로그 이벤트가 급격하게 통과시킬 수 있다. 소모되는 동안 로그 레벨 변화, 로그 수준을 지연 SEQ이 정의 된 경우 이러한 메시지 스켈치하도록 설정되어 있더라도 .

설상가상으로, 약간의 지연 시퀀스의 평가는 따라서 상품의 실현의 소비되고 일치 반대로 버스트들에서 발생하는 부작용을 일으키는 일부 양만큼 앞으로 각각의 값의 소비 실행만한 성능 최적화으로 일괄 처리 될 수 있다.

이 이야기의 교훈은 당신이 일반적으로 실행 흐름을 제어하는 시퀀스의 평가에 의존하지해야한다는 것입니다. 지연 평가가 보급입니다 Clojure에서의 게으름은 다른 언어에서와 동일한 목적을 제공하지 않습니다. Clojure의에서는 게으름 그렇지 전적으로 "간절히"평가되는 언어와 그 데이터 구조의 나머지 부분과, 시퀀스로 제한된다. 지연 시퀀스는 Clojure의 투명 메모리에 맞지 않는과 더 균일 한, 선언, 파이프 라인 방식으로 알고리즘을 표현하는 큰 데이터 세트를 처리 할 수 있도록; 이러한 맥락에서, seq는 collection으로서 연산 임시 가능 매체로 간주 될 수있다.

하나 이상의 데이터 소스를 제공, 당신은 그것에서 그것을 처리 시퀀스를 추출하고,보다 적절한 데이터 구조에 다시 전원을 켜십시오 : 당신은 종종 Clojure의 코드에서이 패턴을 확인할 수 있습니다. 이 패턴은 이 같은 간단한 코드에서 발견 할 수있다 :

(apply str (remove (set "aeiouy")
             "vowels are useless! or maybe not..."))
;= "vwls r slss! r mb nt..."

여기에서 우리는 암시 적으로 문자의 순서로 설정되어 데이터 소스, "vowels are useless! or maybe not..."문자열을 가지고있다. 모든 모음은, String으로 다시 생성 순서를 집계하기 전에 제거됩니다.

Head retention(해드 유지)

항목을 한 번 계산되지만, 여전히 순서에 의해 유지되어 자주 Clojure의 신규 타입에 의해 간과 한 사실은 지연 시퀀스가 지속된다는 것이다. 이는 당신이 순서에 대한 참조를 유지, 당신은 가비지 수집에서 해당 항목을 방지합니다 것을 의미한다. 고장이 타입의 head 유지라고 (또는, head에 개최)와 시퀀스의 실현 부분이 너무 커질 경우 잠재적으로 메모리 부족 오류가 발생, 성능에 영향을 VM에 부하를 줄 수 있습니다.

split-with는 술어로 seq 가능한 값을 주어진 함수이며 두 개의 지연 seqs의 벡터를 반환한다; 조건을 만족하지 않는 첫 번째 항목에서 시작하는 술어와 접미사를 만족하는 접두사이다.

(split-with neg? (range -5 5))
;= [(-5 -4 -3 -2 -1) (0 1 2 3 4)]

접두사가 매우 작은 될 것입니다 것을 알고 있지만, 접미사가 매우 큰 곳을 사용하는 경우를 생각해 보자. 접두사에 대한 참조를 유지하는 경우, 순서의 모든 값은 접미사의 당신의 처리가 지연 경우에도 유지됩니다. 해당 시퀀스가 매우 큰 경우,이 헤드 유지율은 메모리 부족 오류가 발생하는지 확인한다 :

(let [[t d] (split-with #(< % 12) (range 1e8))]
  [(count d) (count t)])
;= #<OutOfMemoryError java.lang.OutOfMemoryError: Java heap space>

평가 순서를 반대로하는 문제를 해결한다 :

(let [[t d] (split-with #(< % 12) (range 1e8))]
  [(count t) (count d)])
;= [12 99999988]

t의 마지막 기준은 d의 처리 이전에 발생하기 때문에, range 시퀀스의 헤드에 대한 언급은 유지되지 않고, 어떠한 메모리 문제가 발생하지 않는다.

그들은 지연 시퀀스의 완전한 실현을 강제하기 때문에 map 이나 set, =, count에 삽입은 "head retention"를 일으키는 주요 원인이됩니다.

Associative(연관)

연관 추상화는 어떤 방법으로 키와 값을 연결 데이터 구조에 의해 공유됩니다. 그것은 4 개의 작업에 의해 정의된다 :

  • assoc, 지정된 컬렉션 내의 키와 값 사이에 새로운 연결을 설정
  • dissoc, 컬렉션에서 지정된 키의 연결을 삭제
  • get, 어느 컬렉션에서 특정 키 값을 검색
  • contaion?, 컬렉션에 지정된 키와 관련된 값이 있는 경우에만 true를 돌려 술어이다.

정규 연관 데이터 구조는 또한 Clojure에 의해 제공되는 가장 다양한 데이터 구조로 발생 된 map이다. 당신은 곧 당신의 가장 친한 친구로 map을 찾을 수 있습니다.

우리가 이미 본 바와 같이, maps는 conj와 seq의 핵심 수집 기능을 사용 항목 (key / value key)의 집합으로 볼 수 있습니다. 하지만 연관 기능이 더 자연스러운 적합입니다.

(def m {:a 1, :b 2, :c 3})
;= #'user/m
(get m :b)
;= 2
(get m :d)
;= nil
(get m :d "not-found")
;= "not-found"
(assoc m :d 4)
;= {:a 1, :b 2, :c 3, :d 4}
(dissoc m :b)
;= {:a 1, :c 3}

편리하게, 당신은 또한 지정된 map의 여러 항목에 영향을 assoc과 dissoc을 사용할 수 있습니다 :

(assoc m
  :x 4
  :y 5
  :z 6)
;= {:z 6, :y 5, :x 4, :a 1, :c 3, :b 2}
(dissoc m :a :c)
;= {:b 2}

그들은 maps을 더 자주 사용하지만, get과 assoc도 벡터에 의해서도 지원됩니다. 그것은 처음에 어긋 될 수도 있지만, maps와 벡터는 인덱스와 연관 값을 벡터 모두 연관 모음입니다 :

(def v [1 2 3])
;= #'user/v
(get v 1)                ①
;= 2
(get v 10)
;= nil
(get v 10 "not-found")
;= "not-found"
(assoc v
  1 4
  0 -12
  2 :p)                  ②
;= [-12 4 :p]

① v 는 인덱스 2와 인덱스 2를 "연결"한다.

② 당신은 assoc을 사용하여 벡터의 인덱스의 값을 업데이트 할 수 있습니다; 또, 연관 추상화의 의도에 따라, 이 assoc 호출이 map에서 운영하는 이전 목록에서 여러 항목 assoc 구조적으로 동일합니다. 그러나 데이터 구조 의존적 효과를 갖는다.

접속사가 벡터에 추가 것처럼 그 값이 효율적으로 추가 할 수있는 경우, 당신은 또한에 새 값의 인덱스가 될 것입니다. 무엇을 알 필요가 경고 벡터-에 추가 할 assoc을 사용할 수 있기 때문에 :

(assoc v 3 10)
;= [1 2 3 10]

마지막으로, "키"가 존재하는 경우 get은 sets에서 동작하여 리턴한다:

(get #{1 2 3} 2)
;= 2
(get #{1 2 3} 4)
;= nil
(get #{1 2 3} 4 "not-found")
;= "not-found"

그 자체로 set에는 key / value 의미가 없지만 set에서 작동이 있다는 것을 의미 할 때,의 값을 반환받을 세트 "연관"자신과의 값. 이 이상하게 보일 수 있지만, 조건의 시험과 일반적인 사용과 일치 남아있는 동안은, 세트 get의 의미를 충족할 수 있습니다 :

(when (get #{1 2 3} 2)
  (println "it contains `2`!"))
; it contains `2`!
Clojure의 maps와 sets의 의미 자체 get으로 사용할 수 있는 이상으로, 아주 직접적으로 get을 지원한다. 참조 컬렉션 조회가 get을 건드리지 않고, 간결하게 할 수 있는 방법은 111 페이지에 Collections는 함수이다.

**contains?**은 지정된 키가있는 경우에만 연관 수집 true를 반환하는 서술어이다.

(contains? [1 2 3] 0)
;= true
(contains? {:a 5 :b 6} :b)
;= true
(contains? {:a 5 :b 6} 42)
;= false
(contains? #{1 2 3} 1)
;= true
 contains?는 Clojure의 프로그래머가 초기 벡터 [0 1 2 3] 특정 수치를 포함하는 경우가 판별 사용하는 것이 적절할 것으로, 즉, 항상 컬렉션에서 값의 존재 여부를 검색하여 일반적인 실수 값을 판별한다. 이 오해는 매우 혼란 결과로 이어질 수 있다:

 (contains? [1 2 3] 3)
;= false
(contains? [1 2 3] 2)
;= true
(contains? [1 2 3] 0)
;= true

물론 함유하기 때문에, 상기 결과는 정확? 단지 어떤 매핑이 제공 키 -이 경우, 인덱스 3, 2, 0 존재하는지 확인하고 있습니다.
컬렉션의 특정 값의 존재를 확인하기 위해, 113 페이지의 "컬렉션과 키와 고차원 함수"에 설명 된 일부를 사용하는게 일반적이다.

get과 contains? 은 두드러지게 쓸데가 많다 : 그들은 vector,maps,set, Java maps, Strings, Java arrays에 효과적으로 동작한다.

(get "Clojure" 3)
;= \j
(contains? (java.util.HashMap.) "not-there")
;= false
(get (into-array [1 2 3]) 0)
;= 1

Beware of nil values(nil 에 주의)

제공된 디폴트 값이 없고 주어진 키에 대한 항목이 없다면 get은 nil을 반환한다. 그러나 nil이 완벽히 유효한 값이고, 주어진 키와 연관 될 때 분명한 값을 반환한다 :

(get {:ethel nil} :lucy)
;= nil
(get {:ethel nil} :ethel)
;= nil

어떻게 nil과 연관된 키 및 존재하지 않는 키 간의 차이를 구별 할 수 있는가?

우리는 contains?을 사용할 수 있지만 우리는 두 개의 맵을 조회한다면 모든 시간에 일을 할 것이다. 그래서 contains? 항목이 지정된 키 존재하면 결정하고, 다음 동일한 것을 검색하고 값을 얻는다.

우리는 현명하고 특별한 기본 값을 사용해 get이 기본 값을 리턴하도록 호출하지 않는다면 key는 찾을 수 없다고 가정한다. 그러나 그런 스킴은 심각한 단점이 있다: 특별한 기본값은 정상적인 키로서맵로 누출하고, 하나를 사용하는 것은 특별한 디폴트 값은 항상 논리적 사실 때문에 GET 조회의 결과도, 조건문 멋지게 재생되지 않음을 보장하는 것이 종종 너무 쉽게 때 지정된 키는 존재하지 않는다.

대답은 찾기를 사용하는 것입니다. 대신에 관련된 값을 반환, 그것은 전체 항목을 반환하는 것을 제외하고 얻을 매우 같은 작품을 발견; 찾을 수없는 경우 또는, 닐.

(find {:ethel nil} :lucy)
;= nil
(find {:ethel nil} :ethel)
;= [:ethel nil]

추가적으로 find는 인자 분해와 if-let(이나 when-let)같은 조건 문으로 매우 잘 동작한다.

(if-let [e (find {:a 5 :b 6} :a)]
  (format "found %s => %s" (key e) (val e))
  "not found")
;= "found :a => 5"
(if-let [[k v] (find {:a 5 :b 6} :a)]
  (format "found %s => %s" k v)
  "not found")
;= "found :a => 5"

물론, contains?로 주어진 key의 존재를 위해서만 확인하기 원할 수 있다.

Beware of false too(false로도 주의)
연관 컬렉션 조건부 거짓 값으로 사용될 때 닐 거의 같은 문제를 제시하고, 동일한 조치를 정상적으로 그들을 처리하도록 주의해야한다.

Indexed(인덱스)

대부분 하나의 n 번째 항목에 대한 직접 도달하는 방법에 대해 얘기 방지, 또는 그 위치에있는 값을 변경하면서 지금까지, 우리는 벡터에 대해 이야기했습니다. 그 이유가있다 : 인덱스는 새로운 포인터.

솔직히, 문자열이나 배열 또는 다른 순차적 위해 그들은을 지수가 되실 데이터되어 거의 당신의 알고리즘에 의해 위임하지 않습니다. 대부분의 경우, 인덱스를 처리하는 복잡성을 유도 : 인덱스 연산과 범위 검사를 통해 또는 불필요한 indirections을 통해 중. 특별한 상황의 이상으로, 우리는 과도한 인덱스 조회 또는 수정 코드 냄새라고 말하는 안심.

즉,이 같은 것들에 대한 시간과 장소이며, 인덱스 추상화가 들어오는 곳이 말했다. 그것은 get의 특성화이다 nth의 하나의 기능, 구성되어 있습니다. 그들은 범위를 벗어날 인덱스를 처리하는 방법에 차이가 : get은 nil을 반환하지만 nth는 예외가 발생합니다 :

(nth [:a :b :c] 2)
;= :c
(get [:a :b :c] 2)
;= :c
(nth [:a :b :c] 3)
;= java.lang.IndexOutOfBoundsException
(get [:a :b :c] 3)

;= nil
(nth [:a :b :c] -1)
;= java.lang.IndexOutOfBoundsException
(get [:a :b :c] -1)
;= nil

당신은 기본 반환 값을 제공 할 때 이러한 차이에도 불구하고, 그 의미는 동일합니다 :

(nth [:a :b :c] -1 :not-found)
;= :not-found
(get [:a :b :c] -1 :not-found)
;= :not-found

nth와 get은 다른 의미를 전달한다. 첫째, nth는 숫자 인덱스 작업과 많은 수치 인덱싱 할 수 있는 일에서 작동 할 수 있습니다 벡터,리스트, 시퀀스, 자바 배열, 자바 목록, 문자열 및 정규 표현식 확인기(matcher). 반면에, get은 더 일반적이다 : 그것은 우리가 이미 보았 듯이, 연관 유형의 종류에 작동하고, 문제의 수집 또는 값에 키로 숫자 인덱스를 처리합니다.

nth 와 get 사이의 또 다른 주요 차이점은 get 더 탄력적이다. 우리는 이미 get이 인덱스(키로 취급되는)가 발견되지 않는 경우, 예외로 반환하지 않고 nil로 처리하는 것을 보았다. get은 더 나아가 : 조회의 대상을 지원하지 않을 경우라도 nth는 예외를 throw하지만, nil을 반환합니다.

(get 42 0)
;= nil
(nth 42 0)
;= java.lang.UnsupportedOperationException: nth not supported on this type: Long
Clojure의 벡터는 n 번째 자체에 사용할 수있는 이상으로, 상당히 직접 n 번째의 의미를 지원합니다. 참조 조회는 n 번째를 건드리지 않고, 간결하게 할 수 있는 방법은 (111 페이지) "컬렉션은 함수이다."를 보시오.

Stack(스택)

스택 컬렉션 그 고전적인 지원 후입 선출 (LIFO) 의미이다; 즉, 스택에 추가 된 항목은 가장 최근의 그 뽑아 수 처음이다. Clojure의 구별은 스택 데이터 구조를 가지고 있지 않지만, 세 가지 동작을 통해 스택 추상화를 지원한다 :

  • conj는 스택에 값을 넣는다.(편리하게 컬렉션에서 만들어진 기능을 재사용한다.)

  • pop은 스택의 가장 위의 값의 제거된 값을 얻는다.

  • peek은 스택의 가장 위 값을 얻는다.

리스트와 벡터는 모두 스택으로서 사용할 수 있다.(예 3-4, 3-5) 스택의 상단은 conj가 효율적으로 동작하는 데이터 구조에 각각의 끝이다.

Example 3-4. Using a list as a stack

(conj '() 1)
;= (1)
(conj '(2 1) 3)
;= (3 2 1)
(peek '(3 2 1))
;= 3
(pop '(3 2 1))
;= (2 1)
(pop '(1))
;= ()

Example 3-5. Using a vector as a stack

(conj [] 1)
;= [1]
(conj [1 2] 3)
;= [1 2 3]
(peek [1 2 3])
;= 3
(pop [1 2 3])
;= [1 2]
(pop [1])
;= []

빈 스택에서 pop을 사용하면 결과가 에러이다.

Set

우리는 이미 세트 연관 추상화에 부분적으로 참여하는 방법을 살펴 보았다. 그들은 자신과 키를 연결, 퇴화 맵의 일종으로 처리됩니다 :

(get #{1 2 3} 2)
;= 2
(get #{1 2 3} 4)
;= nil
(get #{1 2 3} 4 "not-found")
;= "not-found"

그러나 완료하려면 set 추상화는 disj를 요구하는데, 주어진 set에서 값(들)을 제거한다.

(disj #{1 2 3} 3 1)
;= #{2}

세트 추상화 자체가 약간 있지만, 세트에 기본 작업의 대부분을 일반적인 수집 및 연관 의미를 재사용, 우리는 당신이 clojure.set에 익숙해지는 것이 좋습니다. Clojure에서의 표준 라이브러리의 네임 스페이스, clojure.set은 subset?, superset?, union, intersection, project 등을 포함한 세트를 통해 다양한 높은 수준의 운영 및 조건을 구현 기능 세트를 제공합니다.

Sorted

그 값은 선택적으로 특수 비교기 인터페이스의 조건 또는 구현에 의해 정의 된 안정된 순서에 유지됩니다 정렬 된 추상화 보증에 참여 컬렉션. 이것은 당신이 효율적으로 모든 또는 컬렉션 '값의 부분 범위에 걸쳐 in-order, reverse-order seqs 할 수 있습니다. 이러한 작업은 다음에 의해 제공된다 :

  • rseq는 뒤바뀐 컬렉션 값의 seq를 리턴하는데 일정 시간에서 반환되는 것을 보장한다.
  • subseq는 키들의 명세된 범위에서 떨어진 컬렉션의 값의 seq를 리턴한다.
  • rsubseq는 subseq와 같지만 반대 순서의 seq를 리턴한다.

maps와 sets만이 정렬된 변형에서 사용될 수 있다. 그들은 특정한 표기법이 없다; 그들은 정렬 순서를 정의하는 자신의 조건이나 비교를 제공하는 경우, sorted-map, sorted-set, sorted-map-by, sorted-set-by에 의해 만들어진다.

정렬된 컬렉션이 주어지면, 당신은 쿼리할 수 있는 어떠한 추상화 함수도 사용할 수 있다.

(def sm (sorted-map :z 5 :x 9 :y 0 :b 2 :a 3 :c 4))
;= #'user/sm
sm
;= {:a 3, :b 2, :c 4, :x 9, :y 0, :z 5}
(rseq sm)                                               ①
;= ([:z 5] [:y 0] [:x 9] [:c 4] [:b 2] [:a 3])
(subseq sm <= :c)                                    ②
;= ([:a 3] [:b 2] [:c 4])
(subseq sm > :b <= :y)                              ③
;= ([:c 4] [:x 9] [:y 0])
(rsubseq sm > :b <= :y)                             ④
;= ([:y 0] [:x 9] [:c 4])

① rseq는 일정 시간에 뒤바뀐 정렬에서 sm 으로 seq를 반환한다. ② 모든 값을 가진 sm에 쿼리하여 :c와 동일한 것까지 정렬된 keys들을 반환한다. ③ :b 이후와 :y와 같거나 그 이전 것을 쿼리한다. ④ subseq로 쿼리하는 것과 같지만 반대로 정렬한다.

sm이 정렬되어 있기 때문에, 이들 동작 각각은 다양한 선형 - 시간 작업을해야 그들의 서열 전용 필연적 인보다 훨씬 더 나은 성능 특성을 갖고 (예, 필터, - 걸릴)와 같은 결과를 얻었다. 특히, rseq는 역순으로 임의의 컬렉션의 서열을 얻기 위해 사용될 수 있는 reverse와 대조적으로, 일정 시간 내에 복귀 보장하지만, 이는 선형 시간으로 동작한다.

compare는 모든 Clojure의 스칼라 량, 순차적 컬렉션을 지원하는 각 수준에서 사전 식 정렬, 오름차순으로 기본 정렬을 정의한다 :

(compare 2 2)
;= 0
(compare "ab" "abc")
;= -1
(compare ["a" "b" "c"] ["a" "b"])
;= 1
(compare ["a" 2] ["a" 2 0])
;= -1

사실, comapre는 문자열, 숫자, 연속 컬렉션 이상 지원이 java.lang.Comparable 구현하는 것을 지원 -부울, 키워드, 기호 및 모든 Java와 이 인터페이스를 구현하는 타사 클래스가 포함되어 있습니다. compare는 강력한 기능이지만 기본 비교기이다.

Comparators and predicates to define ordering(비교기와 정렬을 정의하는 서술어)

두 인수가 동일한 경우, 비교기는 첫번째 인수가 두번째 것 보다 클 때 양의 정수를, 첫번째 것이 두번째 것보다 작을 때, 음의 정수를, 같을 때 0을 리턴한다.

Clojure의 모든 함수들은 java.util.Comparator 및 비교기로 사용할 수 있다. - 비록 명백하게, 모든 기능을 그대로 사용되도록 의도되지 않을 수 있다. 특히,당신은 함수가 비교기 인터페이스를 구현하기 위해 특별한 아무것도 할 필요가 없다. - 어떤 두 인자 서술만으로 그렇게 된다.

비교를 생성하기 위해 특별한 인터페이스를 구현할 필요가없는 외에도,이 사실은 합성 순서 부를 작성하는 것이 훨씬 쉽다는 것을 의미한다 : 보조 정렬은 합성 함수 떨어져. 비교 함수는 정렬 된 컬렉션의 팩토리 함수뿐만 아니라, sort 및 sort-by로 직접 전달 될 수 있습니다 :

(sort < (repeatedly 10 #(rand-int 100)))
;= (12 16 22 23 41 42 61 63 83 87)
(sort-by first > (map-indexed vector "Clojure"))
;= ([6 \e] [5 \r] [4 \u] [3 \j] [2 \o] [1 \l] [0 \C])
[어떻게 Clojure의 비교기로 서술어를 사용할까요?]

이 인수가 동일한 경우 그 반환 값이 음 또는 양의 정수, 또는 0으로 정의되어 있기 때문에 조건과 같은 <, 부울 반환 값이-수, 비교기로 사용하는 것이 혼란을 보일 수 있습니다.

비교에 조건을 설정하는 데 사용되는 알고리즘은 매우 간단합니다 : 술어가 비교기에 주어진 순서에 따라 두 개의 인수와 처음이라고합니다. 술어가 true를 돌려주는 경우는 -1이 돌려 주어집니다. 그렇지 않으면, 술어은 다시 호출하지만, 반전 두 개의 인수의 순서와됩니다. 이 시간이 그것을이 true를 돌려주는 경우, 다음 1은 반환됩니다. 그렇지 않으면, 다른 것에 의해 조정되지 않는 인자가 없는 것이 주어지면 같다고 여겨지고 0이 반환됩니다.

comparator는 이 로직을 사용하는 비교기 함수로 두개의 서술어를 명시적으로 사용할 수 있다.


((comparator <) 1 4)
;= -1
((comparator <) 4 1)
;= 1
((comparator <) 4 4)
;= 0


이 거의 기능을 암시 비교기을 받아 다양한 Clojure의 기능을 함께 사용하면이 변환을 제공하며, 2 개의 인수를하기 때문에, 사용하지 않지만 기능은 이미 java.util.Comparator 인터페이스를 구현합니다.

따라서, 기본 compare, sorted-map, sorted-set-by가 정렬되는 곳에서 sort-map과 sorted-set이 maps와 set를 만들 때 정렬 순서를 유지하도록 비교기(다시, 두 인자를 서술하여 함수가 동작하는 것처럼)를 수용한다. 당신이 (compare로 부터 떨어진) 분리 수거에 전달할 수 있는 가장 간단한 비교기는 아마 (comp - compare)은 비교의 결과를 부정, 그리고 컬렉션의 그러므로 정렬 순서를 출력하는 것이다:

(sorted-map-by compare :z 5 :x 9 :y 0 :b 2 :a 3 :c 4)
;= {:a 3, :b 2, :c 4, :x 9, :y 0, :z 5}
(sorted-map-by (comp - compare) :z 5 :x 9 :y 0 :b 2 :a 3 :c 4)
;= {:z 5, :y 0, :x 9, :c 4, :b 2, :a 3}

그것은 그 정렬 순서는 정렬 된 map 또는 set 내에서 동등함 정의에 주목해야 이 때로는 합리적이지만 놀라운 결과로 이어질 수 있습니다. 예를 들어, 숫자의 크기 순서를 리턴하는 기능을 갖고 있다 :

(defn magnitude
  [x]
  (-> x Math/log10 Math/floor))
;= #'user/magnitude
(magnitude 100)
;= 2.0
(magnitude 100000)
;= 5.0

간단한 만큼, 우리는 그것의 제 1 및 제 2 인자 사이의 크기 순서의 차이를 반환합니다. magnitude를 사용하여 비교 조건을 만들 수 있습니다 :

(defn compare-magnitude
  [a b]
  (- (magnitude a) (magnitude b)))
((comparator compare-magnitude) 10 10000)
;= -1
((comparator compare-magnitude) 100 10)
;= 1
((comparator compare-magnitude) 10 75)
;= 0

정렬된 컬렉션으로 비교기를 사용하면 흥미롭다:

(sorted-set-by compare-magnitude 10 1000 500)  ①
;= #{10 500 1000}
(conj *1 600)                                                  ②
;= #{10 500 1000}
(disj *1 750)                                                    ③           
;= #{10 1000}
(contains? *1 1239)                                          ④                  
;= true

① 10, 100, 1000은 각각 크기가 다른 순서 때문에 비교기에 따른 다른 인자들로 set에 할당한다. ② no-op인 set으로 600을 더한다. 600이 500과 같은 크기의 같은 순서이고 비교기에 의해 500과 같다고 여겨진다. ③ 750은 비교기에 의해 500과 같다고 여겨진다. 500은 set으로부터 제거된다. disj에 인자로 750이 제공된다. ④ 유사하게 1239는 1000의 크기로 같은 순서이므로 contains?는 true를 반환한다. key는 주워진 set에서 발견된 인자와 동일하다.

때로는 이 동작을 원할 수 있고, 아닐 수도 있다. 당신이 당신의 비교기의 구현을 완벽하게 제어 할 수 있습니다 염두에 두십시오; 그것은 (또는 재사용) 술어를 사용하는 것이 편리하지만, 당신은 당신이 원하는 동등의 의미를 집행하기 위해 언제든지 음 또는 양의 정수 또는 0을 반환하도록 선택할 수 있습니다. compare-magnitude는 인수가 크기 순서가 있는 경우 비교 위임에 의해서만 해당하는 번호가 동일한 것으로 간주되는 것을 보장하기 위해 다시 작성 될 수 있다.

(defn compare-magnitude
  [a b]
  (let [diff (- (magnitude a) (magnitude b))]
    (if (zero? diff)
      (compare a b)
      diff)))
(sorted-set-by compare-magnitude 10 1000 500)
;= #{10 500 1000}
(conj *1 600)
;= #{10 500 600 1000}
(disj *1 750)
;= #{10 500 600 1000}

이제 우리의 세트의 값은 크기 순서로 정렬 상태를 유지하지만, 우리가 직관적으로 예상대로 (conj와 disj 등) 수치와 동일한 동작이다.

subseq과 rsubseq는 사용자 정의 비교기로 정의 된 정렬된 컬렉션에서 (순차적 또는 역순으로) 간격을 추출하는 기대로 계속 동작한다:

(sorted-set-by compare-magnitude 10 1000 500 670 1239)
;= #{10 500 670 1000 1239}
(def ss *1)
;= #'user/ss
(subseq ss > 500)
;= (670 1000 1239)
(subseq ss > 500 <= 1000)
;= (670 1000)
(rsubseq ss > 500 <= 1000)
;= (1000 670)
이 기능의 사용에 필요한 비교 기호 <, <=,>, 및> = 전적으로 정렬된 컬렉션에 사용되는 실제 비교에 관한 힌트로서 해당 술어는 사용되지 않습니다.

이러한 함수 중 재밌는 응용은 선형 보간을 구현하는 것이다.

(defn interpolate
 "Takes a collection of points (as [x y] tuples), returning a function
  which is a linear interpolation between those points."
 [points]
  (let [results (into (sorted-map) (map vec points))] ①
    (fn [x]
      (let [[xa ya] (first (rsubseq results <= x))         ②
            [xb yb] (first (subseq results > x))]
        (if (and xa xb)                                          ③
          (/ (+ (* ya (- xb x)) (* yb (- x xa)))            ④
             (- xb xa))
          (or ya yb))))))

① (map vec points) 는 각 포인트는 벡터이며, 따라서 map 상에 항목으로 추가 될 수 있음을 보장한다. ② 여기와 아래 라인에 우리는 알려진 포인트들에서 x의 최근접한 두개의 이웃을 탐색한다. ③ xa 또는 xb가 nil의 범위를 벗어나고 우리는 (or ya yb)를 리턴하며 우리가 아닌 값이다. ④ 일반적인 케이스는 선형 보간 함수이다.

테스트해보자; 세가지 점이 있다. [0 0], [10 10], [15 5]:

[차트]

알려진 X 좌표를 들어, 우리는 우리가 가지고 있는 데이터를 맞는 Y를 찾을 수 있습니다 :

(def f (interpolate [[0 0] [10 10] [15 5]]))
;= #'user/f
(map f [2 10 12])
;= (2 10 8)

완벽하다!

Concise Collection Access(간결한 컬렉션 엑세스)

특별히 연관 추상화를 지원하는 컬렉션에서의 값을 액세스하는 것은 쉽게 가장 공통적인 작업이다. 지속적으로 얻을 또는 n 번째는 매우 얻기 어려운 입력 할 필요 케이스이다. 다행히, Clojure의 컬렉션과 연관 컬렉션에 사용되는 키의 가장 일반적인 유형도의 의미와 기능이 get 또는 nth (관련 컬렉션의 구체적인 유형에 맞게).

Collections are functions(컬렉션은 함수이다.) 아주 간단하게, Clojure의 모음을 제공하는 키 또는 인덱스와 연관된 값을 조회 기능은 다음과 같습니다. 그래서 :

(get [:a :b :c] 2)
;= :c
(get {:a 5 :b 6} :b)
;= 6
(get {:a 5 :b 6} :c 7)
;= 7
(get #{1 2 3} 3)
;= 3

이 더 간결한 표현을 정확히 동일합니다 :

([:a :b :c] 2)
;= :c
({:a 5 :b 6} :b)
;= 6
({:a 5 :b 6} :c 7)
;= 7
(#{1 2 3} 3)
;= 3

각각의 경우, 수집 기능 위치에 있으므로 자체 내에 찾기 위해 키 또는 인덱스로 호출되고 있다. maps는 조회가 실패 할 경우 get 같은 선택적인 두 번째 인수를 허용, 옵션 기본값은 돌아왔다. 벡터와 세트 모두 조회를 위한 단 하나의 값 / 인덱스를 받아; 기본값은 지원되지 않습니다. 벡터 조회를 위해 제공되는 지표는 단지 nth와 마찬가지로, 벡터의 범위 내에 있어야 합니다.

([:a :b :c] -1)
;= #<IndexOutOfBoundsException java.lang.IndexOutOfBoundsException>

Collection keys are (often) functions.(컬렉션 키들은 종종 함수들이다.) 마찬가지로, 키들의 가장 공통적인 타입 - 키워드들과 기호들 - 은 또한 제공된 컬렉션에서 함수들로 보여진다. 그래서

(get {:a 5 :b 6} :b)
;= 6
(get {:a 5 :b 6} :c 7)
;= 7
(get #{:a :b :c} :d)
;= nil

는 정확히 더 간결한 표현과 동일하다.

(:b {:a 5 :b 6})
;= 6
(:c {:a 5 :b 6} 7)
;= 7
(:d #{:a :b :c})
;= nil

위치의 함수 값이 기능해야하기 때문에, 인덱스 숫자가 사용될 수 없다; 따라서, 벡터 룩업이 방법을 사용하여 수행 될 수 없다.

Idiomatic Usage(관용적 사용)

좋아요, 그래서 컬렉션 값에 액세스 덜 자세한 방법이 있습니다. 그건 분명 좋은,하지만 몇 가지 추가 지침없이, 그것은 언제 어떻게 각각의 변형 사용하는 방법에 관해서는 분명하지 않을 수도 있습니다 : 컬렉션에서 키워드 또는 기호와 조회 기능 중에서 어떤 것이 사용되어야 할까?

이 맛의 영역으로 위험 속에있다; 하지만, 일반적으로, 우리는 키워드 또는 기호의 사용을 권장하는 기능으로 조회된다. 이 관용구의 가장 직접적인 이점은 조회 기능으로 사용하면 키워드와 기호는 대부분 리터럴 때문에, 널 포인터 예외는 일반적으로 피할 수 있다는 것입니다.

생각해보자

(defn get-foo
  [map]
  (:foo map))
;= #'user/get-foo
(get-foo nil)
;= nil
(defn get-bar
  [map]
  (map :bar))
;= #'user/get-bar
(get-bar nil)
;= #<NullPointerException java.lang.NullPointerException>

또한, (coll : foo)가 coll, 컬렉션을 가정하는 것도 함수이다.

즉, 대부분의 Clojure의 데이터 구조에 대한 사실이지만, (예를 들어) 목록에 대한 사실이 아니다, 그리고 Clojure의의 수집 추상화에 참여하지만 그 기능도하지 않은 다른 유형 반드시 사실이 아니다.

이는 (:foo coll)이 더 바람직하게 한다. 당신은 문자 그대로의 키워드가 있음을 확신 할 수있는 한, :foo가 항상 함수이며 결코 닐이 아니다. coll에 의해 참조된 값은 어느 조건을 충족할 수 있다.

물론, 컬렉션 키워드 또는 기호 이외의 키가 있는 경우 당신은 컬렉션 또는 get, nth을 조회 함수로 사용해야 한다.

Collections and Keys and Higher-Order Functions(컬렉션과 키와 고차원 함수)

키워드들, 심볼들, 및 많은 컬렉션 때문에 공통 기능은 고차 함수에 대한 입력으로 그들을 사용할 때, 아주 편리하다. 우리는 우리 고객들의 이름들을 말한다; 어떠한 함수들도 정의하지 안혹, get을 명시적으로 사용하지 않고:

(map :name [{:age 21 :name "David"}
            {:gender :f :name "Suzanne"}
            {:name "Sara" :location "NYC"}])
;= ("David" "Suzanne" "Sara")

some은 제공된 서술어로부터 논리 참 값을 리턴하는 시퀀스에서 첫번째 값을 찾는다; 접합에서 사용된 sets는 공통 패턴이다:

(some #{1 3 7} [0 2 4 5 6])
;= nil
(some #{1 3 7} [0 2 3 4 5 6])
;= 3

이것은 조건문에서 컬렉션을 검색의 결과를 사용하는 매우 간결한 방법이다. 더 일반적인 기능은 filter이다. 이는 주어진 서술어에 따라 참인 값을 할당하는 지연 시퀀스를 리턴한다. 다시, 우리는 필요에 의해 적절하고 잠재적으로 다른 함수들과 조합할 때, 컬렉션이나 키워드, 기호를 사용할 수 있다.

(filter :age [{:age 21 :name "David"}
              {:gender :f :name "Suzanne"}
              {:name "Sara" :location "NYC"}])
;= ({:age 21, :name "David"})
(filter (comp (partial <= 25) :age) [{:age 21 :name "David"}
                                     {:gender :f :name "Suzanne" :age 20}
				     {:name "Sara" :location "NYC" :age 34}])
;= ({:age 34, :name "Sara", :location "NYC"})

remove는 filter의 보어이며, 자연스럽다: 그것은 주어진 함수(filter (complement f) collection)의 complement와 주어진 컬렉션을 필터링하는 것에 의해 구현된다.

Beware of the nil (닐의 주의)
그것은 몇 가지 값이 문제의 값이 존재하지 않거나 거짓 인 경우 둘 다 논리적으로 거짓이기 때문에, 결과는 우리의 기대에 부합하지 않을 수도 있음을 잊지하기 쉬운 지정된 컬렉션에 속하는지 여부를 테스트 세트를 사용하기 매우 간단하다 :

(remove #{5 7} (cons false (range 10)))
;= (false 0 1 2 3 4 6 8 9)
(remove #{5 7 false} (cons false (range 10)))
;= (false 0 1 2 3 4 6 8 9)


그래서, set에서 nil 또는 false에 대해 확신할 수 없을 때, get을 통한 contains? 또는 직접 호출을 이용할 수 있다 :


(remove (partial contains? #{5 7 false}) (cons false (range 10)))
;= (0 1 2 3 4 6 8 9)

Data Structure Types(데이터 구조 형태)

Clojure에 적절하게 다양한 추상화를 만족 각각의 구체적인 데이터 구조들을 제공한다. 우리는 그들의 행동과 의미가 추상화 (또는 추상화의 일부)가 참여하여 대부분의 정의, 희망 것 같이 우리는 이미 꽤 많은-실제로 이러한 구체적인 구현 작업을 했습니다.

여기, 우리는 빠른 속도로 자신의 건축에해야 할 대부분의 구체적인 데이터 구조 유형, 각각 분리 구현 세부 사항의 일부를 이동합니다.

Lists

목록은 Clojure의에서 간단한 컬렉션 유형입니다. 우리는 7 페이지의 "식, 연산자, 구문 및 우선 순위"에 설명 된대로 그들의 기본 및 가장 일반적인 목적은 Clojure의 코드에서 호출에 대한 표현의 역할을하는 것입니다; 같은, 당신이 당신의 프로그램의 런타임 보다 소스 파일에서 더 자연스럽다. Clojure의 목록은 단독으로 연결되어 있지만 효율적에 새로운 헤드 값을 넣거나, 팝업 또는 시퀀스 운영자 나머지는 이전의 헤드 값없이 하위 목록에 대한 참조를 얻기 위해하는 접속사를 사용하여, 액세스 또는 머리에서 "수정"입니다. 그들은 연결리스트이기 때문에, 효율적인 랜덤 액세스를 지원하지 않는다; 따라서, 목록에 있는 nth는 (등등 벡터, 배열, 함께 사용하면 일정 시간 반대) 선형 시간에 실행하고 이렇게 하면 sublinear 효율성 get의 목적에 부합하지 않기 때문에 모든 목록을 지원하지 않습니다.

리스트는 자신의 시퀀스이므로 가치가 없다; 따라서, 목록의 seq는 항상 목록과 리스트의 분산되지 않은 순차적 화면을 리턴한다.

우리는 이 전에 자연스런 리스트만을 봤다.

'(1 2 3)
;= (1 2 3)

여기에 인용부화를 하지 않으면 값 1을 호출하는 것으로 연산하고 실패한다. 이것의 부수작용은 리스트의 표현식을 연산하지 않는다.

'(1 2 (+ 1 2))
;= (1 2 (+ 1 2))

대부분의 사람들은 단순히 회원 표현식이 항상 평가 될 경우에 문자 벡터를 사용합니다. 그러나, 당신이 정말로 목록을 필요해야 할 경우가 있고, 다른 데이터 구조는 수행하지 않습니다. 다른 때는 list에 이른다.

(list 1 2 (+ 1 2))
;= (1 2 3)

list는 값의 어떤 수도 수용하며, 각 값은 반환된 리스트이 인자가 된다.

끝으로, 당신은 값이 명시적인 리스트라면 list? 서술어를 테스트할 수 있다.

Vectors

벡터는 효율적인 랜덤 액세스 조회 및 변경을 지원 순차적 인 데이터 구조이다. 그것은 java.util.ArrayList Python의 리스트, 루비의 arrays를 사용하는 프로그래머의 기대에 대응한다. 벡터는 또한 다용도로 연관에서 참여할 수 있고, 인덱싱 우리가 봐온 스택 추상화이다. 익숙한 문자적 벡터는 별 문제로하고, 벡터는 vector와 vec을 사용해 만들어진다:

(vector 1 2 3)
;= [1 2 3]
(vec (range 5))
;= [0 1 2 3 4]

vector는 list로 아날로그하며, vec는 새로운 벡터를 만드는 온전한 컨텐츠를 갖는 순차적인 단일 인자만 기대한다. 이것은 배열, 리스트, 시퀀스, 연속된 값에서 어떤 데이터를 처리할 때 유용하지만 벡터의 용량에서 유리한 것을 원하는 조작에서 유용하다.

Vectors as tuples

튜플은 벡터를 위한 가장 공통 사용 사례의 하나이다. 언제나 어떤 값을 가능한 작은 기념으로 같이 패치한다면 벡터에 넣을 수 있다.(함수에서 다중 값을 리턴할 때 같이)

(defn euclidian-division   ①
  [x y]
  [(quot x y) (rem x y)])
(euclidian-division 42 8)
;= [5 2]

① 간단하게 처리한다면, (juxt quot rem)은 이것의 결과와 동일한 함수를 리턴한다.

클로저의 분해하는 인자분해 메카니즘의 쌍은 그들의 성분으로 값들이 쉽게 나타나도록 한다.

(let [[q r] (euclidian-division 53 7)]
  (str "53/7 = " q " * 7 + " r))
;= "53/7 = 7 * 7 + 4"

튜플을 유혹하고 쉽게하는 대로, 그들이 끝을 의미하지 않는다는 것을 잊지마라. 그들은 공통 API의 공개된 부분보다 내부의 라이브러리나 모듈에 숨겨져 있다. 이 뒤에 근거는 두 가지이다.

  • 튜플은 자기 문서화되지 않습니다. 당신은 지속적으로 각 인덱스의 각각의 역할을 기억해야합니다.

  • 튜플은 유연성이다. 당신은 특정 반환 값에 적합하지 않은 중간 슬롯에 값을 제공해야하고 당신은 꼬리에 추가가 아닌 다른 방법으로 튜플을 확장 할 수 없습니다.

Maps는 두가지 제한에 영향을 받지 않는다. 그러므로 공통 API의 부분인 함수와 일반적이지 않은 리턴된 값, maps들은 잘 어울린다.

물론, 어떤 룰에 이것은 예외이다. 도메인에 명확한 튜플의 목적이 있는 공세서는 튜플로서의 벡터를 사용하는 것이 더 적절하다.(좌표, 그래프에서 점에 지향되는 등)

(def point-3d [42 26 -7])
(def travel-legs [["LYS" "FRA"] ["FRA" "PHL"] ["PHL" "RDU"]])

상기 후자의 경우에, 우리는 두 벡터의 다른 사용법을 유의, 밖의 벡터는 리스트보다 좋지만 안의 벡터는 에어포트 트라이그램의 튜플로 사용될 수 있다. [from to].

Sets

연관을 논의하고 추상화를 설정할 때 우리가 이미 손도 안 한 구체적인 데이터 구조의 구현과 같은 세트에 대해 할 말이 좀있다. Clojure의의 다른 데이터 구조 유형과 마찬가지로, 세트는 우리가 이미 본 적이 리터럴 표기법을

#{1 2 3}
;= #{1 2 3}
#{1 2 3 3}   ①
;= #<IllegalArgumentException java.lang.IllegalArgumentException:
;=   Duplicate key: 3>

① 정의에 의해 세트가 중복 된 값을 포함 할 수 없기 때문에, 중복 된 값을 포함 리터럴은 거부됩니다.

그리고 해시 설정 함수는 인수의 수에서 정렬되지 않은 세트를 만들 수 있습니다 :

(hash-set :a :b :c :d)
;= #{:a :c :b :d}

마지막으로, 당신은 set 함수를 사용하여 어떠한 컬렉션에서 값으로부터 set을 만들어 낼 수 있다.

(set [1 6 1 8 3 7 7])
;= #{1 3 6 7 8}

이는 실제로 순차적인 어떠한 것토 처리할 수 있고, 매우 간결한 그 sets를 위해 허용할 수 있다.

(apply str (remove (set "aeiouy") "vowels are useless"))
;= "vwls r slss"
(defn numeric? [s] (every? (set "0123456789") s))
;= #'user/numeric?
(numeric? "123")
;= true
(numeric? "42b")
;= false

정렬된 set도 사용가능하며, 106페이지의 "정렬"에서 볼 수 있다.

Maps

이외에도 문자 이제 익숙한 maps에서 :

{:a 5 :b 6}
;= {:a 5, :b 6}
{:a 5 :a 5}                                           ①
;= #<IllegalArgumentException java.lang.IllegalArgumentException:
;=   Duplicate key: :a>

① 세트, map에서 고유한 키의 값과 마찬가지로; 이 요구 사항을 위반 리터럴은 거부됩니다.

정렬되지 않은 맵은 키 / 값 쌍의 번호를 받아 해쉬 맵을 사용하여 생성 될 수있다. 이것은 주로 사용자가 스스로 벡터 튜플로 그룹화되지 않은 키 / 값 쌍의 컬렉션을 가질 때 적용과 함께 사용된다 :

(hash-map :a 5 :b 6)
;= {:a 5, :b 6}
(apply hash-map [:a 5 :b 6])
;= {:a 5, :b 6}

정렬된 set도 사용가능하며, 106페이지의 "정렬"에서 볼 수 있다.

keys and vals. maps에 특정하지만, 이러한 함수들은 소스 map으로부터 키 또는 값의 시퀀스를 받는 편리한 방법이 있다 :

(keys m)
;= (:a :b :c)
(vals m)
;= (1 2 3)

엔트리들에서 그들의 시퀀스를 얻는 map에서 seq를 사용한 기본적인 짧은 문법이다. 그리고 각 엔트리로부터 key와 val을 얻을 수 있다.

(map key m)
;= (:a :c :b)
(map val m)
;= (1 3 2)

Maps as ad-hoc structs(임시 구조체로서의 maps)

맵 값은 임의의 유형이 될 수 있기 때문에, 이들은 빈번히 키를 각 필드 (또한 슬롯들)을 식별하기위한 간단하고 유연한 모델, 가장 자주 키워드로 사용된다.

(def playlist
  [{:title "Elephant", :artist "The White Stripes", :year 2003}
   {:title "Helioself", :artist "Papas Fritas", :year 1997}
   {:title "Stories from the City, Stories from the Sea",
    :artist "PJ Harvey", :year 2000}
   {:title "Buildings and Grounds", :artist "Papas Fritas", :year 2000}
   {:title "Zen Rodeo", :artist "Mardi Gras BB", :year 2002}])

Clojure의 데이터 모델링 간단한지도로 시작하는 것이 매우 일반적이다. 당신이 당신의 엔티티로 구성됩니다 슬롯 무엇인지 확실하지 특히, maps는 견고한 데이터 모델을 미리 정의 할 필요없이 바로 작동시킬 수 있습니다.

당신이 당신의 모델 (Clojure에서의 추상화에 참여하고 다른 데이터 구조) maps를 사용하면 자신의 모든 기능은 당신이 그것으로 모든 일에 흐른다. 예를 들어, 데이터 집계를 "쿼리", 당신은 키 같은 키워드를 사용하는 가정하면 사소한된다 :

(map :title playlist)
;= ("Elephant" "Helioself" "Stories from the City, Stories from the Sea"
;=  "Buildings and Grounds" "Zen Rodeo")

유사하게, 연관되는 인자분해같은 것은, "Map 인자분해" 에 소개되어 있으며, "구조체" 개별적인 맵에서 간결화된 기능을 하는 것은 (:slot data) 로 제거된다.

(defn summarize [{:keys [title artist year]}]
  (str title " / " artist " / " year))

클로저는 당신이 원하는 깔끔한 업그레이드를 제공하는 것으로 오버 아키텍팅을 초기로 부터 저장하려 했다. "구조체"에 map-based를 사용하여 성숙한 모델을 프로타입핑했다. 글므로 maps의 모델링은 죽지 않았다. 대신, 특정된 구현대신 클로적 컬렉션 추상화로 프로그래밍 하는 동안 전문화된 구조를 위한 map-based 모델을 대체헐 수 있었다. 연관된 타입을 생성하는 defrecord에 의해 정의된 것처럼. 270페이지의 "당신의 타입을 정의하기"에 defrecord를 논의할 수 있고, 277의 maps와 records 사용할 때에서 maps를 다룰 수 있다.

Other usages of maps(maps의 다른 사용)

Maps는 종종 요약, 인덱스, 또는 변환 테이블로 사용된다; 데이터베이스 색인 및 보기의 여기 생각합니다.

예를 들어, group-by는 키 함수에 따른 컬렉션 부분에 매우 유용합니다.

(group-by #(rem % 3) (range 10))
;= {0 [0 3 6 9], 1 [1 4 7], 2 [2 5 8]}

여기에 우리가 제공하는 기능에 의해 정의 된 키에 함께 그룹화 번호를 참조하십시오. 다음 재생목록 데이터에 우리는 가수별로 앨범에 인덱스를 쉽게 만들 수 있습니다.

(group-by :artist playlist)
;= {"Papas Fritas" [{:title "Helioself", :artist "Papas Fritas", :year 1997}
;=                  {:title "Buildings and Grounds", :artist "Papas Fritas"}]
;=  ...}

두 "컬럼"에서 인덱싱은 (group-by (juxt :col1 :col2) data)만큼 쉽습니다.

때때로 당신은 아이템ㅌ의 벡터를 리터나는 대신 키만에 대한 아이템들의 합을 계산하길 원할 수도 있습니다. 당신은 group-by를 사용하고 합을 내기 위해 각 값을 처리할 수 있습니다.

(into {} (for [[k v] (group-by key-fn coll)]
           [k (summarize v)]))

key-fn과 summarize들은 실제 함수들을 위한 자리입니다. 그러나 이것은 당신이 데이터베이스 쿼리로 부터 큰 결과 set을 다룰 때 처럼 컬렉션을 확장할 때 귀찮아집니다. 이럴 대 당신은 group-by와 reduce의 혼합을 사용해야만 합니다. reduce-by는 SQL에서 select ... group by ... 와 같은 데이터에서 합산의 종류를 계산하도록 도와줍니다.

(defn reduce-by
  [key-fn f init coll]
   (reduce (fn [summaries x]
            (let [k (key-fn x)]
              (assoc summaries k (f (summaries k init) x))))
    {} coll))
x, xs, and other not-so-cryptic names(x, xs 그리고 다른 암호같은 이름이 아닌 것)

클로저에 입문하는 이들은 x와 xs 같이 요약된 이름같은 혼동스런 사용을 발견하게 된다. 각 문자의 의미는 라이브러리 코딩 스탠다드 스타일 가이드에 의해 부분적으로 코딩되었다는 것이다.(http://dev.clojure.org/display/design/Library+Coding+Standards) 필연적으로, 당신은 x 라는 이름을 사용할 때, 당신의 코드는 x의 타입에 일반적이고 잊기 쉽다고 말한다. 그리고 invoice를 부르는 것에서 포인트가 없다. 마찬가지로, x 값들의 컬렉션이나 시퀀스 등은 종종 xs로 명명된다. 당신의 코드가 더 일반적이고, 당신이 사용하는 이름이 덜 구체적이 될 것이다.

ACME 사에 구매 주문 리스트가 있다고 하자, "구조체"로서의 maps는 다음과 같다.

(def orders
  [{:product "Clock", :customer "Wile Coyote", :qty 6, :total 300}
   {:product "Dynamite", :customer "Wile Coyote", :qty 20, :total 5000}
   {:product "Shotgun", :customer "Elmer Fudd", :qty 2, :total 800}
   {:product "Shells", :customer "Elmer Fudd", :qty 4, :total 100}
   {:product "Hole", :customer "Wile Coyote", :qty 1, :total 1000}
   {:product "Anvil", :customer "Elmer Fudd", :qty 2, :total 300}
   {:product "Anvil", :customer "Wile Coyote", :qty 6, :total 900}])

redue-by로 우리는 고객별로 주문 합계를 쉽게 계산할 수 있다.

(reduce-by :customer #(+ %1 (:total %2)) 0 orders)
;= {"Elmer Fudd" 1200, "Wile Coyote" 7200}

비슷하게, 당신은 각 제품에 대한 고객을 얻을 수 있다.

(reduce-by :product #(conj %1 (:customer %2)) #{} orders)
;= {"Anvil" #{"Wile Coyote" "Elmer Fudd"},
;=  "Hole" #{"Wile Coyote"},
;=  "Shells" #{"Elmer Fudd"},
;=  "Shotgun" #{"Elmer Fudd"},
;=  "Dynamite" #{"Wile Coyote"},
;=  "Clock" #{"Wile Coyote"}}

두 레벨 구분을 통해 제품별, 고객별로 보고 싶다면? 당신은 간단히 키로 두 값의 벡터를 리턴하도록 요구할 수 있다. 그런 함수를 사용하는 몇 가지 방법이 있다.

(fn [order]
  [(:customer order) (:product order)])
#(vector (:customer %) (:product %))
(fn [{:keys [customer product]}]
  [customer product])
(juxt :customer :product)

우리는 하나의 가장 명확하고 간결을 선호합니다 :

(reduce-by (juxt :customer :product)
  #(+ %1 (:total %2)) 0 orders)
;= {["Wile Coyote" "Anvil"] 900,
;=  ["Elmer Fudd" "Anvil"] 300,
;=  ["Wile Coyote" "Hole"] 1000,
;=  ["Elmer Fudd" "Shells"] 100,
;=  ["Elmer Fudd" "Shotgun"] 800,
;=  ["Wile Coyote" "Dynamite"] 5000,
;=  ["Wile Coyote" "Clock"] 300}

우리가 기대하는 것과 다르다 - 우리는 maps의 map이 없다. 이 문제는 map을 얕게 하는 reduce-by로 귀결된다. 당신은 결과 map에 메시징할 수 있거나 네스티드 maps를 위한 버전을 만들어 reduce-by "수정"을 할 수 있다.

네스티드 maps로 reduce-by를 작업하게 하는 것은 assoc를 호출하는 것을 대체하고 assoc-in과 get-in으로 명시적 get(함수로 사용되는 map일 경우)을 사용하는 것만큼 쉽다.

(defn reduce-by-in
  [keys-fn f init coll]
  (reduce (fn [summaries x]
            (let [ks (keys-fn x)]
              (assoc-in summaries ks
                (f (get-in summaries ks init) x))))
    {} coll))

기대하듯, 우리는 두 단계로 구분할 수 있다.

(reduce-by-in (juxt :customer :product)
  #(+ %1 (:total %2)) 0 orders)
;= {"Elmer Fudd" {"Anvil" 300,
;=                "Shells" 100,
;=                "Shotgun" 800},
;=  "Wile Coyote" {"Anvil" 900,
;=                 "Hole" 1000,
;=                 "Dynamite" 5000,
;=                 "Clock" 300}}

두번째 선택은 우리의 이전 결과 데이터를 변형하는 것이다.

(def flat-breakup
  {["Wile Coyote" "Anvil"] 900,
   ["Elmer Fudd" "Anvil"] 300,
   ["Wile Coyote" "Hole"] 1000,
   ["Elmer Fudd" "Shells"] 100,
   ["Elmer Fudd" "Shotgun"] 800,
   ["Wile Coyote" "Dynamite"] 5000,
   ["Wile Coyote" "Clock"] 300})

maps 의 map으로 넣는다. 이렇게 하면, assoc-in을 사용할 수 있다.

(reduce #(apply assoc-in %1 %2) {} flat-breakup)
;= {"Elmer Fudd" {"Shells" 100,
;=                "Anvil" 300,
;=                "Shotgun" 800},
;=  "Wile Coyote" {"Hole" 1000,
;=                 "Dynamite" 5000,
;=                 "Clock" 300,
;=                 "Anvil" 900}}

flat-breakup 맵에 의해 제공된 seq에서 각 값은 [["Wile Coyote" "Anvil"] 900]와 같은 맵 엔트리이다. 그러므로, 우리의 감축 함수 apply가 맵 엔트리들의 각각이 사용될 때, 결과는 assoic-in을 호출한다-예,(assoc-in {} ["Wile Coyote" "Anvil"] 900)-편리하게 결과 map의 구조와 그 내부의 갑들을 정의하는 각 엔트리내에서 데이터들을 사용할 수 있다.