以下代码在problem 19的4closure网站上运行。
我写的函数应该返回序列中的最后一个元素。
此定义发生了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))
上述两个区块的唯一区别似乎是参数列表中破坏性序列绑定 使用解构。
编辑:在第一个函数定义中修复拼写错误
答案 0 :(得分:2)
以下是答案:
(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
你有答案。
答案 1 :(得分:1)
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)