在迭代序列时特别处理最后一个元素

时间:2012-04-14 03:20:23

标签: clojure iteration sequence

我发现在迭代时我经常需要以特殊的方式处理序列中的最后一个元素。例如,使用内置函数interpose

> (interpose ", " (range 4))

(0 ", " 1 ", " 2 ", " 3)

考虑这个问题的一种方法是:

  • 取第一个元素,添加“,”
  • 取第二个元素,添加“,”
  • 取第三个元素,添加“,”
  • ...
  • 选择倒数第二个元素,添加“,”
  • 选择最后一个元素,不执行任何操作

我还发现在使用Seesaw构建Mig布局时我需要做一些特别的事情。例如,假设我想构建它:

+---+---+---+
| 1 | 2 | 3 |
+---+---+---+
| 4 | 5 | 6 |
+---+---+---+
| 7 | 8 | 9 |
+---+---+---+

其中每个数字是某个组件(例如按钮)。我可以通过制作整个面板“流”的Mig布局约束,然后将以下约束添加到每个组件来实现这一点:

  • “grow” - 组件1,2,4,5,7,8,9
  • “成长,包裹” - 组件3,6

请注意,上面可以这样说:

  • 每个组件“增长”除了行中的最后一个组件
  • “增长,包裹”所有其他组件最后一行中的最后一个组件

再次显示相同的“特殊最后元素”主题。

所以有两个问题:

  • 上述推理有意义吗?即我应该从根本上改变设计,并考虑到上述样本问题和一般主题,以不同的方式考虑这个问题吗?
  • 如果没有,是否有惯用和简短的方法来做到这一点?

是的,你可以创建辅助函数和宏,但我经常发现这一点,我倾向于认为它应该是上面的一个。换句话说 - 你遇到了相同类型的“问题”,你如何解决它们?

3 个答案:

答案 0 :(得分:1)

你正在考虑序列的错误结束。 clojure中的所有序列(实际上是LISP)都是一系列元素序列的元素。

序列处理旨在让您为序列的第一个元素执行某些操作,然后将其余部分视为一个整体(可能是递归的)。实际上,clojure具有名为firstrest的函数来鼓励这种思维方式。

如@Rafal的评论所述,interpose可以是......

  • 采取第一个元素
  • 取第二个元素,先于“,”
  • 取第三个元素,以“,”开头 ...

在clojure中你可以(几乎)用以下方式实现:

(defn interpose [s]
    (cons (first s) (map #(str "," %) (rest s))))

core.clj中的实际实现建立在interleave函数上,这个函数更复杂,因为它处理两个序列,但仍构建在第一个+ rest习惯用法上。

许多(大多数?)算法都适合这种思维方式。

答案 1 :(得分:1)

这里的大多数讨论都是基于你的假设,即所需的功能是“喜欢”。它在某些方面,但最大的区别是,为了优化插入的用途,它是懒惰的。

延迟函数从序列中获取元素,该序列可以是未知(即流)或甚至无限(即范围)长度。仅计算在根函数(即take)处产生值所需的元素。调用last和依赖于last的函数,如count,意味着原始序列需要完全遍历最后或计数才能实现。这就是sw1nn所警告的。

然而,在这种情况下,根据David的回答,元素的数量或最后一个元素的索引可能已经知道了。只要你可以在不使用count的情况下将它作为参数使用,你就可以很容易地创建这样的函数,甚至可以使它变得懒惰。

(def buttons [\a \b \c \d \e \f \g \h \i])

(defn partition-nth-but
  [n b coll]
  (map
    (partial map second)                            ; remove index
    (partition-by
      #(and (integer? (/ (% 0) n)) (not= (% 0) b))  ; partition by index every nth but b
      (map-indexed (fn [i v] [(inc i) v]) coll))))  ; index coll offset 1 

=> (partition-nth-but 3 9 buttons)
((\a \b) (\c) (\d \e) (\f) (\g \h \i))

(def grow str)
(def grow-and-wrap (comp clojure.string/upper-case grow))

=> (map apply (cycle [grow grow-and-wrap]) (partition-nth-but 3 9 buttons))
("ab" "C" "de" "F" "ghi")

但是如果我们正在应用一系列函数,我们也可以循环通过正确的函数重复

(defn every-but-nth
  [n rf nf]
  (concat (repeat (dec n) rf) (repeat 1 nf)))

=> (apply concat
     (every-but-nth 3
       (every-but-nth 3 "grow" "grow-and")
     (repeat 3 "grow")))
("grow" "grow" "grow-and" "grow" "grow" "grow-and" "grow" "grow" "grow")

=> (map
     #(% %2)
     (apply concat (every-but-nth
                     3
                     (every-but-nth 3 grow grow-and-wrap)
                     (repeat 3 grow)))
     buttons)
("a" "b" "C" "d" "e" "F" "g" "h" "i")

答案 2 :(得分:0)

由于您知道输入的大小,例如 n ,因此只需对第一个 n-1 元素执行某些操作。这是初始 interpose 示例的最简单的解决方案。

成长示例中,将 n-1 (或8)个元素包含在3和6处。然后添加 n (9)最后。

但是,您可能并不总是知道输入的大小。如果是这种情况,则可以通过省略第一个元素并仅对剩余元素进行操作来实现相同的结果。这是更普遍的情况,可能更接近于在使用clojure时如何鼓励你思考。