2

为了好玩,我使用 Peter Norvig 的 Udacity CS212 课程(用 python 授课)作为学习 Clojure 的工具。

在所述课程中,他有一个函数可以返回以指定频率出现的序列的第一个元素:

def kind(n, ranks):
    """Return the first rank that this hand has
    exactly n-of-a-kind of. Return None if there
    is no n-of-a-kind in the hand."""
    for r in ranks:
        if ranks.count(r) == n: return r
    return None

我已经想出了如何在 clojure 中作为单行来做到这一点,但它是多么可怕的单行:

(defn n-of-kind
  [n ranks]
  "Detect whether a hand rank contains n of a kind, returning first
  rank that contains exactly n elements"
  (first (keys (into {} (filter #(= (second %) n) (frequencies ranks))))))

(n-of-kind 3 [5 5 5 3 3]) ;; correctly returns 5

我的直觉是必须有更好的方法。频率函数非常有用,但此代码的其余部分只是搜索一个值并返回其键。如果频率函数返回了一个以频率作为键而不是值的映射,我可以执行类似 ((frequencies ranks) n) 的操作。

任何人都可以提出一种更易读/更简洁的方法吗?

4

2 回答 2

2

另一个版本

(defn n-of-kind [n ranks]
  (first (filter #(= n (count (filter #{%} ranks)))
                 ranks)))
于 2013-01-03T17:49:56.187 回答
1

您可以通过使用值作为键和键作为值来构建新映射来反转映射:

(zipmap (vals my-map) (keys my-map))

使用从频率到键的映射很容易,并且解决了最初的问题,尽管它遇到了出现相同次数的项目消失的问题,因为它们的第二个计数破坏了第一个:

user> (def data (take 20 (repeatedly #(rand-nth [:a :b :c :d :e :f]))))
#'user/data
user> (let [f (frequencies data)] (zipmap (vals f) (keys f)))
{1 :f, 3 :d, 4 :c}
user> (frequencies data)
{:e 4, :b 4, :a 4, :d 3, :c 4, :f 1}

如果您改为从频率到具有该频率的键集的映射列表开始,然后将其减少到单个集合映射,那么尽管代码有点大,但不会丢失任何数据:

user> (reduce (partial merge-with clojure.set/union) 
        (let [f (frequencies data)] 
          (map hash-map (vals f) (map hash-set (keys f)))))

{1 #{:f}, 3 #{:d}, 4 #{:a :c :b :e}}
于 2013-01-03T18:56:03.803 回答