我正在通过解决问题来学习Clojure,我遇到了with one of them,基本上我必须在日志文件中找到前五个字符串。
这是我到目前为止所得到的:
(ns topfive
(:import (java.io BufferedReader FileReader)))
(defn extract-query [line]
(.substring line (+ (.lastIndexOf line "=") 1) (.lastIndexOf line "]")))
(defn process-file [file-name, queries]
(with-open [rdr (BufferedReader. (FileReader. file-name))]
(doseq [line (line-seq rdr)]
(assoc queries (extract-query line) (inc (get queries (extract-query line) 0))))))
(process-file "in" {})
我的问题是queries
不包含任何内容,我已经检查过extract-queries
返回我想要的字符串,我认为这可能与语言本身有关,{{ 3)} Clojure在语言层面具有不变性,但这对我来说似乎并不是一个好点。
你能否就我做错的事情提出一些建议?
答案 0 :(得分:9)
Clojure确实具有较低级别的不变性,并且哈希映射是不可变的。因此assoc
不会就地改变地图,它会创建一个包含更新项的新地图,并返回新地图。您一遍又一遍地呼叫assoc
,但丢弃结果。
一种解决方法是使用reduce
代替doseq
。 doseq
遍历seq并对每个项目执行某些操作,但不会累积任何结果。所以它应该主要用于有副作用的东西,例如:打印到屏幕或文件。 reduce
类似地迭代seq,但它确实积累了结果。
(defn process-file [file-name, queries]
(with-open [rdr (BufferedReader. (FileReader. file-name))]
(reduce (fn [queries, line]
(assoc queries (extract-query line) (inc (get queries (extract-query line) 0))))
queries
(line-seq rdr))))
你可以做一些事情来进一步简化这一点。 queries
参数不需要process-file
,因为它始终是一个空的地图。您可以使用assoc
和update-in
更简洁地撰写fnil
行。这也让我们避免每行调用extract-query
两次。您可以使用reader
中的Clojure包装器clojure.java.io
替换对Java Reader类的所有调用。您可以使用正则表达式替换对substring
的调用;正则表达式更简洁,但对于大型输入,您的版本可能会执行得更快。您也可以使用#()
替换我的示例中的匿名函数和含糖的阅读器宏版本,虽然它此时开始看起来有点吵,所以我可能会使用let
来使它读取好一点。
(ns topfive
(:require [clojure.java [io :as io]]))
(defn extract-query [line]
(nth (re-find #"query=([^]]+)" line) 1))
(defn process-file [file-name]
(with-open [rdr (io/reader file-name)]
(reduce #(let [search-term (extract-query %2)]
(update-in %1 [search-term] (fnil inc 0)))
{}
(line-seq rdr))))
答案 1 :(得分:1)
除了Brians的优秀答案:线程宏可能会提高可读性:
(ns stackoverflow
(:use [clojure.string :only [split]]
[clojure.java.io :only [reader]]))
(->> (reader "input.txt")
(line-seq)
(map #(last (split % #"=")))
(frequencies))