Pregunta

Dada una cadena como esta:

a,"cadena, con",varios,"valores y algunos", citado

¿Cuál es un buen algoritmo para dividir esto en función de comas ignorando las comas dentro de las secciones citadas?

La salida debería ser una matriz:

[ "a", "cadena, con", "varios", "valores y algunos", "citado" ]

¿Fue útil?

Solución

Si el idioma que elegí no ofreciera una forma de hacer esto sin pensar, inicialmente consideraría dos opciones como la salida más fácil:

  1. Realice un análisis previo y reemplace las comas dentro de la cadena con otro carácter de control, luego divídalas, seguido de un análisis posterior en la matriz para reemplazar el carácter de control utilizado anteriormente con las comas.

  2. Alternativamente, divídalos entre comas y luego analice posteriormente la matriz resultante en otra matriz verificando las comillas iniciales en cada entrada de la matriz y concatenando las entradas hasta llegar a una cita final.

Sin embargo, estos son trucos, y si se trata de un ejercicio puramente "mental", sospecho que resultarán inútiles.Si se trata de un problema del mundo real, sería útil conocer el idioma para poder ofrecer algunos consejos específicos.

Otros consejos

Parece que tienes algunas buenas respuestas aquí.

Para aquellos que quieran manejar su propio análisis de archivos CSV, presten atención a los consejos de los expertos y No utilice su propio analizador CSV.

Tu primer pensamiento es, "Necesito manejar comas dentro de comillas".

Tu próximo pensamiento será, "Oh, mierda, necesito manejar comillas dentro de comillas.Citas escapadas.Doble comillas.Comillas simples..."

Es un camino hacia la locura.No escribas el tuyo.Encuentre una biblioteca con una amplia cobertura de pruebas unitarias que abarque todas las partes difíciles y que haya pasado por un infierno para usted.Para .NET, utilice el programa gratuito Ayudantes de archivos biblioteca.

Pitón:

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

Por supuesto, usar un analizador CSV es mejor, pero solo por diversión puedes:

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 

El autor aquí incluyó un blob de código C# que maneja el escenario con el que tienes problemas:

Importaciones de archivos CSV en .Net

No debería ser demasiado difícil de traducir.

¿Qué pasa si aparece un número impar de citas en la cadena original?

Esto se parece asombrosamente al análisis de CSV, que tiene algunas peculiaridades en el manejo de campos entre comillas.El campo solo tiene carácter de escape si está delimitado por comillas dobles, por lo que:

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

se convierte

campo1

campo2, campo3

campo4

"campo5

campo6" campo7

Observe que si no comienza ni termina con una cita, entonces no es un campo entrecomillado y las comillas dobles simplemente se tratan como comillas dobles.

Incidentemente, mi código al que alguien se vinculó en realidad no maneja esto correctamente, si mal no recuerdo.

Aquí hay una implementación simple de Python basada en el pseudocódigo 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

Utilizo esto para analizar cadenas, no estoy seguro si ayuda aquí;¿Pero quizás con algunas modificaciones menores?

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

Este es un análisis estándar de estilo CSV.Mucha gente intenta hacer esto con expresiones regulares.Puedes llegar a aproximadamente el 90% con expresiones regulares, pero realmente necesitas un analizador CSV real para hacerlo correctamente.Encontre un Analizador rápido y excelente de C# CSV en CodeProject Hace unos meses que lo recomiendo mucho!

Aquí hay uno en pseudocódigo (también conocido comoPython) en una sola pasada :-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'])

Aquí hay un algoritmo simple:

  1. Determine si la cadena comienza con un '"' personaje
  2. Divida la cadena en una matriz delimitada por el '"' personaje.
  3. Marque las comas entre comillas con un marcador de posición. #COMMA#
    • Si la entrada comienza con un '"', marque aquellos elementos en la matriz donde el índice % 2 == 0
    • De lo contrario, marque aquellos elementos en la matriz donde el índice % 2 == 1
  4. Concatene los elementos de la matriz para formar una cadena de entrada modificada.
  5. Divida la cadena en una matriz delimitada por el ',' personaje.
  6. Reemplace todas las instancias en la matriz de #COMMA# marcadores de posición con el ',' personaje.
  7. La matriz es su salida.

Aquí está la implementación de Python:
(fijado para manejar '"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']

Simplemente no pude resistirme a ver si podía hacerlo funcionar en una sola línea de Python:

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

Devuelve ['a', 'cadena, con', 'varios', 'valores y algunos', 'citado']

Funciona primero reemplazando las citas ',' interiores a otro separador (|), dividiendo la cadena en ',' y reemplazando el | separador de nuevo.

Como dijiste agnóstico del lenguaje, escribí mi algoritmo en el lenguaje más cercano posible al pseudocódigo:

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
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top