【问题标题】:Does "map" necessarily produce an additional level of nesting?“地图”是否一定会产生额外的嵌套级别?
【发布时间】:2021-06-23 22:41:14
【问题描述】:

使用嵌套的maps 会自动创建另一层嵌套吗?这是我使用的一个基本示例:

; One level
(map (lambda (x1)
   "Hi")
 '(1))

; Two levels
(map (lambda (x1)
   (map (lambda (x2)
          "Hi")
        '(1)))
     '(1))

; Three levels
(map (lambda (x1)
   (map (lambda (x2)
      (map (lambda (x3)
          "Hi")
           '(1)))
        '(1)))
     '(1))

; Four levels
(map (lambda (x1)
   (map (lambda (x2)
      (map (lambda (x3)
         (map (lambda (x4)
          "Hi")
           '(1)))
        '(1)))
     '(1)))
   '(1))
("Hi")
(("Hi"))
((("Hi")))
(((("Hi"))))

或者,有没有反例添加另一个map 不会产生额外的嵌套?我无法“了解”map 如何增加另一个级别。例如,对于第一级:

(map (lambda (x1)
   "Hi")
 '(1))

我知道列表中有一个元素,并且“对于每个元素”在列表中 - 我们将在列表中的该位置返回元素 "Hi",因此对于第一级,我们得到 ("Hi") .

但是,例如,我们如何从列表("Hi") 中获得第二级的嵌套列表?我知道我已经问了很多与map 和嵌套相关的问题,但希望如果我能理解从1 级别到2 的变化,我可以弄清楚其余的......

【问题讨论】:

  • map 返回结果列表。如果你嵌套它们,你会得到一个嵌套列表。

标签: scheme lisp racket sicp


【解决方案1】:

map 收集所有函数调用的返回值,并将它们包装在另一个列表中。

如果函数返回一个列表,你会得到一个列表列表。

因此,如果您有嵌套地图,您总是会得到嵌套列表作为最终结果。而且每一级映射都会增加另一级嵌套。

请注意,仅当您实际上在每个级别返回 map 的值时,这才是正确的。你可以用它做其他事情,例如

;; Two levels
(map (lambda (x1)
       (length (map (lambda (x2)
                      "Hi")
                    '(1))))
     '(1))

这将返回(1)

【讨论】:

    【解决方案2】:

    一对一

    你的例子包括(map (lambda (x) x) ...)

    (map (lambda (ignore-me)
           (map (lambda (x) x) ; <- no-op
                '(1 2 3)))
         '(1))
    
    ((1 2 3))
    

    “很难看到”,因为内部 map 本质上是一个空操作。 map 在输入和输出之间创建 1:1 关系,对映射过程的作用没有任何意见。如果映射过程返回单个元素、列表或非常嵌套的列表,则无关紧要 -

    (map (lambda (ignore-me)
           '(1 2 3))
         '(foo))
    
    ((1 2 3))
    
    (map (lambda (_)
           '(((((really nested))))))
         '(1 2 3))
    
    ((((((really nested)))))
     (((((really nested)))))
     (((((really nested))))))
    

    或者使用一些 ascii 可视化 -

    (define (fn x) (+ x x))
    
    (map fn '(1 2 3))
    ;          \ \ \
    ;           \ \ (fn 3)
    ;            \ \      \
    ;             \ (fn 2) \
    ;              \      \ \
    ;               (fn 1) \ \
    ;                     \ \ \
    ;  output:           '(2 4 6) 
    

    你是否应该选择将map里面的映射程序到另一个map调用-

    (map (lambda (...)
           (map ...)) ; <- nested map
         ...)
    

    同样的规则适用。每个map 输出与其输入列表具有1:1 关系。我们可以想象map的类型以获得更多洞察力-

    map : ('a -> 'b, 'a list) -> 'b list
           --------  -------     -------
             \        \           \_ returns a list of type 'b
              \        \
               \        \__ input list of type 'a
                \
                 \__ mapping procedure, maps
                     one element of type 'a
                     to one element of type 'b
    

    实施地图

    作为一项学习练习,实施map 有助于深入了解其工作原理 -

    (define (map fn lst)
      (if (null? lst)
          '()
          (cons (fn (car lst))
                (map fn (cdr lst)))))
    
    (map (lambda (x)
           (list x x x x x))
         '(1 2 3))
    
    ((1 1 1 1 1)
     (2 2 2 2 2)
     (3 3 3 3 3))
    

    如您所见,maplst 中的元素类型或fn 的返回值是什么没有意见。 map 将列表的car 传递给fncons'es 到列表的cdr 的递归结果。


    附加地图

    我们应该查看的另一个相关过程是append-map -

    append-map : ('a -> 'b list, 'a list) -> 'b list
                  -------------  -------     -------
                    \             \           \_ returns a list of type 'b
                     \             \_ input list of type 'a
                      \
                       \_ mapping function, maps
                          one element of type 'a
                          to a LIST of elements of type 'b

    这里唯一的区别是映射过程应该返回一个元素的列表,而不仅仅是一个元素。这样,append-map 在输入列表和输出列表之间创建了 1:many 关系。

    (append-map (lambda (x)
                  (list x x x x x))
                '(1 2 3))
    
    (1 1 1 1 1 2 2 2 2 2 3 3 3 3 3)
    

    append-map 的这一特性是它有时被称为flatmap 的原因,因为它“扁平化”了一层嵌套 -

    (append-map (lambda (x)
                  (map (lambda (y)
                         (list x y))
                       '(spade heart club diamond)))
                '(J Q K A))
    
    ((J spade)
     (J heart)
     (J club)
     (J diamond)
     (Q spade)
     (Q heart)
     (Q club)
     (Q diamond)
     (K spade)
     (K heart)
     (K club)
     (K diamond)
     (A spade)
     (A heart)
     (A club)
     (A diamond))
    

    为读者准备的后续练习:

    • 如果我们在上面的示例中将append-map 换成map,输出会是什么?
    • 以另一种或两种方式定义map。验证正确的行为
    • 根据map 定义append-map。不使用map 再次定义它。

    松散的打字

    Scheme 是一种无类型语言,因此列表的内容不需要是同质的。尽管如此,以这种方式考虑 mapappend-map 还是很有用的,因为类型有助于传达函数的行为方式。以下是 Racket 提供的更准确的类型定义 -

    (map proc lst ...+) → list?
      proc : procedure?
      lst : list?
    
    (append-map proc lst ...+) → list?
      proc : procedure?
      lst : list?
    

    这些要松散得多,并且确实反映了您可以实际编写的松散程序。比如——

    (append-map (lambda (x)
                  (list 'a-symbol "hello" 'float (* x 1.5) 'bool (> x 1)))
                '(1 2 3))
    
    (a-symbol "hello" float 1.5 bool #f a-symbol "hello" float 3.0 bool #t a-symbol "hello" float 4.5 bool #t)
    

    variadic map 和 append-map

    你在上面的类型中看到那些...+ 了吗?为了简单起见,到目前为止,我一直在隐藏一个重要的细节。 map 的可变参数接口意味着它可以接受 1 个或更多输入列表 -

    (map (lambda (x y z)
           (list x y z))
         '(1 2 3)
         '(4 5 6)
         '(7 8 9))
    
    ((1 4 7) (2 5 8) (3 6 9))
    
    (append-map (lambda (x y z)
                  (list x y z))
                '(1 2 3)
                '(4 5 6)
                '(7 8 9))
    
    (1 4 7 2 5 8 3 6 9)
    

    为读者准备的后续练习:

    • 定义可变参数map
    • 定义可变参数append-map

    一点点 lambda 演算

    您了解 lambda 演算。您知道(lambda (x y z) (list x y z)list 相同吗?这被称为Eta reduction -

    (map list '(1 2 3) '(4 5 6) '(7 8 9))
    
    ((1 4 7) (2 5 8) (3 6 9))
    
    (append-map list '(1 2 3) '(4 5 6) '(7 8 9))
    
    (1 4 7 2 5 8 3 6 9)
    

    分隔延续

    Remember when 我们谈到了delimited continuations 和运营商shiftreset?了解了append-map,再来看看ambambiguous choice operator-

    (define (amb . lst)
      (shift k (append-map k lst)))
    

    我们可以像这样使用amb -

    (reset (list (amb 'J 'Q 'K 'A) (amb '♡ '♢ '♤ '♧)))
    
    (J ♡ J ♢ J ♤ J ♧ Q ♡ Q ♢ Q ♤ Q ♧ K ♡ K ♢ K ♤ K ♧ A ♡ A ♢ A ♤ A ♧)
    
    (reset (list (list (amb 'J 'Q 'K 'A) (amb '♡ '♢ '♤ '♧))))
    
    ((J ♡) (J ♢) (J ♤) (J ♧) (Q ♡) (Q ♢) (Q ♤) (Q ♧) (K ♡) (K ♢) (K ♤) (K ♧) (A ♡) (A ♢) (A ♤) (A ♧))
    

    在一个更有用的例子中,我们使用pythagorean theorem to find-right-triangles -

    (define (pythag a b c)
      (= (+ (* a a) (* b b)) (* c c))) ; a² + b² = c²
    
    (define (find-right-triangles . side-lengths)
      (filter ((curry apply) pythag)
              (reset (list
                       (list (apply amb side-lengths)
                             (apply amb side-lengths)
                             (apply amb side-lengths))))))
    
    (find-right-triangles 1 2 3 4 5 6 7 8 9 10 11 12)
    
    ((3 4 5) (4 3 5) (6 8 10) (8 6 10))
    

    定界延续有效地把你的程序从里到外,让你专注于声明性结构 -

    (define (bit)
      (amb #\0 #\1))
    
    (define (4-bit)
      (reset (list (string (bit) (bit) (bit) (bit)))))
    
    (4-bit)
    
    ("0000" "0001" "0010" "0011" "0100" "0101" "0110" "0111" "1000" "1001" "1010" "1011" "1100" "1101" "1110" "1111")
    

    amb 可以在任何表达式中的任何位置使用,以对每个提供的值计算一次表达式。这种无限的使用可以产生非凡的结果-

    (reset
      (list
        (if (> 5 (amb 6 3))
            (string-append "you have "
                           (amb "dark" "long" "flowing")
                           " "
                           (amb "hair" "sentences"))
            (string-append "please do not "
                           (amb "steal" "hurt")
                           " "
                           (amb "any" "other")
                           " "
                           (amb "people" "animals")))))
    
    ("please do not steal any people"
     "please do not steal any animals"
     "please do not steal other people"
     "please do not steal other animals"
     "please do not hurt any people"
     "please do not hurt any animals"
     "please do not hurt other people"
     "please do not hurt other animals"
     "you have dark hair"
     "you have dark sentences"
     "you have long hair"
     "you have long sentences"
     "you have flowing hair"
     "you have flowing sentences")
    

    【讨论】:

    • 这个肯定不是“模糊”的。 :)
    • "超过评论限制" lmao :D
    【解决方案3】:

    map 将为该列表中的每个元素返回一个列表。让我们通过一个多地图示例来展示嵌套如何发挥作用。

    首先,让我们对一个数字序列执行一个map,然后对它们“什么都不做”。也就是说,只需从输入中返回元素:

    (map (lambda (x) x)
         '(1 2 3))
    ; (1 2 3)
    

    此时,如果我们添加另一个map,可能很容易认为不应该发生另一个嵌套级别,因为这里我们没有“嵌套”输入列表。它只是“停留”在(1 2 3)

    但是如果我们添加一个外层,我们可以看到这是如何发生的:

    (map (lambda (ignore-me)
           (map (lambda (x) x)
                '(1 2 3)))
         '(1))
    ; (( 1 2 3 ))
    

    只有一个“外部元素”很难看出这一点,但是,让我们添加 5 个外部元素并使其更容易被发现:

    (map (lambda (ignore-me)
           (map (lambda (x) x)
                '(1 2 3)))
         '(5 5 5 5 5))
    ;(
    ;  (1 2 3) 
    ;  (1 2 3) 
    ;  (1 2 3) 
    ;  (1 2 3) 
    ;  (1 2 3)
    ;)
    

    在这里,我们可以看到 每个 外部元素 - 其中有五个 - 我们返回“内部结果” - 在上述情况下为 (1 2 3)。如果我们再次做同样的事情,这一次产生两个额外的外部循环,我们可以看到类似的结果:

    (map (lambda (ignore-me1)
    (map (lambda (ignore-me2)
           (map (lambda (x) x)
                '(1 2 3)))
         '(5 5 5 5 5)))
         '(2 2))
    ; (
    ;   ((1 2 3) (1 2 3) (1 2 3) (1 2 3) (1 2 3)) 
    ;   ((1 2 3) (1 2 3) (1 2 3) (1 2 3) (1 2 3))
    ; )
    

    【讨论】:

    • 非常漂亮和详细。
    猜你喜欢
    • 2021-02-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-20
    • 2015-03-25
    • 2018-10-16
    • 2021-02-16
    • 2018-02-14
    相关资源
    最近更新 更多