Racket编程指南——11 迭代和推导

11 迭代和推导

用于语法形式的for家族支持对序列(sequences)进行迭代。列表、向量、字符串、字节字符串、输入端口和散列表都可以用作序列,像in-range的构造函数可以提供更多类型的序列。

for的变种以不同的方式累积迭代结果,但它们都具有相同的语法形态。 现在简化了,for的语法是

(for ([id sequence-expr] ...)
  body ...+)

for循环遍历由sequence-expr生成的序列。 对于序列的每个元素,for将元素绑定到id,然后副作用求值body

Examples:
> (for ([i '(1 2 3)])
    (display i))

123

> (for ([i "abc"])
    (printf "~a..." i))

a...b...c...

> (for ([i 4])
    (display i))

0123

forfor/list变体更像Racket。它将body结果累积到列表中,而不是仅仅副作用求值body。 在更多的技术术语中,for/list实现了列表理解(list comprehension)

Examples:
> (for/list ([i '(1 2 3)])
    (* i i))

'(1 4 9)

> (for/list ([i "abc"])
    i)

'(#\a #\b #\c)

> (for/list ([i 4])
    i)

'(0 1 2 3)

for的完整语法可容纳多个序列并行迭代,for*变体可以嵌套迭代,而不是并行运行。 forfor*的更多变体以不同的方式产生积累body结果。 在所有这些变体中,包含迭代的判断可以同时包含绑定。

不过,在for的变体细节之前,最好是先查看生成有趣示例的序列生成器的类型。

11.1 序列构造器

in-range函数生成数值序列,给定可选的起始数字(默认为0),序列结束前的数字和可选的步长(默认为1)。 直接使用非负整数k作为序列是对(in-range k)的简写。

Examples:
> (for ([i 3])
    (display i))

012

> (for ([i (in-range 3)])
    (display i))

012

> (for ([i (in-range 1 4)])
    (display i))

123

> (for ([i (in-range 1 4 2)])
    (display i))

13

> (for ([i (in-range 4 1 -1)])
    (display i))

432

> (for ([i (in-range 1 4 1/2)])
    (printf " ~a " i))

 1  3/2  2  5/2  3  7/2

in-naturals函数是相似的,除了起始数字必须是确切的非负整数(默认为0),步长总是1,没有上限。for循环只使用in-naturals将永远不会终止,除非正文表达引发异常或以其它方式退出。

Example:
> (for ([i (in-naturals)])
    (if (= i 10)
        (error "too much!")
        (display i)))

0123456789

too much!

stop-before函数和stop-after函数构造给定序列和判断的新的序列。这个新序列就像这个给定的序列,但是在判断返回true的第一个元素之前或之后立即被截断。

Example:
> (for ([i (stop-before "abc def"
                        char-whitespace?)])
    (display i))

abc

in-listin-vectorin-string这样的序列构造器只是简单地使用列表(list)、向量(vector)和字符串(string)作为序列。和in-range一样,这些构造器在给定错误类型的值时会引发异常,并且由于它们会避免运行时调度来确定序列类型,因此可以实现更高效的代码生成; 有关更多信息,请参阅迭代性能

Examples:
> (for ([i (in-string "abc")])
    (display i))

abc

> (for ([i (in-string '(1 2 3))])
    (display i))

in-string: contract violation

  expected: string

  given: '(1 2 3)

11.2 forfor*

更完整的for语法是

(for (clause ...)
  body ...+)
clause=[id sequence-expr]
|#:when boolean-expr
|#:unless boolean-expr

当多个[id sequence-expr]子句在一个for表里提供时,相应的序列并行遍历:

> (for ([i (in-range 1 4)]
        [chapter '("Intro" "Details" "Conclusion")])
    (printf "Chapter ~a. ~a\n" i chapter))

Chapter 1. Intro

Chapter 2. Details

Chapter 3. Conclusion

对于并行序列,for表达式在任何序列结束时停止迭代。这种行为允许in-naturals创造数值的无限序列,可用于索引:

> (for ([i (in-naturals 1)]
        [chapter '("Intro" "Details" "Conclusion")])
    (printf "Chapter ~a. ~a\n" i chapter))

Chapter 1. Intro

Chapter 2. Details

Chapter 3. Conclusion

for*表具有与 for相同的语法,嵌套多个序列,而不是并行运行它们:

> (for* ([book '("Guide" "Reference")]
         [chapter '("Intro" "Details" "Conclusion")])
    (printf "~a ~a\n" book chapter))

Guide Intro

Guide Details

Guide Conclusion

Reference Intro

Reference Details

Reference Conclusion

因此,for*是对嵌套for的一个简写,以同样的方式let*是一个let嵌套的简写。

clause#:when boolean-expr表是另一个简写。仅当boolean-expr产生一个真值时它允许body求值:

> (for* ([book '("Guide" "Reference")]
         [chapter '("Intro" "Details" "Conclusion")]
         #:when (not (equal? chapter "Details")))
    (printf "~a ~a\n" book chapter))

Guide Intro

Guide Conclusion

Reference Intro

Reference Conclusion

#:whenboolean-expr可以适用于任何上述迭代绑定。在for表里,仅仅如果在前面绑定的迭代测试是嵌套的时,这个范围是有意义的;因此,用#:when隔离绑定是多重嵌套的,而不是平行的,甚至于用for也一样。

> (for ([book '("Guide" "Reference" "Notes")]
        #:when (not (equal? book "Notes"))
        [i (in-naturals 1)]
        [chapter '("Intro" "Details" "Conclusion" "Index")]
        #:when (not (equal? chapter "Index")))
    (printf "~a Chapter ~a. ~a\n" book i chapter))

Guide Chapter 1. Intro

Guide Chapter 2. Details

Guide Chapter 3. Conclusion

Reference Chapter 1. Intro

Reference Chapter 2. Details

Reference Chapter 3. Conclusion

#:unless子句和#:when子句是类似的,但仅当boolean-expr产生非值时对body求值。

11.3 for/listfor*/list

for/list表具有与for相同的语法,它对 body求值以获取进入新构造列表的值:

> (for/list ([i (in-naturals 1)]
             [chapter '("Intro" "Details" "Conclusion")])
    (string-append (number->string i) ". " chapter))

'("1. Intro" "2. Details" "3. Conclusion")

for-list表的#:when子句跟body求值一起修剪结果列表:

> (for/list ([i (in-naturals 1)]
             [chapter '("Intro" "Details" "Conclusion")]
             #:when (odd? i))
    chapter)

'("Intro" "Conclusion")

使用for/list#:when修剪行为比for更有用。而对for来说直接的when表通常是满足需要的,for/list里的when表达式表会导致结果列表包含 #<void>以代替省略列表元素。

for*/list表类似于for*,嵌套多个迭代:

> (for*/list ([book '("Guide" "Ref.")]
              [chapter '("Intro" "Details")])
    (string-append book " " chapter))

'("Guide Intro" "Guide Details" "Ref. Intro" "Ref. Details")

for*/list表与嵌套for/list表不太一样。嵌套的for/list将生成一个列表的列表,而不是一个简单列表。非常类似于#:when,而且,for*/list的嵌套比for*的嵌套更有用。

11.4 for/vector and for*/vector

for/vector表可以使用与for/list表相同的语法,但是对body的求值放入一个新构造的向量而不是列表:

> (for/vector ([i (in-naturals 1)]
               [chapter '("Intro" "Details" "Conclusion")])
    (string-append (number->string i) ". " chapter))

'#("1. Intro" "2. Details" "3. Conclusion")

for*/vector表的行为类似,但迭代和for*一样嵌套。

在预先提供的情况下,for/vectorfor*/vector表也允许构造向量的长度。由此产生的迭代可以比直接的for/vectorfor*/vector更有效地执行:

> (let ([chapters '("Intro" "Details" "Conclusion")])
    (for/vector #:length (length chapters) ([i (in-naturals 1)]
                                            [chapter chapters])
      (string-append (number->string i) ". " chapter)))

'#("1. Intro" "2. Details" "3. Conclusion")

如果提供了长度,当向量被填充或被请求完成时迭代停止,而无论哪个先来。如果所提供的长度超过请求的迭代次数,则向量中的剩余位置被初始化为make-vector的缺省参数。

11.5 for/andfor/or

for/and表用and组合迭代结果,一旦遇到#f就停止:

> (for/and ([chapter '("Intro" "Details" "Conclusion")])
    (equal? chapter "Intro"))

#f

for/or表用or组合迭代结果,一旦遇到真(true)值立即停止:

> (for/or ([chapter '("Intro" "Details" "Conclusion")])
    (equal? chapter "Intro"))

#t

与通常一样,for*/andfor*/or表提供与嵌套迭代相同的功能。

11.6 for/firstfor/last

for/first表返回第一次对body进行求值的结果,跳过了进一步的迭代。这个带有一个#:when子句的表是最非常有用的。

> (for/first ([chapter '("Intro" "Details" "Conclusion" "Index")]
              #:when (not (equal? chapter "Intro")))
    chapter)

"Details"

body求值进行零次,那么结果是#f

for/last表运行所有迭代,返回最后一次迭代的值(或如果没有迭代运行返回#f):

> (for/last ([chapter '("Intro" "Details" "Conclusion" "Index")]
              #:when (not (equal? chapter "Index")))
    chapter)

"Conclusion"

通常,for*/firstfor*/last表提供和嵌套迭代相同的工具:

> (for*/first ([book '("Guide" "Reference")]
               [chapter '("Intro" "Details" "Conclusion" "Index")]
               #:when (not (equal? chapter "Intro")))
    (list book chapter))

'("Guide" "Details")

> (for*/last ([book '("Guide" "Reference")]
              [chapter '("Intro" "Details" "Conclusion" "Index")]
              #:when (not (equal? chapter "Index")))
    (list book chapter))

'("Reference" "Conclusion")

11.7 for/foldfor*/fold

for/fold表是合并迭代结果的一种非常通用的方法。由于必须在开始时声明累积变量,它的语法与原来的for语法略有不同:

(for/fold ([accum-id init-expr] ...)
          (clause ...)
  body ...+)

在简单的情况下,仅提供[accum-id init-expr],那么for/fold的结果是accum-id的最终值,并启动了init-expr的值。在clausebodyaccum-id可参照获得其当前值,并且最后的body为下一次迭代的提供accum-id值。

Examples:
> (for/fold ([len 0])
            ([chapter '("Intro" "Conclusion")])
    (+ len (string-length chapter)))

15

> (for/fold ([prev #f])
            ([i (in-naturals 1)]
             [chapter '("Intro" "Details" "Details" "Conclusion")]
             #:when (not (equal? chapter prev)))
    (printf "~a. ~a\n" i chapter)
    chapter)

1. Intro

2. Details

4. Conclusion

"Conclusion"

当多个accum-id被指定,那么最后的body必须产生多值,每一个对应accum-idfor/fold的表达式本身给结果产生多值。

Example:
> (for/fold ([prev #f]
             [counter 1])
            ([chapter '("Intro" "Details" "Details" "Conclusion")]
             #:when (not (equal? chapter prev)))
    (printf "~a. ~a\n" counter chapter)
    (values chapter
            (add1 counter)))

1. Intro

2. Details

3. Conclusion

"Conclusion"

4

11.8 多值序列

同样,函数或表达式可以生成多个值,序列的单个迭代可以生成多个元素。例如,作为序列的哈希表生成两个迭代的两个值:一个键和一个值。

同样方式,let-values将多个结果绑定到多个标识,for能将多个序列元素绑定到多个迭代标识:

let必须改变let-values以绑定多个标识,for只是允许标识列表中的任何子句里的括号代替单个标识。

> (for ([(k v) #hash(("apple" . 1) ("banana" . 3))])
    (printf "~a count: ~a\n" k v))

apple count: 1

banana count: 3

这种对多值绑定的扩展对所有for变体都适用。例如,for*/list嵌套迭代,构建列表,也可以处理多值序列:

> (for*/list ([(k v) #hash(("apple" . 1) ("banana" . 3))]
              [(i) (in-range v)])
    k)

'("apple" "banana" "banana" "banana")

11.9 打断迭代

更完整的for语法是

(for (clause ...)
  body-or-break ... body)
clause=[id sequence-expr]
|#:when boolean-expr
|#:unless boolean-expr
|break
body-or-break=body
|break
break=#:break boolean-expr
|#:final boolean-expr

那是,#:break#:final子句可以包括在迭代的绑定子句和主体之间。在绑定子句中,#:break类似于#:unless,但当其boolean-expr为真时,for中的所有序列都将停止。处在body内,除了当boolean-expr是真时,#:break对序列有一样的效果,并且它也阻止随后的body从当前迭代的求值。

例如,当在有效跳跃后的序列以及主体之间使用#:unless

> (for ([book '("Guide" "Story" "Reference")]
        #:unless (equal? book "Story")
        [chapter '("Intro" "Details" "Conclusion")])
    (printf "~a ~a\n" book chapter))

Guide Intro

Guide Details

Guide Conclusion

Reference Intro

Reference Details

Reference Conclusion

使用#:break子句致使整个for迭代终止:

> (for ([book '("Guide" "Story" "Reference")]
        #:break (equal? book "Story")
        [chapter '("Intro" "Details" "Conclusion")])
    (printf "~a ~a\n" book chapter))

Guide Intro

Guide Details

Guide Conclusion

> (for* ([book '("Guide" "Story" "Reference")]
         [chapter '("Intro" "Details" "Conclusion")])
    #:break (and (equal? book "Story")
                 (equal? chapter "Conclusion"))
    (printf "~a ~a\n" book chapter))

Guide Intro

Guide Details

Guide Conclusion

Story Intro

Story Details

#:final子句类似于#:break,但它不立即终止迭代。相反,它最多地允许为每一个序列和最多再一个 body的求值绘制再一个元素。

> (for* ([book '("Guide" "Story" "Reference")]
         [chapter '("Intro" "Details" "Conclusion")])
    #:final (and (equal? book "Story")
                 (equal? chapter "Conclusion"))
    (printf "~a ~a\n" book chapter))

Guide Intro

Guide Details

Guide Conclusion

Story Intro

Story Details

Story Conclusion

> (for ([book '("Guide" "Story" "Reference")]
        #:final (equal? book "Story")
        [chapter '("Intro" "Details" "Conclusion")])
    (printf "~a ~a\n" book chapter))

Guide Intro

Guide Details

Guide Conclusion

Story Intro

11.10 迭代性能

理想情况下,作为递归函数调用,for迭代的运行速度应该与手工编写的循环一样快。然而,手写循环通常是针对特定类型的数据,如列表。在这种情况下,手写循环直接使用选择器,比如carcdr,而不是处理所有序列表并分派给合适的迭代器。

当足够的信息反复提供给迭代序列时,for表可以提供手写循环的性能。具体来说,子句应具有下列fast-clause表之一:

  fast-clause=[id fast-seq]
|[(id) fast-seq]
|[(id id) fast-indexed-seq]
|[(id ...) fast-parallel-seq]

  fast-seq=(in-range expr)
|(in-range expr expr)
|(in-range expr expr expr)
|(in-naturals)
|(in-naturals expr)
|(in-list expr)
|(in-vector expr)
|(in-string expr)
|(in-bytes expr)
|(in-value expr)
|(stop-before fast-seq predicate-expr)
|(stop-after fast-seq predicate-expr)

  fast-indexed-seq=(in-indexed fast-seq)
|(stop-before fast-indexed-seq predicate-expr)
|(stop-after fast-indexed-seq predicate-expr)

  fast-parallel-seq=(in-parallel fast-seq ...)
|(stop-before fast-parallel-seq predicate-expr)
|(stop-after fast-parallel-seq predicate-expr)

Examples:
> (time (for ([i (in-range 100000)])
          (for ([elem (in-list '(a b c d e f g h))]) ; 
            (void))))

cpu time: 2 real time: 2 gc time: 0

> (time (for ([i (in-range 100000)])
          (for ([elem '(a b c d e f g h)])           ; 
            (void))))

cpu time: 2 real time: 2 gc time: 0

> (time (let ([seq (in-list '(a b c d e f g h))])
          (for ([i (in-range 100000)])
            (for ([elem seq])                        ; 
              (void)))))

cpu time: 19 real time: 19 gc time: 0

上面的语法是不完整的,因为提供良好性能的语法模式集是可扩展的,就像序列值集合一样。序列构造器的文档应该说明直接使用for子句(clause)的性能优势。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值