返回以特定频率发生的第一个元素

时间:2013-01-03 17:37:12

标签: clojure

为了好玩,我正在使用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

我的直觉是必须有更好的方式。频率函数非常有用,但此代码的其余部分只是搜索值并返回其键。如果频率函数返回了一个频率为关键的映射而不是值,我可以做((频率等级)n)。

任何人都可以建议一种更可读/更简洁的方法吗?

2 个答案:

答案 0 :(得分:2)

另一个版本

(defn n-of-kind [n ranks]
  (first (filter #(= n (count (filter #{%} ranks)))
                 ranks)))

答案 1 :(得分: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}}