4.17.1 Sequences
(part ("(lib scribblings/guide/guide.scrbl)" "sequences")) in (part ("(lib scribblings/guide/guide.scrbl)" "top")) introduces sequences.
sequence 封装了一个有序的值集合。 sequence 的元素可以通过 for 语法形式之一、通过 sequence-generate 返回的过程,或者通过将 sequence 转换为 stream 来提取。
sequence 数据类型与许多其他数据类型重叠。在内置数据类型中,sequence 数据类型包括以下:
精确非负整数(见下文)
字符串(见 Strings)
字节字符串(见 Byte Strings)
列表(见 Pairs and Lists)
可变列表(见 Mutable Pairs and Lists)
向量(见 Vectors)
flvectors(见 Flonum Vectors)
fxvectors(见 Fixnum Vectors)
哈希表(见 Hash Tables)
字典(见 Dictionaries)
集合(见 Sets)
输入端口(见 Ports)
streams(见 Streams)
一个非负 整数 的 精确数 k 作为一个 sequence, 类似于 (in-range k),但 k 本身不是一个 stream。
可以使用结构体类型属性定义自定义 sequences。定义自定义 sequence 最简单的方法是使用 gen:stream 泛型接口。Streams 适用于可直接迭代的数据结构。 例如,列表可以通过 first 和 rest 直接迭代。另一方面,向量不能直接迭代: 迭代必须通过索引进行。对于不能直接迭代的数据结构,该数据结构的 iterator 可以定义为一个 stream(例如,包含向量索引的结构体)。
例如,展开链表(表示为向量列表)本身不适合 stream 抽象,但具有可以表示为 streams 的基于索引的迭代器:
> (struct unrolled-list-iterator (idx lst) #:methods gen:stream [(define (stream-empty? iter) (define lst (unrolled-list-iterator-lst iter)) (or (null? lst) (and (>= (unrolled-list-iterator-idx iter) (vector-length (first lst))) (null? (rest lst))))) (define (stream-first iter) (vector-ref (first (unrolled-list-iterator-lst iter)) (unrolled-list-iterator-idx iter))) (define (stream-rest iter) (define idx (unrolled-list-iterator-idx iter)) (define lst (unrolled-list-iterator-lst iter)) (if (>= idx (sub1 (vector-length (first lst)))) (unrolled-list-iterator 0 (rest lst)) (unrolled-list-iterator (add1 idx) lst)))])
> (define (make-unrolled-list-iterator ul) (unrolled-list-iterator 0 (unrolled-list-lov ul)))
> (struct unrolled-list (lov) #:property prop:sequence make-unrolled-list-iterator) > (define ul1 (unrolled-list '(#(cracker biscuit) #(cookie scone)))) > (for/list ([x ul1]) x) '(cracker biscuit cookie scone)
prop:sequence 属性在指定迭代方面提供了更大的灵活性,例如当需要预处理步骤来准备数据以进行迭代时。 make-do-sequence 函数创建一个 sequence,给定一个返回实现 sequence 的过程的 thunk, 而 prop:sequence 属性可以与结构体类型关联以实现其到 sequence 的隐式转换。
对于大多数 sequence 类型,从 sequence 中提取元素不会对原始 sequence 值产生副作用; 例如,从列表中提取元素的 sequence 不会改变列表。对于其他 sequence 类型, 每次提取都意味着一个副作用;例如,从端口提取字节的 sequence 会导致从端口读取字节。 一个 sequence 的状态可以跨越该 sequence 的所有使用(如端口), 也可以限定在每次通过 for 形式、sequence->stream、 sequence-generate 或 sequence-generate* initiate 该 sequence 的不同时间。 具体来说,传递给 make-do-sequence 的 thunk 在每次使用该 sequence 时被调用以 initiate 该 sequence。 因此,不同的 sequences 在被多次 initiate 时表现不同。
> (define (double-initiate s1) ; initiate the sequence twice (define-values (more?.1 next.1) (sequence-generate s1)) (define-values (more?.2 next.2) (sequence-generate s1)) ; alternate fetching from sequence via the two initiations (list (next.1) (next.2) (next.1) (next.2))) > (double-initiate (open-input-string "abcdef")) '(97 98 99 100)
> (double-initiate (list 97 98 99 100)) '(97 97 98 98)
> (double-initiate (in-naturals 97)) '(97 97 98 98)
此外,sequence 中的后续元素可能仅仅通过调用 sequence-generate 的第一个结果就被"消耗"了, 即使第二个结果从未被调用。
> (define (double-initiate-and-use-more? s1) ; initiate the sequence twice (define-values (more?.1 next.1) (sequence-generate s1)) (define-values (more?.2 next.2) (sequence-generate s1)) ; alternate fetching from sequence via the two initiations ; but this time call `more?` in between (list (next.1) (more?.1) (next.2) (more?.2) (next.1) (more?.1) (next.2) (more?.2))) > (double-initiate-and-use-more? (open-input-string "abcdef")) '(97 #t 99 #t 98 #t 100 #t)
在此示例中,第一次调用 sequence-generate 中嵌入的状态仅仅通过调用 more?.1 就"获取"了 98。
sequence 的单个元素通常对应单个值,但一个元素也可能对应多个值。 例如,哈希表为 sequence 中的每个元素生成两个值——一个键及其值。
4.17.1.1 Sequence Predicate and Constructors
procedure
end : real? (in-range start end [step]) → stream? start : real? end : real? step : real? = 1
当给定零作为 step 时,in-range 返回一个无限 sequence。 当 step 是一个非常小的数字,且 step 或 sequence 元素是浮点数时, 它也可能返回无限 sequences。
procedure
(in-inclusive-range start end [step]) → stream?
start : real? end : real? step : real? = 1
> (sequence->list (in-inclusive-range 7 11)) '(7 8 9 10 11)
> (sequence->list (in-inclusive-range 7 11 2)) '(7 9 11)
> (sequence->list (in-inclusive-range 7 10 2)) '(7 9)
Added in version 8.0.0.13 of package base.
procedure
(in-naturals [start]) → stream?
start : exact-nonnegative-integer? = 0
> (for/list ([k (in-naturals)] [x (in-range 10)]) (list k x)) '((0 0) (1 1) (2 2) (3 3) (4 4) (5 5) (6 6) (7 7) (8 8) (9 9))
See Pairs and Lists for information on using lists as sequences.
Changed in version 6.7.0.4 of package base: 改进了 for 中列表的元素可达性保证。
See Mutable Pairs and Lists for information on using mutable lists as sequences.
> (for/list ([x (in-mlist (mcons "RACKET" (mcons "LANG" '())))]) (string-length x)) '(6 4)
procedure
vec : vector? start : exact-nonnegative-integer? = 0 stop : (or/c exact-integer? #f) = #f step : (and/c exact-integer? (not/c zero?)) = 1
See Vectors for information on using vectors as sequences.
可选参数 start、stop 和 step 与 in-range 类似, 不同之处在于 stop 的 #f 值等价于 (vector-length vec)。 也就是说,sequence 中的第一个元素是 (vector-ref vec start), 每个后续元素通过将 step 加到前一个元素的索引来生成。 如果 step 非负,sequence 在索引大于或等于 end 之前停止; 如果 step 为负,sequence 在索引小于或等于 end 之前停止。
如果 start 不是有效索引,则 exn:fail:contract exception is raised, 除非 start、stop 和 (vector-length vec) 相等, 此时结果为空 sequence。
> (for ([x (in-vector (vector 1) 1)]) x) > (for ([x (in-vector (vector 1) 2)]) x) in-vector: starting index is out of range
starting index: 2
valid range: [0, 0]
vector: '#(1)
> (for ([x (in-vector (vector) 0 0)]) x) > (for ([x (in-vector (vector 1) 1 1)]) x)
如果 stop 不在 [-1, (vector-length vec)] 范围内,则 exn:fail:contract exception is raised。
如果 start 小于 stop 且 step 为负,则 exn:fail:contract exception is raised。 类似地,如果 start 大于 stop 且 step 为正,则 exn:fail:contract exception is raised。
An in-vector application can provide better performance for vector iteration when it appears directly in a for clause.
> (define (histogram vector-of-words) (define a-hash (make-hash)) (for ([word (in-vector vector-of-words)]) (hash-set! a-hash word (add1 (hash-ref a-hash word 0)))) a-hash) > (histogram #("hello" "world" "hello" "sunshine")) '#hash(("hello" . 2) ("sunshine" . 1) ("world" . 1))
procedure
str : string? start : exact-nonnegative-integer? = 0 stop : (or/c exact-integer? #f) = #f step : (and/c exact-integer? (not/c zero?)) = 1
See Strings for information on using strings as sequences.
可选参数 start、stop 和 step 与 in-vector 中相同。
An in-string application can provide better performance for string iteration when it appears directly in a for clause.
> (define (line-count str) (for/sum ([ch (in-string str)]) (if (char=? #\newline ch) 1 0))) > (line-count "this string\nhas\nthree \nnewlines") 3
procedure
bstr : bytes? start : exact-nonnegative-integer? = 0 stop : (or/c exact-integer? #f) = #f step : (and/c exact-integer? (not/c zero?)) = 1
See Byte Strings for information on using byte strings as sequences.
可选参数 start、stop 和 step 与 in-vector 中相同。
An in-bytes application can provide better performance for byte string iteration when it appears directly in a for clause.
> (define (has-eof? bs) (for/or ([ch (in-bytes bs)]) (= ch 0))) > (has-eof? #"this byte string has an \0embedded zero byte") #t
> (has-eof? #"this byte string does not") #f
procedure
r : (input-port? . -> . any/c) = read in : input-port? = (current-input-port)
procedure
(in-input-port-bytes in) → sequence?
in : input-port?
procedure
(in-input-port-chars in) → sequence?
in : input-port?
procedure
in : input-port? = (current-input-port)
mode : (or/c 'linefeed 'return 'return-linefeed 'any 'any-one) = 'any
procedure
(in-bytes-lines [in mode]) → sequence?
in : input-port? = (current-input-port)
mode : (or/c 'linefeed 'return 'return-linefeed 'any 'any-one) = 'any
如果提供了 bad-index-v,则当 hash 被并发修改使得迭代没有 valid hash index 时, bad-index-v 将同时作为键和值返回。提供 bad-index-v 在遍历具有弱引用键的哈希表时特别有用, 因为条目可以被异步移除(即在 in-hash 已承诺进行另一次迭代之后,但在它能够访问下一次迭代的条目之前)。
> (define table (hash 'a 1 'b 2))
> (for ([(key value) (in-hash table)]) (printf "key: ~a value: ~a\n" key value))
key: b value: 2
key: a value: 1
See Hash Tables for information on using hash tables as sequences.
Changed in version 7.0.0.10 of package base: 添加了可选的 bad-index-v 参数。
procedure
(in-hash-keys hash) → sequence?
hash : hash? (in-hash-keys hash bad-index-v) → sequence? hash : hash? bad-index-v : any/c
> (define table (hash 'a 1 'b 2))
> (for ([key (in-hash-keys table)]) (printf "key: ~a\n" key))
key: b
key: a
Changed in version 7.0.0.10 of package base: 添加了可选的 bad-index-v 参数。
procedure
(in-hash-values hash) → sequence?
hash : hash? (in-hash-values hash bad-index-v) → sequence? hash : hash? bad-index-v : any/c
> (define table (hash 'a 1 'b 2))
> (for ([value (in-hash-values table)]) (printf "value: ~a\n" value))
value: 2
value: 1
Changed in version 7.0.0.10 of package base: 添加了可选的 bad-index-v 参数。
procedure
(in-hash-pairs hash) → sequence?
hash : hash? (in-hash-pairs hash bad-index-v) → sequence? hash : hash? bad-index-v : any/c
bad-index-v 参数(如果提供)的使用方式与 in-hash 相同。 当遇到无效索引时,sequence 中的 pair 将以 bad-index-v 作为其 car 和 cdr。
> (define table (hash 'a 1 'b 2))
> (for ([key+value (in-hash-pairs table)]) (printf "key and value: ~a\n" key+value))
key and value: (b . 2)
key and value: (a . 1)
Changed in version 7.0.0.10 of package base: 添加了可选的 bad-index-v 参数。
Added in version 6.4.0.6 of package base.
Changed in version 7.0.0.10: 添加了可选的 bad-index-v 参数。
Changed in version 8.0.0.10: 添加了 ephemeron 变体。
procedure
(in-directory [dir use-dir?]) → sequence?
dir : (or/c #f path-string?) = #f
use-dir? : ((and/c path? complete-path?) . -> . any/c) = (lambda (dir-path) #t)
in-directory sequence 递归遍历嵌套子目录(由 use-dir? 过滤)。 要生成仅包含目录的直接内容的 sequence,请使用 directory-list 的结果作为 sequence。
每个目录的直接内容按 path<? 排序报告,并且子目录的内容在目录中后续路径之前报告。
> (current-directory (collection-path "info"))
> (for/list ([f (in-directory)]) f)
'(#<path:compiled>
#<path:compiled/main_rkt.dep>
#<path:compiled/main_rkt.zo>
#<path:main.rkt>)
> (for/list ([f (in-directory "compiled")]) f) '(#<path:main_rkt.dep> #<path:main_rkt.zo>)
> (for/list ([f (in-directory "compiled")]) f) '(#<path:compiled/main_rkt.dep> #<path:compiled/main_rkt.zo>)
> (for/list ([f (in-directory #f (lambda (p) (not (regexp-match? #rx"compiled" p))))]) f) '(#<path:main.rkt> #<path:compiled>)
Changed in version 6.0.0.1 of package base: 添加了 use-dir? 参数。
Changed in version 6.6.0.4: 添加了排序结果的保证。
procedure
(in-producer producer) → sequence?
producer : procedure? (in-producer producer stop arg ...) → sequence? producer : procedure? stop : any/c arg : any/c
如果未给定 stop 值,sequence 将无限继续,因此通常将其与有限 sequence 一起使用或使用 #:break 等。 如果给定了 stop 值,则用于标识标记 sequence 结束的值(且 stop 值不包含在 sequence 中); stop 可以是应用于 producer 结果的谓词,也可以是与结果用 eq? 测试的值。 (如果停止值本身是一个函数或 producer 返回多个值,则 stop 参数必须是谓词。)
如果指定了额外的 arg,它们会传递给每次对 producer 的调用。
> (define (counter) (define n 0) (lambda ([d 1]) (set! n (+ d n)) n)) > (for/list ([x (in-producer (counter))] [y (in-range 4)]) x) '(1 2 3 4)
> (for/list ([x (in-producer (counter))] #:break (= x 5)) x) '(1 2 3 4)
> (for/list ([x (in-producer (counter) 5)]) x) '(1 2 3 4)
> (for/list ([x (in-producer (counter) 5 1/2)]) x) '(1/2 1 3/2 2 5/2 3 7/2 4 9/2)
> (for/list ([x (in-producer read eof (open-input-string "1 2 3"))]) x) '(1 2 3)
此形式主要用于 for*/list 等形式中的 let 类绑定——但更近期添加的 #:do 子句形式覆盖了许多相同的用途。
procedure
(in-indexed seq) → sequence?
seq : sequence?
> (for ([(ch i) (in-indexed "hello")]) (printf "The char at position ~a is: ~a\n" i ch))
The char at position 0 is: h
The char at position 1 is: e
The char at position 2 is: l
The char at position 3 is: l
The char at position 4 is: o
procedure
(in-sequences seq ...) → sequence?
seq : sequence?
procedure
(in-parallel seq ...) → sequence?
seq : sequence?
procedure
(in-values-sequence seq) → sequence?
seq : sequence?
procedure
(in-values*-sequence seq) → sequence?
seq : sequence?
procedure
(make-do-sequence thunk) → sequence?
thunk :
(or/c (-> (values (any/c . -> . any) (any/c . -> . any/c) any/c (or/c (any/c . -> . any/c) #f) (or/c (() () #:rest list? . ->* . any/c) #f) (or/c ((any/c) () #:rest list? . ->* . any/c) #f))) (-> (values (any/c . -> . any) (or/c (any/c . -> . any/c) #f) (any/c . -> . any/c) any/c (or/c (any/c . -> . any/c) #f) (or/c (() () #:rest list? . ->* . any/c) #f) (or/c ((any/c) () #:rest list? . ->* . any/c) #f))))
第一个结果是 pos->element 过程,它接受当前位置并返回当前元素的值。
可选的第二个结果是 early-next-pos 过程,进一步描述如下。 或者,可选的第二个结果可以是 #f,等价于恒等函数。
第三个(或第二个)结果是 next-pos 过程,它接受当前位置并返回下一个位置。
第四个(或第三个)结果是初始位置。
第五个(或第四个)结果是 continue-with-pos? 函数,它接受当前位置, 如果 sequence 包含当前位置的值则返回真结果,如果 sequence 应该结束而不是包含值则返回假。 或者,第五个(或第四个)结果可以是 #f 表示 sequence 应该始终包含当前值。 此函数在使用 pos->element 之前对每个位置进行检查。
第六个(或第五个)结果是 continue-with-val? 函数,类似于第五个(或第四个)结果, 但它接受当前元素值而不是当前位置。或者,第六个(或第五个)结果可以是 #f 表示 sequence 应该始终包含当前位置的值。
第七个(或第六个)结果是 continue-after-pos+val? 过程, 它同时接受当前位置和当前元素值,并确定在当前元素已包含在 sequence 中后 sequence 是否结束。 或者,第七个(或第六个)结果可以是 #f 表示 sequence 在当前元素后总是可以继续。
early-next-pos 过程(可选的第二个结果)接受当前位置并返回更新后的位置。 此更新后的位置用于 next-pos 和 continue-after-pos+val?, 但不用于 continue-with-pos?(它使用原始当前位置)。 early-next-pos 的意图是支持一种 sequence,其中位置必须递增以避免在循环处理 sequence 值时 保持值可达,因此 early-next-pos 在 pos->element 之后立即应用。
上面列出的每个过程每个位置只调用一次。在最后三个过程中,一旦其中一个过程返回 #f, sequence 就结束,且不再调用任何过程。通常,其中一个函数确定结束条件, 而 #f 用于代替其他两个函数。
Changed in version 6.7.0.4 of package base: 添加了对可选第二个结果的支持。
使用预先存在的 sequence:
> (struct my-set (table) #:property prop:sequence (lambda (s) (in-hash-keys (my-set-table s))))
> (define (make-set . xs) (my-set (for/hash ([x (in-list xs)]) (values x #t))))
> (for/list ([c (make-set 'celeriac 'carrot 'potato)]) c) '(potato celeriac carrot)
使用 make-do-sequence:
> (struct train (car next) #:property prop:sequence (lambda (t) (make-do-sequence (lambda () (values train-car train-next t (lambda (t) t) (lambda (v) #t) (lambda (t v) #t))))))
> (for/list ([c (train 'engine (train 'boxcar (train 'caboose #f)))]) c) '(engine boxcar caboose)
4.17.1.2 Sequence Conversion
procedure
(sequence->stream seq) → stream?
seq : sequence?
如果从 seq 提取元素涉及副作用,则每次首次使用 stream-first 或 stream-rest 访问或跳过元素时都会执行该副作用。
注意 sequence 本身可以有状态,因此对同一个 seq 的多次 sequence->stream 调用不一定独立。
> (define inport (open-input-bytes (bytes 1 2 3 4 5))) > (define strm (sequence->stream inport)) > (stream-first strm) 1
> (stream-first (stream-rest strm)) 2
> (stream-first strm) 1
> (define strm2 (sequence->stream inport)) > (stream-first strm2) 3
> (stream-first (stream-rest strm2)) 4
注意 sequence 本身可以有状态,因此对同一个 seq 的多次 sequence-generate 调用不一定独立。
> (define inport (open-input-bytes (bytes 1 2 3 4 5))) > (define-values (more? get) (sequence-generate inport)) > (more?) #t
> (get) 1
> (get) 2
> (define-values (more2? get2) (sequence-generate inport)) > (list (get2) (get2) (get2)) '(3 4 5)
> (more2?) #f
procedure
(sequence-generate* seq)
→
(or/c list? #f) (-> (values (or/c list? #f) procedure?)) seq : sequence?
4.17.1.3 Additional Sequence Operations
| (require racket/sequence) | package: base |
value
procedure
(sequence->list s) → list?
s : sequence?
procedure
s : sequence?
procedure
(sequence-ref s i) → any
s : sequence? i : exact-nonnegative-integer?
procedure
(sequence-tail s i) → sequence?
s : sequence? i : exact-nonnegative-integer?
如果 initiating s 涉及副作用, 则 sequence s 直到结果 sequence 被 initiate 时才被 initiate, 此时前 i 个元素从 sequence 中提取。
procedure
(sequence-append s ...) → sequence?
s : sequence?
如果所有给定的 s 都是 streams,则结果也是一个 stream。
procedure
(sequence-map f s) → sequence?
f : procedure? s : sequence?
procedure
f : procedure? s : sequence?
procedure
(sequence-add-between s e) → sequence?
s : sequence? e : any/c
> (let* ([all-reds (in-cycle '("red"))] [red-and-blues (sequence-add-between all-reds "blue")]) (for/list ([n (in-range 10)] [elt red-and-blues]) elt)) '("red" "blue" "red" "blue" "red" "blue" "red" "blue" "red" "blue")
> (for ([text (sequence-add-between '("veni" "vidi" "duci") ", ")]) (display text)) veni, vidi, duci
procedure
(sequence/c [ #:min-count min-count] elem/c ...) → contract? min-count : (or/c #f exact-nonnegative-integer?) = #f elem/c : contract?
如果 min-count 是数字,则要求 stream 至少包含那么多元素。
> (define/contract predicates (sequence/c (-> any/c boolean?)) (in-list (list integer? string->symbol)))
> (for ([P predicates]) (printf "~s\n" (P "cat"))) #f
predicates: broke its own contract
promised: boolean?
produced: 'cat
in: an element of
(sequence/c (-> any/c boolean?))
contract from: (definition predicates)
blaming: (definition predicates)
(assuming the contract is correct)
at: eval:55:0
> (define/contract numbers&strings (sequence/c number? string?) (in-dict (list (cons 1 "one") (cons 2 "two") (cons 3 'three))))
> (for ([(N S) numbers&strings]) (printf "~s: ~a\n" N S))
1: one
2: two
numbers&strings: broke its own contract
promised: string?
produced: 'three
in: an element of
(sequence/c number? string?)
contract from: (definition numbers&strings)
blaming: (definition numbers&strings)
(assuming the contract is correct)
at: eval:57:0
> (define/contract a-sequence (sequence/c #:min-count 2 char?) "x")
> (for ([x a-sequence] [i (in-naturals)]) (printf "~a is ~a\n" i x)) 0 is x
a-sequence: broke its own contract
promised: a sequence that contains at least 2 values
produced: "x"
in: (sequence/c #:min-count 2 char?)
contract from: (definition a-sequence)
blaming: (definition a-sequence)
(assuming the contract is correct)
at: eval:59:0
4.17.1.3.1 Additional Sequence Constructors and Functions
> (for/list ([x (in-syntax #'(1 2 3))]) x) '(#<syntax:eval:61:0 1> #<syntax:eval:61:0 2> #<syntax:eval:61:0 3>)
Added in version 6.3 of package base.
procedure
length : exact-positive-integer? seq : sequence?
Added in version 6.3 of package base.