Teilen Sie eine Zeichenfolge und ignorieren Sie Abschnitte in Anführungszeichen
-
08-06-2019 - |
Frage
Gegeben sei eine Zeichenfolge wie diese:
a,"string, with",various,"values, and some",quoted
Was ist ein guter Algorithmus, um dies anhand von Kommas aufzuteilen und dabei die Kommas in den zitierten Abschnitten zu ignorieren?
Die Ausgabe sollte ein Array sein:
[ „a“, „string, with“, „various“, „values, and some“, „quoted“ ]
Lösung
Wenn die Sprache meiner Wahl keine Möglichkeit bieten würde, dies ohne nachzudenken zu tun, würde ich zunächst zwei Optionen als einfachen Ausweg in Betracht ziehen:
Analysieren Sie die Kommas innerhalb der Zeichenfolge vorab und ersetzen Sie sie durch ein anderes Steuerzeichen. Teilen Sie sie dann auf, gefolgt von einer Nachanalyse des Arrays, um das zuvor verwendete Steuerzeichen durch die Kommas zu ersetzen.
Alternativ können Sie sie durch Kommas aufteilen und dann das resultierende Array nachträglich in ein anderes Array analysieren, bei jedem Array-Eintrag auf führende Anführungszeichen prüfen und die Einträge verketten, bis ich ein abschließendes Anführungszeichen erreicht habe.
Dabei handelt es sich jedoch um Hacks, und wenn es sich dabei um eine rein „mentale“ Übung handelt, vermute ich, dass sie sich als nicht hilfreich erweisen werden.Wenn es sich um ein reales Problem handelt, wäre es hilfreich, die Sprache zu kennen, damit wir spezifische Ratschläge geben können.
Andere Tipps
Sieht aus, als hätten Sie hier einige gute Antworten.
Für diejenigen unter Ihnen, die das Parsen Ihrer CSV-Datei selbst übernehmen möchten, befolgen Sie die Ratschläge der Experten und Erstellen Sie nicht Ihren eigenen CSV-Parser.
Ihr erster Gedanke ist: „Ich muss Kommas innerhalb von Anführungszeichen verarbeiten.“
Ihr nächster Gedanke wird sein: „Oh, Mist, ich muss Anführungszeichen innerhalb von Anführungszeichen behandeln.Entkommene Anführungszeichen.Anführungszeichen.Einzelzitate..."
Es ist ein Weg in den Wahnsinn.Schreiben Sie nicht Ihre eigenen.Finden Sie eine Bibliothek mit einer umfangreichen Unit-Test-Abdeckung, die alle schwierigen Teile abdeckt und für Sie durch die Hölle gegangen ist.Verwenden Sie für .NET die kostenlose Version FileHelpers Bibliothek.
Python:
import csv
reader = csv.reader(open("some.csv"))
for row in reader:
print row
Natürlich ist die Verwendung eines CSV-Parsers besser, aber aus Spaß könnten Sie Folgendes tun:
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
Der Autor hier hat einen Blob von C#-Code eingefügt, der das Szenario behandelt, mit dem Sie ein Problem haben:
Sollte nicht zu schwer zu übersetzen sein.
Was ist, wenn eine ungerade Anzahl von Zitaten in der ursprünglichen Zeichenfolge angezeigt wird?
Dies ähnelt auf unheimliche Weise dem CSV-Parsing, das einige Besonderheiten bei der Verarbeitung von Feldern in Anführungszeichen aufweist.Das Feld wird nur maskiert, wenn das Feld durch doppelte Anführungszeichen begrenzt wird, also:
Feld1, „Feld2, Feld3“, Feld4, „Feld5, Feld6“ Feld7
wird
Feld1
Feld2, Feld3
Feld4
"field5
Feld6" Feld7
Beachten Sie, dass es sich nicht um ein in Anführungszeichen gesetztes Feld handelt und die doppelten Anführungszeichen einfach als doppelte Anführungszeichen behandelt werden, wenn es nicht sowohl mit einem Anführungszeichen beginnt als auch endet.
Wenn ich mich richtig erinnere, geht mein Code, auf den jemand verlinkt hat, damit nicht richtig um.
Hier ist eine einfache Python-Implementierung basierend auf Pats Pseudocode:
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
Ich verwende dies zum Analysieren von Zeichenfolgen und bin mir nicht sicher, ob es hier hilft.aber vielleicht mit ein paar kleinen Modifikationen?
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
Dies ist eine Standardanalyse im CSV-Stil.Viele Leute versuchen dies mit regulären Ausdrücken zu erreichen.Mit regulären Ausdrücken können Sie etwa 90 % erreichen, aber Sie benötigen wirklich einen echten CSV-Parser, um es richtig zu machen.Ich habe einen ... gefunden Schneller, ausgezeichneter C#-CSV-Parser auf CodeProject vor ein paar Monaten, das ich wärmstens empfehlen kann!
Hier ist einer im Pseudocode (auch bekannt alsPython) in einem Durchgang :-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'])
Hier ist ein einfacher Algorithmus:
- Bestimmen Sie, ob die Zeichenfolge mit a beginnt
'"'
Charakter - Teilen Sie die Zeichenfolge in ein durch das getrenntes Array auf
'"'
Charakter. - Markieren Sie die zitierten Kommas mit einem Platzhalter
#COMMA#
- Wenn die Eingabe mit a beginnt
'"'
, Markieren Sie die Elemente im Array, bei denen der Index % 2 == 0 ist - Andernfalls markieren Sie die Elemente im Array, bei denen der Index % 2 == 1 ist
- Wenn die Eingabe mit a beginnt
- Verketten Sie die Elemente im Array, um eine geänderte Eingabezeichenfolge zu bilden.
- Teilen Sie die Zeichenfolge in ein durch das getrenntes Array auf
','
Charakter. - Ersetzt alle Instanzen im Array von
#COMMA#
Platzhalter mit dem','
Charakter. - Das Array ist Ihre Ausgabe.
Hier ist die Python-Implementierung:
(behoben, um „a,b“,c,“d,e,f,h“,i,j,k“ zu verarbeiten)
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']
Ich konnte einfach nicht widerstehen, zu sehen, ob ich es in einem Python-Einzeiler zum Laufen bringen könnte:
arr = [i.replace("|", ",") for i in re.sub('"([^"]*)\,([^"]*)"',"\g<1>|\g<2>", str_to_test).split(",")]
Gibt ['a', 'string, with', 'various', 'values, and some', 'quoted'] zurück
Es funktioniert, indem es zuerst die 'Inside Zitate an einen anderen Separator (|) ersetzt, die Zeichenfolge auf "auf" aufgeteilt "und das Ersetzen der | ersetzen Trennzeichen wieder.
Da Sie sprachunabhängig sagten, habe ich meinen Algorithmus in der Sprache geschrieben, die dem Pseudocode möglichst am nächsten kommt:
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