Programming Clojure - Unifying Data with Sequences

19686 단어 programming
In Clojure, all these data structures can be accessed through a single abstraction: the sequence (or seq). A seq (pronounced “seek”) is a logical list, the seq is an abstraction that can be used everywhere.
 
Collections that can be viewed as seqs are called seq-able. In this chapter, you will meet a variety of seq-able collections:
• All Clojure collections • All Java collections • Java arrays and strings • Regular expression matches • Directory structures • I/O streams • XML trees
Sequence라는 추상화는Lisp-like 언어에 매우 중요하다. 왜냐하면 이런 FP 언어에서 거의 모든 것이list이고 모두 sequence이기 때문에Lisp, 바로listprocess 언어이다.위에서 여러 가지 추상적인 구체적인 표현을 열거하였다.그래서 sequences의 조작이 굉장히 중요해요.
 

4.1 Everything Is a Sequence


Every aggregate data structure in Clojure can be viewed as a sequence. A sequence has three core capabilities:

(first aseq) ;get the first item

(rest aseq) ;get everything after the first item

(cons elem aseq) ;construct a new sequence by adding an item


sequence는 사실 3개의 핵심 조작인 퍼스트,rest,cons만 있고 매우 간단하다. 다른 각종 조작은 모두 이 3개의 조작을 바탕으로 이루어진 것이다.
왜 퍼스트,rest만 제공합니까?이상하게 생각하기 시작했는데 개인적으로 이 두 가지 조작은 이미 충분하다고 생각한다. 만약에 반복하려고 하면rest에first를 계속 가져간다. 만약에 몇 개의 item을 찾으려면first(rest)를 몇 번 교체해야 한다. 게다가 lazy sequence의 디자인,rest는 모두 먼저 계산하지 않고 first가 도착하면 다시 계산할 수 있다.
(first '(1 2 3))
1
(rest '(1 2 3))
(2 3)
(cons 0 '(1 2 3))
(0 1 2 3)

; seq-able seq ,  seq.  vector seq
; clojure , REPL seq , seq
(rest [1 2 3]) 
(2 3)

(first {:fname "Stu" :lname "Halloway"})
[:fname "Stu"]

(first #{:the :quick :brown :fox})  ;set ,  first 
:brown

(sorted-set :the :quick :brown :fox) ; set
#{:brown :fox :quick :the}
(sorted-map :c 3 :b 2 :a 1)    ; map
{:a 1, :b 2, :c 3}

conj adds one or more elements to a collection, and into adds all the items in one collection to another.

(conj '(1 2 3) :a)
(:a 1 2 3)
(into '(1 2 3) '(:a :b :c))
(:c :b :a 1 2 3)


4.2 Using the Sequence Library


The Clojure sequence library provides a rich set of functionality that can work with any sequence.
The functions provide a rich backbone of functionality that can take advantage of any data structure that obeys the basic first/rest/cons contract.
The following functions are grouped into four broad categories: • Functions that create sequences • Functions that filter sequences • Sequence predicates • Functions that transform sequences
 

Creating Sequences


range
(range start? end step?)

(range 10)
(0 1 2 3 4 5 6 7 8 9)
(range 10 20)
(10 11 12 13 14 15 16 17 18 19)

(range 1 25 2)
(1 3 5 7 9 11 13 15 17 19 21 23)


repeat
(repeat n x)

(repeat 5 1)
(1 1 1 1 1)
(repeat 10 "x")
("x" "x" "x" "x" "x" "x" "x" "x" "x" "x")


iterate , infinite extension of range
(iterate f x)

(take 10 (iterate inc 1)) ;take infinite, seq seq
(1 2 3 4 5 6 7 8 9 10)


cycle
(cycle coll)

(take 10 (cycle (range 3)))
(0 1 2 0 1 2 0 1 2 0)


interleave
takes multiple collections and produces a new collection that interleaves values from each collection until one of the collections is exhausted
(interleave & colls) ;가변 길이

(interleave (whole-numbers) ["A" "B" "C" "D" "E"])
(1 "A" 2 "B" 3 "C" 4 "D" 5 "E")


interpose each of the elements of the input collection separated by a separator
(interpose separator coll)

(interpose "," ["apples" "bananas" "grapes"])
("apples" "," "bananas" "," "grapes")

 

(apply str (interpose \, ["apples" "bananas" "grapes"])) ; string
"apples,bananas,grapes"

 

(use '[clojure.contrib.str-utils :only (str-join)])
(str-join \, ["apples" "bananas" "grapes"]) ;
"apples,bananas,grapes"


 

Filtering Sequences


filter
(filter pred coll)
(take 10 (filter even? (whole-numbers)))
(2 4 6 8 10 12 14 16 18 20)
(take 10 (filter odd? (whole-numbers)))
(1 3 5 7 9 11 13 15 17 19)

take-while, drop-while (take-while pred coll)   =   while (pred?) {take element from coll}, 따라서 첫 번째 조건에 부합되지 않는 사람을 만나면 take를 중지합니다
(drop-while pred coll)

(take-while even? [2 4 6 1 8]) ; [2 4 6]

(take-while #(> % 0) [3 2 1 0 -1 -2]) ; (3 2 1)

 

(drop-while even? [2 4 6 1 3 5]) ; [1 3 5]

(drop-while even? [1 3 2 4 5]) ; [1 3 2 4 5] 1 drop, vector


 
split-at, split-with
split-at takes an index, and split-with takes a predicate

(split-at 5 (range 10))
[(0 1 2 3 4) (5 6 7 8 9)]
(split-with #(<= % 10) (range 0 20 2))
[(0 2 4 6 8 10) (12 14 16 18)]


Sequence Predicates


every?
(every? pred coll)
(every? odd? [1 3 5])
true
(every? odd? [1 3 5 8])
false

some
(some pred coll)
some returns the first nonfalse value for its predicate or returns nil if no element matched

(some even? [1 2 3])
true
(some even? [1 3 5])
nil


not-every, not-any

(not-every? even? (whole-numbers))
true
(not-any? even? (whole-numbers))
false


Transforming Sequences


map
(map f coll)

(map #(format "<p>%s</p>" %) ["the" "quick" "brown" "fox"])
("<p>the</p>" "<p>quick</p>" "<p>brown</p>" "<p>fox</p>")

(map #(format "<%s>%s</%s>" %1 %2 %1) ["h1" "h2" "h3" "h1"] ["the" "quick" "brown" "fox"])
("<h1>the</h1>" "<h2>quick</h2>" "<h3>brown</h3>" "<h1>fox</h1>")


reduce
(reduce f coll)
reduce applies f on the first two elements in coll, then applies f to the result and the third element, and so on. reduce is useful for functions that “total up” a sequence in some way.

(reduce + (range 1 11))
55


sort, sort-by
(sortcomp?coll)(sort-by a-fn comp?coll)는sort보다'a-fn'이 많고,coll에 대해서는 apply a-fn, 그리고sort

(sort [42 1 7 11])
(1 7 11 42)

(sort > [42 1 7 11])
(42 11 7 1)

(sort-by #(.toString %) [42 1 7 11]) ; str , 7>42
(1 11 42 7)

(sort-by :grade > [{:grade 83} {:grade 90} {:grade 77}])
({:grade 90} {:grade 83} {:grade 77})


List Comprehension


The granddaddy of all filters and transformations is the list comprehension. A list comprehension creates a list based on an existing list, using set notation.
이것은python에서도 매우 중요한 특성이다.
 
(for [binding-form coll-expr filter-expr? ...] expr) for takes a vector of binding-form/coll-exprs, plus an optional filter-expr, and then yields a sequence of exprs. List comprehension is more general than functions such as map and filter and can in fact emulate most of the filtering and transformation functions described earlier.

(for [word ["the" "quick" "brown" "fox"]] (format "<p>%s</p>" word))
("<p>the</p>" "<p>quick</p>" "<p>brown</p>" "<p>fox</p>")


Comprehensions can emulate filter using a :when clause

(take 10 (for [n (whole-numbers) :when (even? n)] n))
(2 4 6 8 10 12 14 16 18 20)


A:while clause continues the evaluation only while its expression holds true: when과 달리 맞지 않으면 stop
(for [n (whole-numbers) :while (even? n)] n)
(0)

The real power of for comes when you work with more than one binding expression.

(for [file "ABCDEFGH" rank (range 1 9)] (format "%c%d" file rank))
("A1" "A2" ... elided ... "H7 ""H8")

 

;Clojure iterates over the rightmost binding expression in a sequence comprehension first and then works its way left

(for [rank (range 1 9) file "ABCDEFGH"] (format "%c%d" file rank))
("A1" "B1" ... elided ... "G8" "H8") ;


4.3 Lazy and Infinite Sequences


Most Clojure sequences are lazy; in other words, elements are not calculated until they are needed. Using lazy sequences has many benefits:
• You can postpone expensive computations that may not in fact be needed. • You can work with huge data sets that do not fit into memory. • You can delay I/O until it is absolutely needed.
 

Forcing Sequences


어떤 때는 레이지가 싫기 때문에force seq가 필요하지만 되도록 쓰지 마세요.
The problem usually arises when the code generating the sequence has side effects. Consider the following sequence, which embeds side effects via println:

(def x (for [i (range 1 3)] (do (println i) i))) ; lazy, print
#'user/x


doall, dorun
(doall coll)
doall forces Clojure to walk the elements of a sequence and returns the elements as a result:
(doall x)
| 1
| 2
) (1 2)

(dorun coll) 결과를 유지하지 않고 Dorun walks the elements of a sequence without keeping past elements in memory를 반복합니다.As a result, dorun can walk collections too large to fit in memory.

(dorun x)
| 1
| 2
nil


The nil return value is a telltale reminder that dorun does not hold a reference to the entire sequence.
The dorun and doall functions help you deal with side effects, while most of the rest of Clojure discourages side effects. You should use these functions rarely. (The Clojure core calls each of these functions only once in about 4,000 lines of code.)

4.4 Clojure Makes Java Seq-able


The seq abstraction of first/rest applies to anything that there can be more than one of.
In the Java world, that includes the following: • The Collections API • Regular expressions • File system traversal • XML processing • Relational database results Clojure wraps these Java APIs, making the sequence library available for almost everything you do.
건너뛰다
 

4.5 Calling Structure-Specific Functions


Clojure’s sequence functions allow you to write very general code. Sometimes you will want to be more specific and take advantage of the characteristics of a specific data structure. Clojure includes functions that specifically target lists, vectors, maps, structs, and sets.
우선 편의를 위해 더욱 중요한 것은 효율이다. 통용되는 방법은 더욱 추상적이지만 비교적 효과가 없다.

Functions on Lists

(peek '(1 2 3))  ; first
1
(pop '(1 2 3)) ; rest
(2 3)


Functions on Vectors


get

(get [:a :b :c] 1)
:b
(get [:a :b :c] 5)
nil

([:a :b :c] 1) ;vector function
:b
([:a :b :c] 5)

java.lang.ArrayIndexOutOfBoundsException: 5 ; get ,


assoc associates a new value with a particular index:
(assoc [0 1 2 3 4] 2 :two)
[0 1 :two 3 4]

 
subvec returns a subvector of a vector: (subvec avec start end?)

(subvec [1 2 3 4 5] 3) ;end is not specified, it defaults to the end of the vector
[4 5]
(subvec [1 2 3 4 5] 1 3)
[2 3]


Functions on Maps

(keys {:sundance "spaniel", :darwin "beagle"})
(:sundance :darwin)
(vals {:sundance "spaniel", :darwin "beagle"})
("spaniel" "beagle")

(get {:sundance "spaniel", :darwin "beagle"} :darwin)
"beagle"
(get {:sundance "spaniel", :darwin "beagle"} :snoopy)
nil

({:sundance "spaniel", :darwin "beagle"} :darwin) ;map function

"beagle"
({:sundance "spaniel", :darwin "beagle"} :snoopy)
nil

(:darwin {:sundance "spaniel", :darwin "beagle"} ) ;keyword function
"beagle"
(:snoopy {:sundance "spaniel", :darwin "beagle"} )
nil


 
assoc returns a map with a key/value pair added. dissoc returns a map with a key removed. select-keys returns a map, keeping only the keys passed in merge combines maps. If multiple maps contain a key, the rightmost map wins.

(def song {:name "Agnus Dei"               :artist "Krzysztof Penderecki"
:album "Polish Requiem"
:genre "Classical" })

 

(assoc song :kind "MPEG Audio File") ;add kind:"MPEG Audio File"
{:name "Agnus Dei", :album "Polish Requiem",
:kind "MPEG Audio File", :genre "Classical",
:artist "Krzysztof Penderecki"}


 

(dissoc song :genre) ; rmove genre
{:name "Agnus Dei", :album "Polish Requiem",
:artist "Krzysztof Penderecki"}

 

(select-keys song [:name :artist])
{:name "Agnus Dei", :artist "Krzysztof Penderecki"}

 

(merge song {:size 8118166, :time 507245})
{:name "Agnus Dei", :album "Polish Requiem",
:genre "Classical", :size 8118166,
:artist "Krzysztof Penderecki", :time 507245}


merge-with is like merge, except that when two or more maps have the same key, you can specify your own function for combining the values under the key.
(merge-with merge-fn &maps) 같은 키가 나타날 때의 논리를 사용자 정의할 수 있습니다

(merge-with
concat ;merge-fn
{:rubble ["Barney"], :flintstone ["Fred"]}
{:rubble ["Betty"], :flintstone ["Wilma"]}
{:rubble ["Bam-Bam"], :flintstone ["Pebbles"]})
{:rubble ("Barney" "Betty" "Bam-Bam"),
:flintstone ("Fred" "Wilma" "Pebbles")}


 

Functions on Sets


union, intersection, difference, select

(def languages #{"java" "c" "d" "clojure" })
(def letters #{"a" "b" "c" "d" "e" })
(def beverages #{"java" "chai" "pop" })

;union returns the set of all elements present in either input set,
(union languages beverages)
#{"java" "c" "d" "clojure" "chai" "pop"}

 

;intersection returns the set of all elements present in both input sets,
(intersection languages beverages)
#{"java"}

 

;difference returns the set of all elements present in the first input set, minus those in the second
(difference languages beverages) ;languages beverages , b , l
#{"c" "d" "clojure"}

 

;select returns the set of all elements matching a predicate

(select #(= 1 (.length %)) languages)
#{"c" "d"}


Relational Algebra


Set union and difference are part of set theory, but they are also part of relational algebra, which is the basis for query languages such as SQL. The relational algebra consists of six primitive operators: set union and set difference (described earlier), plus rename, selection, projection, and cross product.
이것은 매우 편리하고 재미있으며 관계 대수를 지지한다.줄마다 맵으로 표시하고, 표는 맵의 set으로 표시합니다.

(def compositions
#{{:name "The Art of the Fugue" :composer "J. S. Bach" }
{:name "Musical Offering" :composer "J. S. Bach" }
{:name "Requiem" :composer "Giuseppe Verdi" }
{:name "Requiem" :composer "W. A. Mozart" }})


(def composers
#{{:composer "J. S. Bach" :country "Germany" }
{:composer "W. A. Mozart" :country "Austria" }
{:composer "Giuseppe Verdi" :country "Italy" }})

(def nations
#{{:nation "Germany" :language "German" }
{:nation "Austria" :language "German" }
{:nation "Italy" :language "Italian" }})


The rename function renames keys (“database columns”), based on a map from original names to new names. (rename relation rename-map)

(rename compositions {:name :title}) ; name title
#{{:title "Requiem", :composer "Giuseppe Verdi"}
{:title "Musical Offering", :composer "J.S.Bach"}
{:title "Requiem", :composer "W. A. Mozart"}
{:title "The Art of the Fugue", :composer "J.S. Bach"}}


The select function returns maps for which a predicate is true and is analogous to the WHERE portion of a SQL SELECT: (select pred relation)

(select #(= (:name %) "Requiem") compositions) ;name Requiem
#{{:name "Requiem", :composer "W. A. Mozart"}
{:name "Requiem", :composer "Giuseppe Verdi"}}


The project function returns only the portions of the maps that match a set of keys. (project relation keys)

(project compositions [:name])
#{{:name "Musical Offering"}
{:name "Requiem"}
{:name "The Art of the Fugue"}}


The cross product returns every possible combination of rows in the different tables. You can do this easily enough in Clojure with a list comprehension: (for [m compositions c composers] (concat m c)) ;전체 조합4 x 3 = 12 rows ...
Although the cross product is theoretically interesting, you will typically want some subset of the full cross product. For example, you might want to join sets based on shared keys: (join relation-1 relation-2 keymap?)

(join compositions composers)   ;join the composition names and composers on the shared key :composer

(join composers nations {:country :nation}) ; country nation map


You can combine the relational primitives.

(project
  (join
    (select #(= (:name %) "Requiem") compositions)
    composers)
  [:country])
#{{:country "Italy"} {:country "Austria"}}

좋은 웹페이지 즐겨찾기