【问题标题】:lisp: function that takes an initial list and diffs it against additional listslisp:获取初始列表并将其与其他列表进行比较的函数
【发布时间】:2019-11-30 03:30:17
【问题描述】:

仍在尝试在 lisp 中重新创建 lodash,以作为熟悉该语言的一种方式。这次我需要算法和句法方面的帮助。

尝试重新创建 _.difference 函数,在 lisp 中,该函数将在输入列表中创建不包含在其他给定列表中的值列表,同时保持原始列表的顺序。

_.difference((2, 1), (2, 3));
// => (1)

不知道如何处理这个问题,我基本上希望能够在遍历后续列表时从initial-list 列表中弹出值。

【问题讨论】:

  • 作为一种简单的方法,您可以循环遍历initial-list,如果您不能find 其他列表中的元素,则循环collect
  • 有道理,你如何跟踪是否在任何后续列表中都找不到元素?
  • 我会写一个函数来找出两个列表的差异——然后折叠或减少所有列表——随后的差异是可交换的
  • 你介意写一些骨架代码来说明吗?部分问题是我不知道在 lisp 中这样做的正确方法是什么。
  • 注意:在提出问题时使用set-difference 减少是不正确的,因为它不能保证顺序。

标签: lisp common-lisp


【解决方案1】:

一种直截了当的方法可以使用一个简单的函数来循环一个列表并收集不在另一个列表中的元素(如果您不想自己实现它,有set-difference)。 reduce 可用于将此函数应用于列表集。

(defun my-diff (l1 l2)
  (loop for x in l1
     unless (find x l2)
     collect x))

(reduce #'my-diff '((2 1 4 3 5) (2 3) (2 0 1)))
;; => (4 5)

【讨论】:

  • 这样做的好处是它很简单。这个函数的缺点是它是 O(nm),n 是输入列表的大小。
【解决方案2】:

这是一个相当明显的实现,它对于大型列表具有良好的性能(并且不假装实现的纯度):

(defun differences (l1 &rest ls)
  ;; This assumes EQL
  (loop with table = (make-hash-table)
        initially (loop for l in ls
                        do (loop for e in l
                                 do (setf (gethash e table) t)))
          for e in l1
          unless (gethash e table nil)
          collect e))

如果列表很小或唯一元素很少,这里有一个类似的版本可能会更好:

(defun differences (l1 &rest ls)
  ;; This assumes EQL
  (loop with uniques = ()
        initially (loop for l in ls
                        do (loop for e in l
                                 do (pushnew e uniques)))
          for e in l1
          unless (member e uniques)
          collect e))

【讨论】:

  • 如果l1 大于其他列表的长度之和,这很好。如果这不是真的,你可以做相反的事情:从l1 的元素中创建一个哈希集,遍历其余部分,从哈希集中删除这些元素(你也可以保留剩余元素的数量,这样你就可以短路如果一切被删除),然后通过l1 再次删除不在您的哈希集中的元素。也许还值得一提的是关于平等测试。这使用了eql,它类似于 JavaScript 的相等性,只是在 JS 中,字符串的比较与string= 在 CL 中的做法和 1 == "1" 中的做法相同
  • @DanRobertson:不幸的是,它比这更复杂。首先,如果不遍历所有列表一次,您就无法选择您想要的算法,这是您不想做的。即使这样,您的列表也比我的多(l1 两次,彼此列表一次),并计算相同数量的哈希(除非您使用计数技巧,当它可能计算较少时)。实际创建的哈希表条目的数量取决于唯一元素的数量,这是您无法提前知道的另一件事。
  • 当然,我不想建议我描述的方法总是更快,或者可以在运行时选择更快的方法,只是有一些原因可能需要一个 Mathis 而不是另一个
【解决方案3】:

使用尾递归:

(defun difference (l1 l2 &key (acc '()) (test #'eql))
  (cond ((null l1) (nreverse acc))
        ((null l2) (nreverse (append (reverse l1) acc)))
        ((member (car l1) l2 :test test) (difference (cdr l1) l2 :acc acc :test test))
        (t (difference (cdr l1) l2 :acc (cons (car l1) acc) :test test))))

使用突变:

(defun difference (l1 l2 &key (test #'eql))
  (let ((res l1))
    (dolist (x l2 res)
      (setf res (remove x res :test test)))))

对于这种必须比较列表中元素的函数, 在 common lisp 中,引入一个可选的&key 是惯例 叫test

lisp 中的等式测试比其他语言更复杂。 所以照顾好他们。

简而言之:

|equality of objects (identity)|#'eq|
|objects (#'eq) and numbers (type dependent) and characters (case sensitive)|#'eql|
|numeric equality (type independent)|#'=|
|character equality (case sensitive)|#'char=|
|string equality (case sensitive)|#'string=|
|#'eql or strings of eql characters or bit vectors of the same contents or lists of equal objects. For other data types #'eq.|#'equal|
|more advanced equality: #'equal objects or numbers (type independent) or characters (case insensitive) or strings (case insensitive). Lists, hashes, arrays and structures are #'equalp if all their elements are #'equalp. For everything else #'eq is used.|#'equalp|

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-21
    • 2020-07-30
    • 1970-01-01
    • 1970-01-01
    • 2022-08-05
    相关资源
    最近更新 更多