Parser CSV en clojure

18 views
Skip to first unread message

Christian Sperandio

unread,
Jul 25, 2012, 5:39:01 PM7/25/12
to clojure-pari...@googlegroups.com
Bonsoir à tous,

Je me suis mis à Clojure tout récemment :$ Après avoir lu le livre Practical Clojure et lisant actuellement Clojure Programming, j'ai décidé de faire un petit poc.
Dans mon travail, nous traitons souvent des données provenant de fichiers CSV. Je me suis donc mis à travailler sur un parser CSV.
Je vous présente donc mon petit projet et j'aimerai connaitre l'avis de personnes plus expérimentées que moi (du genre, est-ce que je suits les best practices du langage).

Voici une petite description de mon projet:
  • Détection des enregistrements sur plusieurs lignes
  • Gestion des délimiteurs et séparateurs de champs

Je fournis le source principal ainsi qu'un source de test dans l'archive. Pour info, j'ai développé avec la version 1.3 de Clojure.

Dans mes tests, je simule le contenu d'un fichier dans un vecteur (afin de faciliter les tests et les validers).

En espèrant ne pas avoir trop écorché le langage et si cela peut servir à d'autres personnes.



parser-src.zip

Bertrand Dechoux

unread,
Jul 26, 2012, 4:04:50 AM7/26/12
to clojure-pari...@googlegroups.com, clojur...@googlegroups.com
Bonjour,

1) J'ai mis le CUG Lyon en copie.

2) Si tu expose le code sur un repository github, tu pourrais avoir plus de réponses.

3) Tu pourrais être intéressé par Incanter. Cette librairie gère déjà le parsing de csv (en réutilisant open CSV) et permet de faire plein de stats, en clojure.
-> http://incanter.org
-> https://github.com/liebke/incanter

4) Par curiosité, je regarderai quand j'aurai le temps.

Bertrand

2012/7/25 Christian Sperandio <christian...@gmail.com>



--
Bertrand Dechoux

Christophe Grand

unread,
Jul 26, 2012, 4:09:20 AM7/26/12
to clojure-pari...@googlegroups.com, clojur...@googlegroups.com
Salut,

A propos du 2) un gist peut suffire et est moins "lourd" à créer qu'une repo classique.

J'ai trop rapidement regardé le code mais :
* j'estime que la fabrication de regex complexifie ta solution par rapport à de la simple recherche de sous-chaîne
* essaye de te passer de loop/recur

Christophe

2012/7/26 Bertrand Dechoux <dech...@gmail.com>



--
Professional: http://cgrand.net/ (fr)
On Clojure: http://clj-me.cgrand.net/ (en)

Laurent PETIT

unread,
Jul 26, 2012, 4:54:01 AM7/26/12
to clojure-pari...@googlegroups.com

Bonjour, et Welcome to Clojure :-D

J'avais préparé une réponse plus complète, mais Bertrand et Christophe ont déjà dit beaucoup, je vais donc juste compléter :

- Si tu fais plus qu'un petit essai dans un coin, alors créer un vrai projet sous GitHub avec un fichier Leiningen project.clj permet de faciliter la vie aux personnes que tu sollicites : très facile alors de regarder un peu ton code, jouer avec en lançant le projet via Lein repl, etc.

- ce genre de librairie est presque un cas d'école pour l'utilisation de tests unitaires automatisés
  - parce que c'est très facile à écrire (dès lors que tu isoles comme tu l'as fait la fonction principale d'analyse d'une ligne de la manière dont la ligne est récupérée, cette fonction est facilement testable)
  - parce qu'une librairie csv, avec ses innombrables cas particuliers, appelle vraiment des tests unitaires automatisés

Je pense donc que ton projet gagnerait énormément à avoir de vrais tests automatisés (juste utiliser clojure.test est largement suffisant pour une librairie csv je pense, car il s'agit plus d'avoir une suite de non-régression).

Enfin, comme le disait Bertrand, il existe déjà plusieurs librairies csv en Clojure, soit "wrappant" des librairies java existantes comme open-csv, soit réécrites de zéro (une recherche dans la ml clojure devrait te retourner les versions les plus récentes).
Bien sûr, je comprends l'intérêt de l'exercice, mais si ton objectif (comme ton email le suggérait à demi mot) est de parser du csv pour un projet "de prod", alors à la fin de l'exercice à ta place j'utiliserai une des librairies déjà existantes.

Quelques remarques moins importantes sur le code :
Trop décomposer en variables peut autant nuire à la lisibilité que pas assez décomposer. Ainsi, tu pourrais enlever une ligne sur 2 ici :
(let [quoted-field-pattern-str (str field-delimiter ".*" field-delimiter)
       quoted-field-pattern (re-pattern quoted-field-pattern-str)
       ...] ...)

(let [quoted-field-pattern (re-pattern (str field-delimiter ".*" field-delimiter))
       ...] ...)

Mais ailleurs, j'ai trouvé difficile de suivre le code car les lignes sont vraiment longues (plus de 140 chars contenant de multiples expressions:
    (loop [parts (s/split csv-record (re-pattern field-separator)) , fields [] , incomplete-field [] ]
      (cond
        (nil? parts) fields
        (and (empty? incomplete-field) (re-matches quoted-field-pattern (first parts))) (recur (next parts) (conj fields (clean-field (first parts))) [])
        (and (empty? incomplete-field) (or (empty? (first parts)) (simple-field-pattern? (first parts)))) (recur (next parts) (conj fields (first parts)) [])
        (re-matches quoted-end-field-pattern (first parts)) (recur (next parts) (conj fields (clean-field (s/join field-separator (conj incomplete-field (first parts))))) [])
        :else (recur (next parts) fields (conj incomplete-field (first parts)))
        )
      ))

Quand les expressions dans un (and) sont longues, les mettre chacune sur une ligne aide bien à la relecture
Dans un cond, si la partie conditionnelle est longue, ce que je fais souvent c'est mettre alors l'expression associée sur la ligne suivante, réindentée de 2 espaces

Propreté du code : à partir du moment où tu sollicites une relecture, il est agréable pour les relecteurs de lire un code "propre", par ex. avec les parenthèses fermantes rassemblées (il m'arrive aussi de laisser pendant le dev des parenthèses fermantes sur leur propre ligne pour plus facilement faire du copier/coller par ligne, ajouter une nouvelle ligne dans un doseq, un cond, etc., mais à la fin je trouve que tout rassembler fait vraiment plus propre)

Par ex., la fonction complete-record est vraiment difficile à la lire uniquement à cause d'une mauvaise indentation et de trop de retours à la ligne :
(defn complete-record?
  "Returns if a line is a complete record or finalizes a complete record."
  ([line] (let [c (seq line) initial-status (if (= (first c) \") false true) ]
          (complete-record? initial-status line))
  )

  ([initial-status line] (let [c (seq line) ]
                         (if (reduce new-open-status initial-status (partition 2 1 c)) (= (last c) \") true))
  )
 )

Il suffirait de peu de choses pour que ça pique moins les yeux:

https://gist.github.com/3181088


Bonne chance, et surtout ne te décourage pas, on est tous passés par là au début.

--
Laurent






Christian Sperandio

unread,
Jul 26, 2012, 5:26:26 AM7/26/12
to clojure-pari...@googlegroups.com
Merci pour tes remarques (je remercie également Bertrand et Christophe) :)

Je reconnais que mon code était difficile à lire et les propositions faites me permettent de voir comment mieux organiser celui-ci. 

Je n'avais pas pensé à utiliser lein. Pour ce premier test, j'ai juste lancé IntelliJ et plongé la tête la première dans Clojure :)

Une remarque de Christophe m'intrigue, pourquoi faut-il éviter l'utilisation de loop/recur?

Et encore une fois merci :)



Christian

Hiram MADELAINE

unread,
Jul 26, 2012, 5:40:17 AM7/26/12
to clojure-pari...@googlegroups.com
Bonjour Christian,

Loop/recur est de très bas niveau, il est préférable d'utiliser quand on le peut les fonctions d'ordre supérieur (HOF's) comme map reduce.
Ton code gagnera grandement en lisibilité.

Hope it helps

Hiram
--
Hiram MADELAINE
96, rue La Fayette 75010 Paris
+33 6 11 51 37 71

david humphreys

unread,
Jul 26, 2012, 7:26:29 AM7/26/12
to clojure-pari...@googlegroups.com
...
3) Tu pourrais être intéressé par Incanter. Cette librairie gère déjà
le parsing de csv (en réutilisant open CSV) et permet de faire plein
de stats, en clojure.
-> http://incanter.org
-> https://github.com/liebke/incanter
...

There is also in Clojure contrib
[http://dev.clojure.org/display/doc/Clojure+Contrib]
data.csv [https://github.com/clojure/data.csv] for CSV reading and writing

2012/7/26 Hiram MADELAINE <hiram.m...@gmail.com>:

Christian Sperandio

unread,
Jul 26, 2012, 7:35:02 AM7/26/12
to clojure-pari...@googlegroups.com
Thanks David,
I suspected this sort of library already exist. I would to make a
study case on a practical need. To see if I could :)
But I keep your link in my mind because, for my needs, Incanter is
like use a canon to kill a fly :) Data CSV is more what I need.

Thanks a lot again.


Le 26 juillet 2012 13:26, david humphreys
<davidjame...@gmail.com> a écrit :

Denis Labaye

unread,
Jul 26, 2012, 7:35:27 AM7/26/12
to clojure-pari...@googlegroups.com
Hello Christian, 

J'ai pas regarde ton code mais je trouve ca tres bien d'implementer un petit projet quand on apprend une nouvelle technologie.

C'est toujours tres formatteur et ca permet de voir des points de details de la techno (bon ou mauvais :).

En tout cas il semble que tu as ete au bout: Un fichier en entree => une structure de donnee tabulaire en sortie. C'est deja super bien!

Pour ce qui est de la re-utilisation de ton code, je pense que tu ais fait "mieux" (a moins que ...) : https://github.com/davidsantiago/clojure-csv.

Je ne t'ai pas trouve sur https://github.com ? Tu n'y ais pas ? C'est un des meilleurs moyen pour partger du code / des idees ...

A+

Denis

2012/7/25 Christian Sperandio <christian...@gmail.com>

Christian Sperandio

unread,
Jul 26, 2012, 7:41:20 AM7/26/12
to clojure-pari...@googlegroups.com
Je ne suis pas encore sur GitHub :) Disons que je n'ai pas encore
d'expérience avec Git (je suis pour l'instant un grand utilisateur de
SVN).
L'implémentation de ce genre de petit projet (faut être modeste) me
permet de voir un peu mieux l'écosystème du développement avec
Clojure.
Je viens de mettre en place le plugin leiningen pour IntelliJ et
l'étape suivante va être l'utilisation de Git :)

Merci à tous pour vos conseils et remarques :)

Denis Labaye

unread,
Jul 26, 2012, 7:46:36 AM7/26/12
to clojure-pari...@googlegroups.com


2012/7/26 Christian Sperandio <christian...@gmail.com>

Je ne suis pas encore sur GitHub :) Disons que je n'ai pas encore
d'expérience avec Git (je suis pour l'instant un grand utilisateur de
SVN).
L'implémentation de ce genre de petit projet (faut être modeste) me
permet de voir un peu mieux l'écosystème du développement avec
Clojure.
Je viens de mettre en place le plugin leiningen pour IntelliJ et
l'étape suivante va être l'utilisation de Git :)

tu n'as pas forcement besoin en fait.

Tu t'inscris, tu cliques sur "gist", tu choisis le language, tu copies/colles dans la textera, tu fais "enregistrer"

et ca te fais du code que tu peux partager : https://gist.github.com/3181607

"No need to know git to use GitHub! Cut & Paste is enough to start!"

Denis

Christophe Grand

unread,
Jul 26, 2012, 8:50:35 AM7/26/12
to clojure-pari...@googlegroups.com
git-svn est un excellent client SVN et un bon moyen de transition.


Christophe Grand

Laurent PETIT

unread,
Jul 26, 2012, 8:55:07 AM7/26/12
to clojure-pari...@googlegroups.com
Le 26 juillet 2012 14:50, Christophe Grand <christop...@gmail.com> a écrit :
git-svn est un excellent client SVN et un  bon moyen de transition.


Je confirme : j'ai utilisé git-svn pendant 8 mois en "sous-marin" dans mon entreprise, afin de me familiariser à mon rythme à Git avant de le proposer à tout le monde (depuis, toute notre R&D a migré sous Git, il n'y a pas eu de gros problème - 1 journée de formation quand même à prévoir afin de bien faire le lavage de cerveau pour ceux qui ne connaissaient pas du tout).

 
Reply all
Reply to author
Forward
0 new messages