为什么只有当函数不使用序列破坏时才会发生StackOverflowError?

问题描述 投票:1回答:2

以下代码在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))

上面两个街区的唯一区别似乎是 破坏性序列结合 在参数列表中使用解构。

  • 那么第一个定义怎么会抛出错误呢?
  • 我失踪的两个功能之间有什么区别吗?

编辑:修复第一个函数定义中的拼写错误

clojure
2个回答
2
投票

这是答案:

(rest [1 2 3]) => (2 3)
(rest [3]) => ()
(next [3]) => nil

使用rest返回空序列(),它在您的测试中计算为true。当没有更多项目时,使用next返回nil。由于Clojure认为nilfalse一样,这将使它成功。

由于许多人在这一点上被绊倒,我宁愿更明确的测试,例如:

(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
投票

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)

© www.soinside.com 2019 - 2024. All rights reserved.