2017-05-31 4 views
1

J'ai essayé d'utiliser pyparsing pour analyser un fichier CSV avec:Comment analyser un fichier CSV par des virgules entre parenthèses et les valeurs manquantes

  • entre parenthèses Commas (ou entre parenthèses, etc.): « a (1,2) , b "devrait retourner la liste [" a (1,2) "," b "]
  • Valeurs manquantes:" a, b ,, c, "devrait retourner la liste ['a', 'b' , '', 'c', '']

J'ai travaillé une solution mais elle semble "sale". Principalement, le Optional à l'intérieur d'un seul des atomiques possibles. Je pense que l'option devrait être indépendante des atomes. C'est-à-dire, je pense qu'il devrait être mis ailleurs, par exemple dans les arguments facultatifs delimitedList, mais dans mon essai et erreur c'était le seul endroit qui a fonctionné et fait sens. Il pourrait être dans l'un des atomiques possibles, donc j'ai choisi le premier.

En outre, je ne comprends pas complètement ce que fait originalTextFor mais si je l'enlève il cesse de fonctionner.

Exemple de travail:

import pyparsing as pp 

# Function that parses a line of columns separated by commas and returns a list of the columns 
def fromLineToRow(line): 
    sqbrackets_col = pp.Word(pp.printables, excludeChars="[],") | pp.nestedExpr(opener="[",closer="]") # matches "a[1,2]" 
    parens_col = pp.Word(pp.printables, excludeChars="(),") | pp.nestedExpr(opener="(",closer=")")  # matches "a(1,2)" 
    # In the following line: 
    # * The "^" means "choose the longest option" 
    # * The "pp.Optional" can be in any of the expressions separated by "^". I put it only on the first. It's used for when there are missing values 
    atomic = pp.originalTextFor(pp.Optional(pp.OneOrMore(parens_col)))^pp.originalTextFor(pp.OneOrMore(sqbrackets_col)) 

    grammar = pp.delimitedList(atomic) 

    row = grammar.parseString(line).asList() 
    return row 

file_str = \ 
"""YEAR,a(2,3),b[3,4] 
1960,2.8,3 
1961,4, 
1962,,1 
1963,1.27,3""" 

for line in file_str.splitlines(): 
    row = fromLineToRow(line) 
    print(row) 

Prints:

['YEAR', 'a(2,3)', 'b[3,4]'] 
['1960', '2.8', '3'] 
['1961', '4', ''] 
['1962', '', '1'] 
['1963', '1.27', '3'] 

Est-ce la bonne façon de le faire? Y a-t-il un moyen "plus propre" d'utiliser le Optional à l'intérieur du premier atome?

Répondre

1

Travailler à l'envers, je reçois ceci:

# chars not in()'s or []'s - also disallow ',' 
non_grouped = pp.Word(pp.printables, excludeChars="[](),") 

# grouped expressions in()'s or []'s 
grouped = pp.nestedExpr(opener="[",closer="]") | pp.nestedExpr(opener="(",closer=")") 

# use OneOrMore to allow non_grouped and grouped together 
atomic = pp.originalTextFor(pp.OneOrMore(non_grouped | grouped)) 
# or based on your examples, you *could* tighten this up to: 
# atomic = pp.originalTextFor(non_grouped + pp.Optional(grouped)) 

originalTextFor recombine le texte d'entrée d'origine dans les limites avant et arrière des expressions appariés et renvoie une seule chaîne. Si vous laissez ceci, alors vous obtiendrez toutes les sous-expressions dans une liste imbriquée de chaînes, comme ['a',['2,3']]. Vous pourrait les rejoindre avec des appels répétés à ''.join, mais cela réduirait les espaces (ou utiliser ' '.join, mais cela a le problème inverse d'introduire potentiellement des espaces).

Pour optionalize les éléments de la liste, juste dire dans la définition de la liste délimitée:

grammar = pp.delimitedList(pp.Optional(atomic, default='')) 

Assurez-vous d'ajouter la valeur par défaut, sinon les emplacements vides vont juste DROPpé.

Avec ces changements que je reçois:

analyse syntaxique
['YEAR', 'a(2,3)', 'b[3,4]'] 
['1960', '2.8', '3'] 
['1961', '4', ''] 
['1962', '', '1'] 
['1963', '1.27', '3'] 
+0

Pour la conversion des valeurs numériques en temps parse, changez atomic' en: atomic = pp.pyparsing_common.number | pp.originalTextFor (... etc.' – PaulMcG

0

Qu'est-ce que vous pouvez faire est d'utiliser regex re, par exemple:

>>> import re 
>>> re.split(r',\s*(?![^()]*\))', line1) 
['a(1,2)', 'b'] 
>>> re.split(r',\s*(?![^()]*\))', line2) 
['a', 'b', '', 'c', ''] 
+0

de line1 devrait être [ "un (1,2)", "b"] au lieu de [ '(1', '2)', « b '] (la virgule à l'intérieur de la parenthèse ne devrait pas être un délimiteur) – Alechan

+0

@Alechan voir ma mise à jour s'il vous plaît – haifzhan

+0

Oui, c'était ma première approche avant d'essayer pyparsing mais quand je commence à ajouter des crochets ou tout autre type d'expressions imbriquées alors le regex devient de plus en plus illisible – Alechan

0
import re 

with open('44289614.csv') as f: 
    for line in map(str.strip, f): 
     l = re.split(',\s*(?![^()[]]*[\)\]])', line) 
     print(len(l), l) 

Sortie:

3 ['YEAR', 'a(2,3)', 'b[3,4]'] 
3 ['1960', '2.8', '3'] 
3 ['1961', '4', ''] 
3 ['1962', '', '1'] 
3 ['1963', '1.27', '3'] 

modifié de this answer. J'aime aussi this answer, ce qui suggère de modifier légèrement l'entrée et en utilisant quotechar du module csv.