問題の名前 : アンエスケープ
問題 : http://nabetani.sakura.ne.jp/hena/orde29unes/
実装リンク集 : https://qiita.com/Nabetani/items/f2db9b916c0a301b744f
次回のイベントは 2/2
see https://yhpg.doorkeeper.jp/events/84247 。
で。
最初に書いた、ruby による実装。
ruby2.5
require "json"
require "strscan"
def do_quote( q, r, ch, mode )
case ch
when q
:normal
else
r.last << ch
mode
end
end
def do_slash( r, ch )
case ch
when "/"
r.last << ch
:normal
when "'"
r.push ""
:sq
when '"'
r.push ""
:dq
else
r.push ""
r.last << ch
:normal
end
end
def do_normal( r, ch )
case ch
when "/"
:slash
when "'"
:sq
when '"'
:dq
else
r.last << ch
:normal
end
end
def unescape(s)
ss = StringScanner.new(s)
r=[""]
mode = :normal
while ! ss.eos?
ch=ss.getch
mode = case mode
when :sq
do_quote("'", r, ch, mode)
when :dq
do_quote('"', r, ch, mode)
when :slash
do_slash( r, ch )
when :normal
do_normal( r, ch )
end
end
return nil unless mode==:normal
r
end
def solve(src)
r = unescape(src)
return "-" if !r || r.empty? || r.any?{ |x| x.empty? }
r.join(",")
end
if $0==__FILE__
data = JSON.parse(DATA.read, symbolize_names: true)
data[:test_data].map{ | number:, src:, expected: |
actual = solve( src )
okay = actual == expected
puts [ number, (okay ? "ok" : "**NG**"), actual, expected ].join(" ")
okay
}.all?.yield_self{ |x| puts( x ? "okay" : "SOMETHING WRONG" ) }
end
__END__
{"event_id":"E29","event_url":"https://yhpg.doorkeeper.jp/events/82699","test_data":[
{"number":0,"src":"foo/bar/baz","expected":"foo,bar,baz"},
{"number":1,"src":"/foo/bar/baz'/","expected":"-"},
{"number":2,"src":"\"","expected":"-"},
{"number":3,"src":"'","expected":"-"},
{"number":63,"src":"Foo/Bar/\"Hoge'/'Fuga\"","expected":"Foo,Bar,Hoge'/'Fuga"}]}
最初に書いたのは、状態遷移させつつ一文字ずつ処理するというわりと順当な感じの実装。
先読みとか一文字戻すとか、そういう難しいことはしていない。
問題を作っていて思ったのは、「//
」が面白いなというところ。
しかし。ルールが簡単すぎて 正規表現で解けてしまう というミス(?)を犯してしまった。まあ仕方ない。
それはさておき。
上記の ruby 版を rust に移植したのが下記:
e29.rs
pub mod unescape {
enum Mode {
Normal,
Sq,
Dq,
Slash,
}
fn push(v: &mut Vec<String>, ch: char) {
let lastpos = v.len() - 1;
v[lastpos].push(ch);
}
fn do_normal(r: &mut Vec<String>, ch: char) -> Mode {
match ch {
'/' => Mode::Slash,
'\'' => Mode::Sq,
'"' => Mode::Dq,
_ => {
push(r, ch);
Mode::Normal
}
}
}
fn do_quote(r: &mut Vec<String>, ch: char, q: char, mode: Mode) -> Mode {
if ch == q {
Mode::Normal
} else {
push(r, ch);
mode
}
}
fn do_slash(r: &mut Vec<String>, ch: char) -> Mode {
match ch {
'/' => {
push(r, ch);
Mode::Normal
}
'\'' => {
r.push("".to_string());
Mode::Sq
}
'"' => {
r.push("".to_string());
Mode::Dq
}
_ => {
r.push(ch.to_string());
Mode::Normal
}
}
}
fn solve_impl(src: String) -> Option<Vec<String>> {
let mut r = vec!["".to_string()];
let mut pos = 0usize;
let mut mode = Mode::Normal;
while pos < src.len() {
let ch = src.chars().nth(pos).unwrap();
mode = match mode {
Mode::Normal => do_normal(&mut r, ch),
Mode::Sq => do_quote(&mut r, ch, '\'', Mode::Sq),
Mode::Dq => do_quote(&mut r, ch, '"', Mode::Dq),
Mode::Slash => do_slash(&mut r, ch),
};
pos += 1;
}
if r.iter().any(|x| x.len() == 0) {
return None;
}
match mode {
Mode::Normal => Some(r),
_ => None,
}
}
pub fn solve(src: String) -> String {
let r = solve_impl(src);
match r {
Some(x) => x.join(","),
_ => "-".to_string(),
}
}
}
lib.rs
pub mod e29;
#[cfg(test)]
mod tests {
use e29::unescape;
macro_rules! test {
($n:expr, $src:expr, $expected:expr) => {
eprintln!("{} {} {}", $n, $src, $expected);
let actual = unescape::solve($src.to_string());
assert_eq!(actual, $expected);
};
}
#[test]
fn it_works() {
test!(0, "foo/bar/baz", "foo,bar,baz");
test!(1, "/foo/bar/baz'/", "-");
test!(2, "\"", "-");
// 中略
test!(63, "Foo/Bar/\"Hoge'/'Fuga\"", "Foo,Bar,Hoge'/'Fuga");
}
}
ruby と同じぐらいの長さで書けていて、いい感じだと思う。
while pos < src.len(){ /* 略 */ }
のループはもう少しいい書き方ができるかもしれない。
当日はダブルクオート一文字を '\"'
と無駄にエスケープしていたんだけど、Qiita のシンタックスハイライトが壊れるので '"'
に修正した。lib.rs の方は治せなかったので壊れたままだけど。