以下代码在qclosure网站上运行problem 19。
我写的函数应该返回序列中的最后一个元素。
此定义发生了java.lang.StackOverflowError:
(fn my-last [lst]
(if (rest lst)
(my-last (rest lst))
(first lst)))
但是,当我运行以下定义时,它运行良好:
(fn my-last [[x & xs]]
(if xs
(my-last xs)
x))
上面两个街区的唯一区别似乎是 破坏性序列结合 在参数列表中使用解构。
编辑:修复第一个函数定义中的拼写错误
这是答案:
(rest [1 2 3]) => (2 3)
(rest [3]) => ()
(next [3]) => nil
使用rest
返回空序列()
,它在您的测试中计算为true
。当没有更多项目时,使用next
返回nil
。由于Clojure认为nil
和false
一样,这将使它成功。
由于许多人在这一点上被绊倒,我宁愿更明确的测试,例如:
(if-not (empty? ...))
或类似的。
更新:
这是我写它的方式。测试东西is from the Tupelo library。
(ns tst.demo.core
(:use tupelo.test)
(:require
[tupelo.core :as t]))
(defn get-last
[items]
(when (empty? items)
(throw (IllegalArgumentException. "get-last: items must not be empty")))
(let [others (rest items)]
(if (empty? others)
(first items)
(get-last others))))
(dotest
(throws? (get-last []))
(is= 1 (get-last [1]))
(is= 2 (get-last [1 2]))
(is= 3 (get-last [1 2 3])))
有些人会坚持认为上面的例子不够“纯粹”,但我认为明确的清晰度每次都会打破隐含的行为。
更新#2
如有疑问,请询问代码它正在做什么:
(defn my-last
[[x & xs]]
(t/spyx {:x x :xs xs})
(if xs
(t/spyx (my-last xs))
(t/spyx x)))
(dotest
(t/spyx (my-last [1 2 3])))
结果:
{:x x, :xs xs} => {:x 1, :xs (2 3)}
{:x x, :xs xs} => {:x 2, :xs (3)}
{:x x, :xs xs} => {:x 3, :xs nil}
x => 3
(my-last xs) => 3
(my-last xs) => 3
(my-last [1 2 3]) => 3
你有答案。
TL; DR回答:第一个版本是创建一个无限循环因为if
语句永远不会是假的,因为(rest [x])
和(rest [])
返回一个真值
专业提示:以下是打破空序列的一些有趣方法:
empty?
seq
(zero? (count coll))
最常用的方法是使用seq
进行解构:
(fn my-last [[x & xs]]
(if (seq xs)
(my-last xs)
x))
但是你也可以在seq
中包装你原来的解决方案,它会起作用:
(fn my-last [lst]
(if (seq (rest lst))
(my-last (rest lst))
(first lst)))
(seq (rest lst))
将返回nil
,这是一个假值,当lst只有一个元素,这是你要检查的。
旁注,你可以解决这个问题的另一种方法是
!得到反向集合的第一个元素,因为问题19只有
last
被禁止!
(comp reverse first)