Question

Étant donné une chaîne comme celle-ci :

a,"chaîne, avec",diverses,"valeurs et certaines", entre guillemets

Quel est un bon algorithme pour diviser cela en fonction des virgules tout en ignorant les virgules à l'intérieur des sections citées ?

Le résultat doit être un tableau :

[ "a", "string, with", "various", "values, and some", "quoted" ]

Était-ce utile?

La solution

Si la langue de mon choix n'offrait pas un moyen de le faire sans réfléchir, j'envisagerais dans un premier temps deux options comme solution de facilité :

  1. Pré-analysez et remplacez les virgules dans la chaîne par un autre caractère de contrôle, puis divisez-les, suivi d'une post-analyse sur le tableau pour remplacer le caractère de contrôle utilisé précédemment par les virgules.

  2. Vous pouvez également les diviser sur les virgules, puis post-analyser le tableau résultant dans un autre tableau en vérifiant les guillemets de début sur chaque entrée du tableau et en concaténant les entrées jusqu'à ce que j'atteigne un guillemet de fin.

Ce sont cependant des hacks, et s’il s’agit d’un pur exercice « mental », je soupçonne qu’ils s’avéreront inutiles.S'il s'agit d'un problème réel, il serait utile de connaître la langue afin que nous puissions offrir des conseils spécifiques.

Autres conseils

On dirait que vous avez de bonnes réponses ici.

Pour ceux d'entre vous qui cherchent à gérer leur propre analyse de fichiers CSV, tenez compte des conseils des experts et Ne lancez pas votre propre analyseur CSV.

Votre première pensée est, "Je dois gérer les virgules entre guillemets."

Votre prochaine pensée sera, "Oh, merde, je dois gérer les guillemets à l'intérieur des guillemets.Citations échappées.Double citation.Guillemets simples..."

C'est un chemin vers la folie.N'écrivez pas le vôtre.Trouvez une bibliothèque avec une couverture étendue de tests unitaires qui aborde toutes les parties difficiles et a traversé l'enfer pour vous.Pour .NET, utilisez le logiciel gratuit Aides-fichiers bibliothèque.

Python:

import csv
reader = csv.reader(open("some.csv"))
for row in reader:
    print row

Bien sûr, utiliser un analyseur CSV est préférable, mais juste pour le plaisir, vous pouvez :

Loop on the string letter by letter.
    If current_letter == quote : 
        toggle inside_quote variable.
    Else if (current_letter ==comma and not inside_quote) : 
        push current_word into array and clear current_word.
    Else 
        append the current_letter to current_word
When the loop is done push the current_word into array 

L'auteur a déposé ici un blob de code C# qui gère le scénario avec lequel vous rencontrez un problème :

Importations de fichiers CSV dans .Net

Cela ne devrait pas être trop difficile à traduire.

Et si un nombre impair de citations apparaissent dans la chaîne d'origine?

Cela ressemble étrangement à l'analyse CSV, qui présente certaines particularités dans la gestion des champs cités.Le champ n'est échappé que s'il est délimité par des guillemets doubles, donc :

champ1, "champ2, champ3", champ4, "champ5, champ6" champ7

devient

champ1

champ2, champ3

champ4

"champ5

champ6" champ7

Remarquez que s'il ne commence pas et ne se termine pas par une citation, alors ce n'est pas un champ entre guillemets et les guillemets doubles sont simplement traités comme des guillemets doubles.

Par ailleurs, mon code auquel quelqu'un est lié ne gère pas cela correctement, si je me souviens bien.

Voici une implémentation python simple basée sur le pseudocode de Pat :

def splitIgnoringSingleQuote(string, split_char, remove_quotes=False):
    string_split = []
    current_word = ""
    inside_quote = False
    for letter in string:
      if letter == "'":
        if not remove_quotes:
           current_word += letter
        if inside_quote:
          inside_quote = False
        else:
          inside_quote = True
      elif letter == split_char and not inside_quote:
        string_split.append(current_word)
        current_word = ""
      else:
        current_word += letter
    string_split.append(current_word)
    return string_split

J'utilise ceci pour analyser des chaînes, je ne sais pas si cela aide ici ;mais avec quelques petites modifications peut-être ?

function getstringbetween($string, $start, $end){
    $string = " ".$string;
    $ini = strpos($string,$start);
    if ($ini == 0) return "";
    $ini += strlen($start);   
    $len = strpos($string,$end,$ini) - $ini;
    return substr($string,$ini,$len);
}

$fullstring = "this is my [tag]dog[/tag]";
$parsed = getstringbetween($fullstring, "[tag]", "[/tag]");

echo $parsed; // (result = dog) 

/mp

Il s'agit d'une analyse standard de style CSV.Beaucoup de gens essaient de le faire avec des expressions régulières.Vous pouvez atteindre environ 90 % avec les expressions régulières, mais vous avez vraiment besoin d'un véritable analyseur CSV pour le faire correctement.j'ai trouvé un rapide et excellent analyseur C# CSV sur CodeProject il y a quelques mois que je recommande vivement !

En voici un en pseudocode (alias.Python) en un seul passage :-P

def parsecsv(instr):
    i = 0
    j = 0

    outstrs = []

    # i is fixed until a match occurs, then it advances
    # up to j. j inches forward each time through:

    while i < len(instr):

        if j < len(instr) and instr[j] == '"':
            # skip the opening quote...
            j += 1
            # then iterate until we find a closing quote.
            while instr[j] != '"':
                j += 1
                if j == len(instr):
                    raise Exception("Unmatched double quote at end of input.")

        if j == len(instr) or instr[j] == ',':
            s = instr[i:j]  # get the substring we've found
            s = s.strip()    # remove extra whitespace

            # remove surrounding quotes if they're there
            if len(s) > 2 and s[0] == '"' and s[-1] == '"':
                s = s[1:-1]

            # add it to the result
            outstrs.append(s)

            # skip over the comma, move i up (to where
            # j will be at the end of the iteration)
            i = j+1

        j = j+1

    return outstrs

def testcase(instr, expected):
    outstr = parsecsv(instr)
    print outstr
    assert expected == outstr

# Doesn't handle things like '1, 2, "a, b, c" d, 2' or
# escaped quotes, but those can be added pretty easily.

testcase('a, b, "1, 2, 3", c', ['a', 'b', '1, 2, 3', 'c'])
testcase('a,b,"1, 2, 3" , c', ['a', 'b', '1, 2, 3', 'c'])

# odd number of quotes gives a "unmatched quote" exception
#testcase('a,b,"1, 2, 3" , "c', ['a', 'b', '1, 2, 3', 'c'])

Voici un algorithme simple :

  1. Déterminez si la chaîne commence par un '"' personnage
  2. Divisez la chaîne en un tableau délimité par le '"' personnage.
  3. Marquez les virgules citées avec un espace réservé #COMMA#
    • Si la saisie commence par un '"', marquez les éléments du tableau où l'index % 2 == 0
    • Sinon, marquez les éléments du tableau où l'index % 2 == 1
  4. Concaténez les éléments du tableau pour former une chaîne d'entrée modifiée.
  5. Divisez la chaîne en un tableau délimité par le ',' personnage.
  6. Remplacer toutes les instances du tableau de #COMMA# espaces réservés avec le ',' personnage.
  7. Le tableau est votre sortie.

Voici l'implémentation python :
(corrigé pour gérer '"a,b",c,"d,e,f,h","i,j,k"')

def parse_input(input):

    quote_mod = int(not input.startswith('"'))

    input = input.split('"')
    for item in input:
        if item == '':
            input.remove(item)
    for i in range(len(input)):
        if i % 2 == quoted_mod:
            input[i] = input[i].replace(",", "#COMMA#")

    input = "".join(input).split(",")
    for item in input:
        if item == '':
            input.remove(item)
    for i in range(len(input)):
        input[i] = input[i].replace("#COMMA#", ",")
    return input

# parse_input('a,"string, with",various,"values, and some",quoted')
#  -> ['a,string', ' with,various,values', ' and some,quoted']
# parse_input('"a,b",c,"d,e,f,h","i,j,k"')
#  -> ['a,b', 'c', 'd,e,f,h', 'i,j,k']

Je n'ai tout simplement pas pu résister à l'idée de voir si je pouvais le faire fonctionner dans un one-liner Python :

arr = [i.replace("|", ",") for i in re.sub('"([^"]*)\,([^"]*)"',"\g<1>|\g<2>", str_to_test).split(",")]

Renvoie ['a', 'string, with', 'various', 'values, and some', 'quoted']

Il fonctionne en remplaçant d'abord le ',' Inside Quotes à un autre séparateur (|), en divisant la chaîne sur ',' et en remplaçant le | séparateur à nouveau.

Puisque vous avez dit indépendant du langage, j'ai écrit mon algorithme dans le langage le plus proche possible du pseudocode :

def find_character_indices(s, ch):
    return [i for i, ltr in enumerate(s) if ltr == ch]


def split_text_preserving_quotes(content, include_quotes=False):
    quote_indices = find_character_indices(content, '"')

    output = content[:quote_indices[0]].split()

    for i in range(1, len(quote_indices)):
        if i % 2 == 1: # end of quoted sequence
            start = quote_indices[i - 1]
            end = quote_indices[i] + 1
            output.extend([content[start:end]])

        else:
            start = quote_indices[i - 1] + 1
            end = quote_indices[i]
            split_section = content[start:end].split()
            output.extend(split_section)

        output += content[quote_indices[-1] + 1:].split()                                                                 

    return output
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top