3

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

> (interpose ", " (range 4))

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

一种思考方式是:

  • 取第一个元素,加上“,”
  • 取第二个元素,加上“,”
  • 取第三个元素,加上“,”
  • ...
  • 取倒数第二个元素,添加“,”
  • 取最后一个元素,什么都不做

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

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

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

  • “增长” - 组件 1、2、4、5、7、8、9
  • “增长,包装” - 组件 3、6

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

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

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

所以两个问题:

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

是的,您可以制作辅助函数和宏,但我发现这种情况经常发生,以至于我倾向于认为它应该是上述之一。换句话说 - 您是否遇到相同类型的“问题”以及如何解决它们?

4

3 回答 3

1

这里的大部分讨论都是基于您的假设,即所需的功能是 interpose 'like'。在某些方面是这样,但最大的不同是为了优化中介的使用,它被做成了惰性的。

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

但是,根据大卫的回答,在这种情况下,元素的数量或最后一个元素的索引可能是已知的。只要您可以在不使用 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")
于 2012-04-15T12:03:19.140 回答
1

您正在考虑序列的错误结束。clojure 中的所有序列(实际上是一般的 LISP)都是一个元素,它包含更多元素的序列。

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

正如@Rafal 在评论中指出的那样,interpose 可以被认为是......

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

在 clojure 中,您可以(几乎)通过以下方式实现:

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

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

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

于 2012-04-14T21:46:44.007 回答
0

既然你知道输入的大小,比如n,只需对前n-1 个元素做一些事情。这是您初始插入示例的最简单的解决方案。

在您的增长示例中,在 3 和 6 处增长n-1(或 8)个元素。然后在最后添加n(9)。

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

于 2012-04-14T03:48:51.107 回答