2010-03-29 41 views
10

मैं सीखने के उद्देश्य के लिए क्लोजर में थोड़ा पार्सर लिख रहा हूं। मूल रूप से एक टीएसवी फ़ाइल पार्सर है जिसे डेटाबेस में डालने की आवश्यकता है, लेकिन मैंने एक जटिलता जोड़ा। जटिलता स्वयं ही है कि एक ही फ़ाइल में अधिक अंतराल होते हैं। इस तरह फ़ाइल देखो:क्लोजर के साथ डेटा पार्सिंग, अंतराल समस्या

(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###                     
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 

मैं इस कोड के साथ समाप्त हो गया इस:

('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))) 

लेकिन जैसे अपवादित यह काम नहीं करता।

कोई संकेत?

धन्यवाद!

+2

बीटीडब्लू, शीर्ष स्तर को छोड़कर 'def' का उपयोग न करें जबतक कि आप वास्तव में नहीं जानते कि आप क्या कर रहे हैं! और निश्चित रूप से इसे म्यूटेबल स्टोरेज के लिए कभी भी इस्तेमाल न करें। इसके बजाय एक रेफ या एटम का प्रयोग करें। –

+0

धन्यवाद, यह एक अनमोल संकेत है! –

+0

मुझे उम्मीद है कि आप rubylearning.org (क्लोजर 101 कोर्स के लिए) पर एक अभ्यास के लिए आधार बनने के लिए ठीक हैं? मुझे लगता है कि काम करने के लिए यह एक बहुत अच्छी समस्या है। –

उत्तर

4

आप (> (count (re-find #"^\#{3}" line)) 0) कर रहे हैं, लेकिन आप केवल (re-find #"^\#{3}" line) कर सकते हैं और परिणाम को बूलियन के रूप में उपयोग कर सकते हैं। यदि मैच विफल रहता है तो re-findnil देता है।

यदि आप किसी संग्रह में आइटम पर पुनरावृत्ति कर रहे हैं, और आप कुछ आइटम छोड़ना चाहते हैं या मूल में दो या दो से अधिक वस्तुओं को परिणाम में जोड़ना चाहते हैं, तो 99% बार आप reduce चाहते हैं। यह आमतौर पर बहुत सरल होने के समाप्त होता है।

;; 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)))) 
+0

यह बहुत अच्छा है। फिलहाल सबसे अच्छा समाधान है जिसे मैं सोच सकता हूं और छोटा भी। बहुत बहुत धन्यवाद। –

+2

यह उदाहरण पर हाथ पर कोई असर नहीं हो सकता है, लेकिन मैं इस तरह के कार्यों के लिए 'कम करने' की उपयुक्तता के बारे में बयान से सहमत नहीं हूं। क्लोजर 'कम करने' में हमेशा सख्त होता है कि यह प्रसंस्करण के लिए उपलब्ध होने से पहले यह पूरे परिणाम में स्मृति को हमेशा पूरा कर देगा (क्योंकि क्लोजर का 'कमी' बाएं गुना है)। यह एक ऐसे दृष्टिकोण के विपरीत है जहां आलसी परिवर्तन एक दूसरे के शीर्ष पर (ढेर के नीचे इनपुट अनुक्रम के साथ) स्तर पर हैं, जहां परिणाम भाग में उत्पादित किए जा सकते हैं। –

+0

इसके अलावा, मुझे लगता है कि 'कम करने' के साथ उपयोग के लिए एक जटिल कार्य लिखने से कोड को पाइप जैसी दृष्टिकोण की तुलना में कम पठनीय और ट्विक करने योग्य बनाता है, हालांकि यह स्वाद का मामला हो सकता है और निश्चित रूप से किसी भी दिए गए विवरणों पर निर्भर करेगा मामला। और न ही मैं गलत प्रभाव डालता हूं, मैं किसी भी तरह से 'कम करने' को भंग नहीं कर रहा हूं, यह सिर्फ अनुक्रमों को अन्य अनुक्रमों में बदलने के विशेष मामले के लिए है, मुझे लगता है कि यह पहली पसंद के बजाय अंतिम उपाय का साधन है। –

1

मुझे आपके विवरण के आधार पर पूरी तरह से यकीन नहीं है, लेकिन शायद आप सिंटैक्स पर फिसल रहे हैं। क्या आप यही करना चाहते हैं?

(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" समूह के लिए है एक साथ उनके संबद्ध विस्तार डेटा के साथ हेडर के इरादे, आपको लगता है कि पूरा करने के लिए कुछ और कोड की आवश्यकता होगी, लेकिन अगर यह "वालों" और या तो लौटने पर बस का प्रयास है, तो एक शीर्षलेख या विस्तार रेखा जिस पर निर्भर करता है, तो यह सही होना चाहिए।

+0

आपकी प्रतिक्रिया के लिए धन्यवाद, अगर फॉर्म आउटपुट पर सिंटैक्स समस्या को ठीक कर रहा है, लेकिन मुझे अभी भी दोनों अनुक्रमों को मर्ज करने का सही तरीका ढूंढना है (यह आपके द्वारा वर्णित पहला मामला है)। ps मेरे विवरण के लिए खेद है, मैं सामान्य रूप से क्लोजर और कार्यात्मक प्रोग्रामिंग के साथ एक नौसिखिया हूं इसलिए मैंने गलत शर्तों का उपयोग किया होगा। –

6

एक संभावित दृष्टिकोण:

  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) के साथ। (((header-1) (process-line-1 process-line-2)) ((header-2) (process-line-3 process-line-4))):

  3. वहाँ प्रत्येक हेडर के बाद कम से कम एक प्रक्रिया लाइन मानते हुए, (partition 2 *2) (जहां *2 अनुक्रम ऊपर चरण 2 में प्राप्त है) एक रूप निम्नलिखित जैसी का एक अनुक्रम वापस आ जाएगी। यदि इनपुट में कुछ शीर्षलेख रेखाएं हो सकती हैं जो किसी भी डेटा लाइन के बाद नहीं होती हैं, तो उपर्युक्त (((header-1a header-1b) (process-line-1 process-line-2)) ...) जैसा दिख सकता है।


(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))) 

((last headers) बिट व्याख्या करने के लिए:

  • अंत में, निम्नलिखित समारोह के साथ कदम 3 (*3) के उत्पादन को बदलने केवल इस मामले में जहां हम कई मिल जाएगा यहां शीर्षलेख तब होते हैं जब उनमें से कुछ की अपनी कोई डेटा लाइन नहीं होती है; वास्तव में डेटा लाइनों से जुड़ा एक अंतिम होता है।)


    इन उदाहरण पैटर्न के साथ:

    (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")) 
    
  • +0

    बहुत बहुत धन्यवाद। यह एक आकर्षण की तरह काम करता है और मैंने दो उपयोगी फ़ंक्शन सीखा: मैपकैट और विभाजन। फिर से धन्यवाद। –

    +0

    आपका स्वागत है! ध्यान दें कि मैंने इस मामले को सही ढंग से संभालने के लिए एक और संपादन किया है जहां कुछ शीर्षकों के पास उनके बाद डेटा लाइन नहीं हो सकती है। –

    +0

    हाँ मैंने इसे देखा! धन्यवाद। –

    संबंधित मुद्दे