Разделите строку, игнорируя разделы, заключенные в кавычки
-
08-06-2019 - |
Вопрос
Дана строка, подобная этой:
a,"строка, с",различные,"значения и некоторые",заключенныев кавычки
Каков хороший алгоритм для разделения этого на основе запятых, игнорируя запятые внутри цитируемых разделов?
На выходе должен быть массив:
[ "a", "строка, с", "различные", "значения и некоторые", "в кавычках" ]
Решение
Если бы выбранный мной язык не предлагал способа сделать это, не задумываясь, то я бы изначально рассмотрел два варианта в качестве простого выхода:
Выполните предварительный анализ и замените запятые в строке другим управляющим символом, затем разделите их, после чего выполните последующий анализ массива для замены управляющего символа, использованного ранее, на запятые.
В качестве альтернативы разделите их на запятые, затем выполните последующий анализ результирующего массива в другой массив, проверяя наличие начальных кавычек в каждой записи массива и объединяя записи, пока я не дойду до завершающей цитаты.
Однако это хаки, и если это чисто "умственное" упражнение, то я подозреваю, что они окажутся бесполезными.Если это реальная мировая проблема, то было бы полезно знать язык, чтобы мы могли предложить несколько конкретных советов.
Другие советы
Похоже, у вас здесь есть несколько хороших ответов.
Для тех из вас, кто хочет самостоятельно выполнить синтаксический анализ CSV-файла, прислушайтесь к советам экспертов и Не запускайте свой собственный CSV-анализатор.
Ваша первая мысль такова, "Мне нужно расставить запятые внутри кавычек".
Вашей следующей мыслью будет, "О, черт, мне нужно обрабатывать кавычки внутри кавычек.Экранированные кавычки.Двойные кавычки.Одинарные кавычки..."
Это путь к безумию.Не пишите ничего своего.Найдите библиотеку с обширным охватом модульных тестов, которая затрагивает все трудные моменты и прошла через ад ради вас.Для .NET используйте бесплатный Помощники по работе с файлами библиотека.
Питон:
import csv
reader = csv.reader(open("some.csv"))
for row in reader:
print row
Конечно, использование CSV-анализатора лучше, но просто ради удовольствия вы могли бы:
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
Автор здесь вставил большой фрагмент кода C #, который обрабатывает сценарий, с которым у вас возникла проблема:
Перевести не должно быть слишком сложно.
Что делать, если в исходной строке содержится нечетное количество кавычек ?
Это до жути похоже на синтаксический анализ CSV, который имеет некоторые особенности при обработке полей, заключенных в кавычки.Поле экранируется только в том случае, если оно заключено в двойные кавычки, так что:
поле 1, "поле 2, поле 3", поле 4, "поле 5, поле 6", поле 7
становится
поле 1
поле 2, поле 3
поле 4
"поле 5
поле 6" поле 7
Обратите внимание, что если оно не начинается и не заканчивается кавычкой, то это поле не заключено в кавычки, и двойные кавычки просто рассматриваются как двойные кавычки.
Фактически, мой код, на который кто-то ссылается, на самом деле не обрабатывает это правильно, если я правильно помню.
Вот простая реализация python, основанная на псевдокоде Пэта:
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
Я использую это для синтаксического анализа строк, не уверен, поможет ли это здесь;но, возможно, с некоторыми незначительными изменениями?
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)
/мп
Это стандартный синтаксический анализ в формате CSV.Многие люди пытаются сделать это с помощью регулярных выражений.Вы можете достичь примерно 90% с помощью регулярных выражений, но вам действительно нужен настоящий анализатор CSV, чтобы сделать это правильно.Я нашел быстрый, отличный анализатор CSV на C # в CodeProject несколько месяцев назад, что я настоятельно рекомендую!
Вот один из них в псевдокоде (он жеPython) за один проход:-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'])
Вот простой алгоритм:
- Определите, начинается ли строка с
'"'
характер - Разделите строку на массив, разделенный символом
'"'
характер. - Отметьте запятые, заключенные в кавычки, заполнителем
#COMMA#
- Если входные данные начинаются с
'"'
, отметьте те элементы в массиве , где индекс % 2 == 0 - В противном случае отметьте те элементы в массиве, где индекс % 2 == 1
- Если входные данные начинаются с
- Объедините элементы в массиве, чтобы сформировать измененную входную строку.
- Разделите строку на массив, разделенный символом
','
характер. - Замените все экземпляры в массиве
#COMMA#
заполнители с','
характер. - Массив - это ваш результат.
Вот реализация на python:
(исправлено для обработки '"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']
Я просто не мог удержаться, чтобы не посмотреть, смогу ли я заставить это работать в однострочном Python:
arr = [i.replace("|", ",") for i in re.sub('"([^"]*)\,([^"]*)"',"\g<1>|\g<2>", str_to_test).split(",")]
Возвращает ['a', 'string, with', 'various', 'values и некоторые', 'в кавычках']
Это работает путем первой замены ',' внутренних кавычек на другой разделитель (|), разделения строки на ',' и повторной замены разделителя |.
Поскольку вы сказали, что не зависит от языка, я написал свой алгоритм на языке, максимально близком к псевдокоду:
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