引用符で囲まれたセクションを無視して文字列を分割する
-
08-06-2019 - |
質問
次のような文字列を指定すると、次のようになります。
a,"string, with",various,"values, and some",quoted
引用されたセクション内のカンマを無視して、これをカンマに基づいて分割する適切なアルゴリズムは何ですか?
出力は配列である必要があります。
[ "a"、"文字列、with"、"various"、"values、およびその一部"、"quoted" ]
解決
私の選択した言語がこれを何も考えずに実行する方法を提供していない場合、私は最初に簡単な方法として 2 つのオプションを検討します。
文字列内のコンマを事前解析して別の制御文字に置き換えてから分割し、その後配列を事後解析して、以前に使用されていた制御文字をコンマに置き換えます。
あるいは、カンマで分割してから、結果の配列を別の配列に事後解析して、各配列エントリの先頭の引用符をチェックし、終了引用符に到達するまでエントリを連結します。
ただし、これらはハックであり、これが純粋な「精神的」訓練である場合、役に立たないことが判明すると思います。これが現実世界の問題であれば、具体的なアドバイスを提供できるよう、その言語を知っておくと役立ちます。
他のヒント
ここで良い答えが得られているようです。
独自の 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
元の文字列に奇数の引用符が表示された場合はどうなりますか?
これは奇妙なことに CSV 解析に似ており、引用符で囲まれたフィールドの処理にいくつかの特徴があります。フィールドが二重引用符で区切られている場合にのみフィールドがエスケープされます。そのため、次のようになります。
フィールド1、「フィールド2、フィールド3」、フィールド4、「フィールド5、フィールド6」 フィールド7
になる
フィールド1
フィールド2、フィールド3
フィールド4
「フィールド5」
フィールド6" フィールド7
開始と終了の両方が引用符でない場合、そのフィールドは引用符で囲まれておらず、二重引用符は単に二重引用符として扱われることに注意してください。
私の記憶が正しければ、誰かがリンクした私のコードは、実際にはこれを正しく処理しません。
Pat の疑似コードに基づいた簡単な 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)
/mp
これは標準的な CSV スタイルの解析です。多くの人が正規表現を使用してこれを行おうとします。正規表現を使用して約 90% まで到達できますが、これを適切に実行するには実際に本物の CSV パーサーが必要です。見つけました CodeProject の高速で優れた C# CSV パーサー 数か月前に、強くお勧めします!
これは疑似コード (別名:Python) 1 パスで :-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'])
簡単なアルゴリズムを次に示します。
- 文字列が a で始まるかどうかを判断します。
'"'
キャラクター - 文字列を、
'"'
キャラクター。 - 引用符で囲まれたカンマをプレースホルダーでマークします
#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'、'文字列、with'、'various'、'values、および some'、'quoted']
最初に「」、内部の引用符を別のセパレーター(|)に置き換え、文字列を '' ''に分割し、|再びセパレーター。
言語に依存しないとおっしゃったので、私は可能な限り疑似コードに最も近い言語でアルゴリズムを書きました。
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