language agnostic - Dividir una cadena ignorando las secciones citadas
language-agnostic parsing (12)
Dada una cadena como esta:
a, "cadena, con", varios, "valores, y algunos", citado
¿Qué es un buen algoritmo para dividir esto basado en comas mientras se ignoran las comas dentro de las secciones citadas?
La salida debe ser una matriz:
["a", "cadena, con", "varios", "valores, y algunos", "citado"]
¿Qué pasa si aparece un número impar de citas en la cadena original?
Esto se parece misteriosamente al análisis CSV, que tiene algunas peculiaridades para el manejo de campos cotizados. El campo solo se escapa si el campo está delimitado con comillas dobles, entonces:
field1, "field2, field3", field4, "field5, field6" field7
se convierte
campo1
campo2, campo3
campo4
"campo5
campo6 "campo7
Observe si no comienza y termina con una cita, entonces no es un campo entre comillas y las comillas dobles simplemente se tratan como comillas dobles.
Insertablemente, mi código al que alguien se vinculó no lo maneja correctamente, si no recuerdo mal.
Aquí hay uno en pseudocódigo (también conocido como Python) en un pase :-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:
- Determine si la cadena comienza con un carácter
''"''
- Divida la cadena en una matriz delimitada por el carácter
''"''
. - Marque las comillas con un marcador de posición
#COMMA#
- Si la entrada comienza con un
''"''
, marque los elementos en la matriz donde el índice% 2 == 0 - De lo contrario, marque esos elementos en la matriz donde el índice% 2 == 1
- Si la entrada comienza con un
- Concatenar los elementos en la matriz para formar una cadena de entrada modificada.
- Divida la cadena en una matriz delimitada por el carácter
'',''
. - Reemplace todas las instancias en la matriz de marcadores de posición
#COMMA#
con el carácter'',''
. - La matriz es tu 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'']
Este es un análisis de estilo CSV estándar. Mucha gente intenta hacer esto con expresiones regulares. Puede obtener aproximadamente el 90% con expresiones regulares, pero realmente necesita un analizador de CSV real para hacerlo correctamente. ¡Hace unos meses encontré un analizador rápido y excelente de C # CSV en CodeProject que recomiendo!
Lo uso para analizar cadenas, no estoy seguro si ayuda aquí; pero con algunas modificaciones menores tal vez?
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
Parece que tienes algunas buenas respuestas aquí.
Para aquellos de ustedes que buscan manejar su propio análisis de archivo CSV, presten atención al consejo de los expertos y no despliegue su propio analizador CSV .
Su primer pensamiento es: "Necesito manejar las comas dentro de las comillas".
Su próximo pensamiento será: "Oh, mierda, necesito manejar las comillas dentro de las comillas. Las comillas saltadas. Las comillas dobles. Las comillas simples ..."
Es un camino a la locura. No escribas el tuyo Encuentre una biblioteca con una extensa cobertura de prueba unitaria que llegue a todas las partes difíciles y haya pasado por un infierno para usted. Para .NET, use la biblioteca gratuita FileHelpers .
Simplemente no pude resistirme a ver si podía hacerlo funcionar en un one-liner de Python:
arr = [i.replace("|", ",") for i in re.sub(''"([^"]*)/,([^"]*)"'',"/g<1>|/g<2>", str_to_test).split(",")]
Devuelve [''a'', ''string, with'', ''various'', ''values, y algunos'', ''cotizados'']
Funciona reemplazando primero las comillas '','' dentro de otro separador (|), dividiendo la cadena en '','' y reemplazando el | separador de nuevo.
Por supuesto, usar un analizador de CSV es mejor, pero solo por diversión podrías:
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
Pitón:
import csv
reader = csv.reader(open("some.csv"))
for row in reader:
print row
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
Si mi lenguaje de elección no ofreciera una manera de hacer esto sin pensar, inicialmente consideraría dos opciones como la salida más fácil:
Realice un análisis previo y reemplace las comas dentro de la cadena con otro carácter de control y luego divídelas, seguido de un análisis posterior en la matriz para reemplazar el carácter de control utilizado anteriormente con las comas.
Alternativamente, divídelas en las comas y luego analiza la matriz resultante en otra matriz, verificando las comillas principales en cada entrada de la matriz y concatenando las entradas hasta que llegue a una comilla de terminación.
Sin embargo, estos son hacks, y si se trata de un ejercicio mental puro, entonces sospecho que no serán útiles. Si esto es un problema del mundo real, sería útil conocer el idioma para poder ofrecer algún consejo específico.
El autor aquí soltó un bloque de código C # que maneja el escenario con el que estás teniendo un problema:
Importaciones de archivos CSV en .Net
No debería ser demasiado difícil de traducir.