Domanda

Data una stringa come questa:

a,"stringa, con", vari, "valori e alcuni", tra virgolette

Qual è un buon algoritmo per dividerlo in base alle virgole ignorando le virgole all'interno delle sezioni citate?

L'output dovrebbe essere un array:

[ "a", "stringa, con", "vari", "valori e alcuni", "virgolette" ]

È stato utile?

Soluzione

Se la mia lingua preferita non offrisse un modo per farlo senza pensare, inizialmente prenderei in considerazione due opzioni come semplice via d'uscita:

  1. Pre-analizzare e sostituire le virgole all'interno della stringa con un altro carattere di controllo, quindi dividerle, seguite da una post-analisi sull'array per sostituire il carattere di controllo utilizzato in precedenza con le virgole.

  2. In alternativa, dividili sulle virgole, quindi post-analizza l'array risultante in un altro array controllando le virgolette iniziali su ciascuna voce dell'array e concatenando le voci finché non raggiungo una virgoletta finale.

Questi sono comunque degli hack e, se si tratta di un puro esercizio "mentale", sospetto che si riveleranno inutili.Se questo è un problema reale, allora sarebbe utile conoscere la lingua in modo da poter offrire qualche consiglio specifico.

Altri suggerimenti

Sembra che tu abbia delle buone risposte qui.

Per quelli di voi che desiderano gestire l'analisi dei propri file CSV, seguite i consigli degli esperti e Non eseguire il rollio del tuo parser CSV.

Il tuo primo pensiero è: "Devo gestire le virgole all'interno delle virgolette."

Il tuo prossimo pensiero sarà: "Oh, cavolo, devo gestire le virgolette all'interno delle virgolette.Citazioni sfuggite.Virgolette.virgolette singole..."

È una strada verso la follia.Non scrivere il tuo.Trova una libreria con un'ampia copertura di test unitari che colpisca tutte le parti difficili e che abbia attraversato l'inferno per te.Per .NET, utilizzare la versione gratuita FileHelper biblioteca.

Pitone:

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

Ovviamente usare un parser CSV è migliore, ma solo per divertimento potresti:

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'autore qui ha inserito un blob di codice C# che gestisce lo scenario con cui stai riscontrando un problema:

Importazioni di file CSV in .Net

Non dovrebbe essere troppo difficile da tradurre.

E se un numero dispari di citazioni appaia nella stringa originale?

Questo assomiglia stranamente all'analisi CSV, che ha alcune peculiarità nella gestione dei campi tra virgolette.Il campo viene sottoposto a escape solo se è delimitato da virgolette doppie, quindi:

campo1, "campo2, campo3", campo4, "campo5, campo6" campo7

diventa

campo1

campo2, campo3

campo4

"campo5

campo6" campo7

Nota se non inizia e termina con una virgoletta, allora non è un campo tra virgolette e le virgolette doppie vengono semplicemente trattate come virgolette doppie.

In modo invisibile il mio codice a cui qualcuno è collegato non lo gestisce correttamente, se ricordo bene.

Ecco una semplice implementazione Python basata sullo pseudocodice di 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

Lo uso per analizzare le stringhe, non sono sicuro che sia d'aiuto qui;ma magari con qualche piccola modifica?

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

Questa è un'analisi standard in stile CSV.Molte persone provano a farlo con le espressioni regolari.Puoi arrivare a circa il 90% con le espressioni regolari, ma hai davvero bisogno di un vero parser CSV per farlo correttamente.Ho trovato un parser CSV C# veloce ed eccellente su CodeProject qualche mese fa che consiglio vivamente!

Eccone uno in pseudocodice (akaPython) in un solo passaggio :-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'])

Ecco un semplice algoritmo:

  1. Determina se la stringa inizia con a '"' carattere
  2. Dividere la stringa in un array delimitato da '"' carattere.
  3. Contrassegna le virgole tra virgolette con un segnaposto #COMMA#
    • Se l'input inizia con a '"', contrassegna gli elementi nell'array in cui l'indice % 2 == 0
    • Altrimenti contrassegna gli elementi nell'array in cui l'indice % 2 == 1
  4. Concatena gli elementi nell'array per formare una stringa di input modificata.
  5. Dividere la stringa in un array delimitato da ',' carattere.
  6. Sostituisci tutte le istanze nell'array di #COMMA# segnaposto con il ',' carattere.
  7. L'array è il tuo output.

Ecco l'implementazione di Python:
(risolto per gestire '"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']

Non ho potuto resistere nel vedere se potevo farlo funzionare in una riga di Python:

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

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

Funziona sostituendo prima la "," citazioni interne a un altro separatore (|), dividendo la stringa su "," e sostituendo il | di nuovo separatore.

Dato che hai detto indipendente dal linguaggio, ho scritto il mio algoritmo nel linguaggio più vicino possibile allo pseudocodice:

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
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top