Анализируя данные с Clojure, проблемой интервала

Я пишу немного синтаксического анализатора в clojure для изучения цели. в основном синтаксический анализатор файла TSV, который должен быть помещен в базу данных, но я добавил сложность. Сама сложность - то, что в том же файле существует больше интервалов. Файл похож на это:

###andreadipersio 2010-03-19 16:10:00###                                                                                
USER     COMM               PID  PPID  %CPU %MEM      TIME  
root     launchd              1     0   0.0  0.0   2:46.97  
root     DirectoryService    11     1   0.0  0.2   0:34.59  
root     notifyd             12     1   0.0  0.0   0:20.83  
root     diskarbitrationd    13     1   0.0  0.0   0:02.84`
....

###andreadipersio 2010-03-19 16:20:00###                                                                                
USER     COMM               PID  PPID  %CPU %MEM      TIME  
root     launchd              1     0   0.0  0.0   2:46.97  
root     DirectoryService    11     1   0.0  0.2   0:34.59  
root     notifyd             12     1   0.0  0.0   0:20.83  
root     diskarbitrationd    13     1   0.0  0.0   0:02.84

Я закончил с этим кодом:

(defn is-header? 
  "Return true  if a line is header"
  [line]
  (> (count (re-find #"^\#{3}" line)) 0))

(defn extract-fields
  "Return regex matches"
  [line pattern]
  (rest (re-find pattern line)))

(defn process-lines
  [lines]
  (map process-line lines))

(defn process-line
  [line]
  (if (is-header? line)
    (extract-fields line header-pattern))
  (extract-fields line data-pattern))

Моя идея состоит в том, который в интервале 'производственной линейки' должен быть объединен с данными, таким образом, у меня есть что-то вроде этого:

('andreadipersio', '2010-03-19', '16:10:00', 'root', 'launchd', 1, 0, 0.0, 0.0, '2:46.97')

для каждой строки до следующего интервала, но я не могу изобразить, как заставить это произойти.

Я попробовал чем-то вроде этого:

(def process-line
  [line]
  (if is-header? line)
    (def header-data (extract-fields line header-pattern)))
  (cons header-data (extract-fields line data-pattern)))

Но это не работает, как исключено.

Какие-либо подсказки?

Спасибо!

10
задан Andrea Di Persio 29 March 2010 в 13:50
поделиться

3 ответа

Вы выполняете (> (count (re-find # "^ \ # {3}" line)) 0) , но можете просто сделать (re-find # " ^ \ # {3} "строка) и используйте результат как логическое значение. re-find возвращает nil , если соответствие не удалось.

Если вы перебираете элементы в коллекции и хотите пропустить некоторые элементы или объединить два или более элементов в оригинале в один элемент в результате, то в 99% случаев вы хотите сократить . Обычно это очень просто.

;; These two libs are called "io" and "string" in bleeding-edge clojure-contrib
;; and some of the function names are different.
(require '(clojure.contrib [str-utils :as s]
                           [duck-streams :as io])) ; SO's syntax-highlighter still sucks

(defn clean [line]
  (s/re-gsub #"^###|###\s*$" "" line))

(defn interval? [line]
  (re-find #"^#{3}" line))

(defn skip? [line]
  (or (empty? line)
      (re-find #"^USER" line)))

(defn parse-line [line]
  (s/re-split #"\s+" (clean line)))

(defn parse [file]
  (first
   (reduce
    (fn [[data interval] line]
      (cond
       (interval? line) [data (parse-line line)]
       (skip? line)     [data interval]
       :else            [(conj data (concat interval (parse-line line))) interval]))
    [[] nil]
    (io/read-lines file))))
4
ответ дан 4 December 2019 в 01:00
поделиться

Я не совсем уверен на основании вашего описания, но, возможно, вы просто ошиблись в синтаксисе. Это то, что вы хотите сделать?

(def process-line [line]
  (if (is-header? line) ; extra parens here over your version
    (extract-fields line header-pattern) ; returning this result
    (extract-fields line data-pattern))) ; implicit "else"

Если цель ваших « cons » состоит в том, чтобы сгруппировать заголовки со связанными с ними подробными данными, вам понадобится дополнительный код для этого, но если он просто попытка «объединить» и вернуть либо заголовок, либо строку с подробностями, в зависимости от того, что это такое, тогда это должно быть правильно.

1
ответ дан 4 December 2019 в 01:00
поделиться

Возможный подход:

  1. Разделите входные данные на строки с помощью line-seq. (Если вы хотите проверить это на строке, вы можете получить line-seq на ней, выполнив (line-seq (java.io.BufferedReader. (java.io.StringReader. test-string))).)

  2. Разделите его на подпоследовательности, каждая из которых содержит либо одну строку заголовка, либо некоторое количество "строк процесса" с помощью (clojure.contrib.seq/partition-by is-header? your-seq-of-lines).

  3. Предполагая, что после каждого заголовка есть хотя бы одна строка процесса, (partition 2 *2) (где *2 - последовательность, полученная в шаге 2 выше) вернет последовательность, напоминающую следующую: (((header-1) (process-line-1 process-line-2)) ((header-2) (process-line-3 process-line-4))). Если входные данные могут содержать несколько строк заголовков, за которыми не следует никаких строк данных, то вышеприведенное может выглядеть как (((header-1a header-1b) (process-line-1 process-line-2)) ...).

  4. Наконец, преобразуйте вывод шага 3 (*3) с помощью следующей функции:


(defn extract-fields-add-headers
  [[headers process-lines]]
  (let [header-fields (extract-fields (last headers) header-pattern)]
    (map #(concat header-fields (extract-fields % data-pattern))
         process-lines)))

(Чтобы объяснить (последние заголовки): единственный случай, когда мы получим здесь несколько заголовков, это когда некоторые из них не имеют собственных линий данных; тот, который фактически присоединен к линиям данных, является последним. )


С этими примерами шаблонов:

(def data-pattern #"(\w+)\s+(\w+)\s+(\d+)\s+(\d+)\s+([0-9.]+)\s+([0-9.]+)\s+([0-9:.]+)")
(def header-pattern #"###(\w+)\s+([0-9-]+)\s+([0-9:]+)###")
;; we'll need to throw out the "USER  COMM  ..." lines,
;; empty lines and the "..." line which I haven't bothered
;; to remove from your sample input
(def discard-pattern #"^USER\s+COMM|^$|^\.\.\.")

Вся "труба" может выглядеть так:

;; just a reminder, normally you'd put this in an ns form:
(use '[clojure.contrib.seq :only (partition-by)])

(->> (line-seq (java.io.BufferedReader. (java.io.StringReader. test-data)))
     (remove #(re-find discard-pattern %)) ; throw out "USER  COMM ..."
     (partition-by is-header?)
     (partition 2)
     ;; mapcat performs a map, then concatenates results
     (mapcat extract-fields-add-headers))

line-seq, предположительно принимающим входные данные из другого источника в вашей конечной программе. )

С вашим примером ввода, вышеупомянутое производит вывод, подобный этому (разрывы строк добавлены для ясности):

(("andreadipersio" "2010-03-19" "16:10:00" "root" "launchd" "1" "0" "0.0" "0.0" "2:46.97")
 ("andreadipersio" "2010-03-19" "16:10:00" "root" "DirectoryService" "11" "1" "0.0" "0.2" "0:34.59")
 ("andreadipersio" "2010-03-19" "16:10:00" "root" "notifyd" "12" "1" "0.0" "0.0" "0:20.83")
 ("andreadipersio" "2010-03-19" "16:10:00" "root" "diskarbitrationd" "13" "1" "0.0" "0.0" "0:02.84")
 ("andreadipersio" "2010-03-19" "16:20:00" "root" "launchd" "1" "0" "0.0" "0.0" "2:46.97")
 ("andreadipersio" "2010-03-19" "16:20:00" "root" "DirectoryService" "11" "1" "0.0" "0.2" "0:34.59")
 ("andreadipersio" "2010-03-19" "16:20:00" "root" "notifyd" "12" "1" "0.0" "0.0" "0:20.83")
 ("andreadipersio" "2010-03-19" "16:20:00" "root" "diskarbitrationd" "13" "1" "0.0" "0.0" "0:02.84"))
6
ответ дан 4 December 2019 в 01:00
поделиться
Другие вопросы по тегам:

Похожие вопросы: