我正在通过解决问题来学习Clojure,我坚持使用其中一个,基本上我必须在日志文件中找到前五个字符串.
这是我到目前为止所得到的:
(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" {})
Run Code Online (Sandbox Code Playgroud)
我的问题是queries
不包含任何东西,我已经检查过它extract-queries
返回我想要的字符串,我认为这可能与语言本身有关,我已经读过 Clojure在语言层面具有不变性,但这仍然对我来说似乎不是一个好点.
你能就我的错误提出一些建议吗?
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))))
Run Code Online (Sandbox Code Playgroud)
您可以做一些事情来进一步简化这一点.不需要queries
参数process-file
,因为它始终是一个空的地图.您assoc
可以使用update-in
和更简洁地编写您的专线fnil
; 这也让我们避免extract-query
每行调用两次.您可以替换所有的Java读取器类与Clojure的包装呼叫reader
在clojure.java.io
.您可以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))))
Run Code Online (Sandbox Code Playgroud)