LoginSignup
2
0

More than 5 years have passed since last update.

オフラインリアルタイムどう書く E19 「カード当てゲーム」をPrologで解いた

Last updated at Posted at 2017-11-04

現場では惨敗でした。
Prolog で書けば綺麗に書けそうだなー、と思い書いてみました。たぶん 3 時間ぐらい。Prolog で書いたとしても負けてる。しかも寝る前に書いたので綺麗ぢゃない。

処理系は GNU-Prolog です。

問題とみなさんの解答はこちら → https://qiita.com/mtsmfm/items/67bf5c121ecbd9b5fab3

orde19.pro
% gprolog --consult-file orde19.pro --entry-goal 'main'

join([C], D) :- number_codes(C, D).
join([C|Cards], Acc) :- number_codes(C, D), append(D, [0',|Prev], Acc), join(Cards, Prev).

ordered_cards(N, Cards) :- findall(X, between(1, N, X), XS), findall(Y, between(1, N, Y), YS), append(XS, YS, S), msort(S, Cards).

cards_row([], _, _, _, []).
cards_row([0'x|Parities], N, Prev1, Prev1, [M|Cards]) :- P1_1 is Prev1 + 1, between(P1_1, N, M),  M mod 2 =:= 0, cards_row(Parities, N, Prev1, M, Cards).
cards_row([0'x|Parities], N, Prev0, Prev1, [M|Cards]) :- Prev0 < Prev1,     between(Prev1, N, M), M mod 2 =:= 0, cards_row(Parities, N, Prev1, M, Cards).
cards_row([0'o|Parities], N, Prev1, Prev1, [M|Cards]) :- P1_1 is Prev1 + 1, between(P1_1, N, M),  M mod 2 =:= 1, cards_row(Parities, N, Prev1, M, Cards).
cards_row([0'o|Parities], N, Prev0, Prev1, [M|Cards]) :- Prev0 < Prev1,     between(Prev1, N, M), M mod 2 =:= 1, cards_row(Parities, N, Prev1, M, Cards).
cards_row(Parities, N, Cards) :- cards_row(Parities, N, 0, 0, Cards).

solve(N, UpperParities, LowerParities, OrderedCards, Result) :-
  cards_row(UpperParities, N, UpperCards),
  cards_row(LowerParities, N, LowerCards),
  append(UpperCards, LowerCards, AllCards),
  join(AllCards, Result),
  msort(AllCards, OrderedCards).

join_results([R], R).
join_results([R|Rs], Acc) :- append(R, [0'||Prev], Acc), join_results(Rs, Prev).

solve(Input, Result) :-
  append(UpperParities, [0',|LowerParities], Input),
  length(UpperParities, N),
  ordered_cards(N, OrderedCards),
  findall(R, solve(N, UpperParities, LowerParities, OrderedCards, R), Rs),
  join_results(Rs, Result).

judge(Input, Expected, Expected) :-
  format("\x1b\[32mpassed  input: ~s\x1b\[0m~n", [Input]), !.
judge(Input, Expected, Actual) :-
  format("\x1b\[31mfailed  input: ~s, expected: ~s, actual: ~s\x1b\[0m~n", [Input, Expected, Actual]), !.

test(Input, Expected) :-
  solve(Input, Actual),
  judge(Input, Expected, Actual).

main :-
  test("xxoxo,oooxo", "2,2,3,4,5,1,1,3,4,5"),
  test("ooxxo,ooxxo", "1,1,2,2,5,3,3,4,4,5|3,3,4,4,5,1,1,2,2,5"),
  test("oxoxo,oxoxo", "1,2,3,4,5,1,2,3,4,5"),
  test("ooxoxx,oxxoxo", "1,3,4,5,6,6,1,2,2,3,4,5"),
  test("ooxxxx,ooxxoo", "1,1,2,2,6,6,3,3,4,4,5,5|3,3,4,4,6,6,1,1,2,2,5,5"),
  test("oxoxox,oxoxox", "1,2,3,4,5,6,1,2,3,4,5,6"),
  test("oxoxxxo,oxoxooo", "1,2,3,4,6,6,7,1,2,3,4,5,5,7"),
  test("oxooxxx,oxxoooo", "1,2,3,3,4,6,6,1,2,4,5,5,7,7"),
  test("oxoxxxo,oxoooxo", "1,2,3,4,4,6,7,1,2,3,5,5,6,7"),
  test("oxoxooox,oxoxoxxx", "1,2,3,4,5,7,7,8,1,2,3,4,5,6,6,8"),
  test("ooxoxxox,ooxxxoox", "3,3,4,5,6,6,7,8,1,1,2,2,4,5,7,8"),
  test("oxoxxxxx,oxoxoooo", "1,2,3,4,6,6,8,8,1,2,3,4,5,5,7,7"),
  test("oxoxooxxo,oxooxxoxo", "1,2,5,6,7,7,8,8,9,1,2,3,3,4,4,5,6,9"),
  test("oxoooooxo,oxxxoxxxo", "1,2,3,3,5,7,7,8,9,1,2,4,4,5,6,6,8,9"),
  test("oxoxoxxox,oxoxooxoo", "1,2,3,4,5,6,6,7,8,1,2,3,4,5,7,8,9,9"),
  test("oooxxxxoxo,xxooooxoxx", "1,1,3,4,4,6,6,7,8,9,2,2,3,5,5,7,8,9,10,10"),
  test("ooooxxxoxx,xxooxxxooo", "1,1,5,5,6,8,8,9,10,10,2,2,3,3,4,4,6,7,7,9"),
  test("xoxxoxxoxo,ooxoooxoxx", "2,3,4,4,5,6,6,7,8,9,1,1,2,3,5,7,8,9,10,10"),
  test("oxooxxoooxo,oxxxoxooxxo", "1,2,3,3,4,4,5,5,7,8,11,1,2,6,6,7,8,9,9,10,10,11"),
  test("oooxxxxoxoo,xxooooxooxx", "1,1,3,4,4,6,6,7,8,11,11,2,2,3,5,5,7,8,9,9,10,10"),
  test("ooooxxoxoxo,oxxxxooxoxo", "1,3,3,5,6,6,7,8,9,10,11,1,2,2,4,4,5,7,8,9,10,11"),
  test("ooooxoxooxox,xxoxxoxoxxox", "1,1,3,5,6,7,8,9,9,10,11,12,2,2,3,4,4,5,6,7,8,10,11,12"),
  test("ooxxoooooxox,ooxxoxxxxxox", "1,1,2,2,5,7,7,9,9,10,11,12,3,3,4,4,5,6,6,8,8,10,11,12|3,3,4,4,5,7,7,9,9,10,11,12,1,1,2,2,5,6,6,8,8,10,11,12"),
  test("xoxoxxooxoxx,ooxoxoxooxxo", "2,3,4,5,6,6,7,7,8,11,12,12,1,1,2,3,4,5,8,9,9,10,10,11|2,3,4,5,6,8,9,9,10,11,12,12,1,1,2,3,4,5,6,7,7,8,10,11"),
  test("oooooooooooo,xxxxxxxxxxxx", "1,1,3,3,5,5,7,7,9,9,11,11,2,2,4,4,6,6,8,8,10,10,12,12"),
  halt.

私のマシンでこれぐらい。そこそこの早さは出ました。

$ time gprolog --consult-file orde19.pro --entry-goal 'main'
GNU Prolog 1.4.4 (64 bits)
Compiled Jan  7 2016, 21:03:39 with clang
By Daniel Diaz
Copyright (C) 1999-2013 Daniel Diaz

...中略...

passed  input: xoxoxxooxoxx,ooxoxoxooxxo
passed  input: oooooooooooo,xxxxxxxxxxxx

real    0m0.144s
user    0m0.129s
sys     0m0.008s

2017/11/05 08:12 追記

とっちらかっていた部分を整理して、コメントつけました。
順番入れ替えたり名前を変えたりしていますが、ほぼ同じものです。

% gprolog --consult-file orde19.pro --entry-goal 'main'

% 数値を文字列に変換してコンマで連結する
join_comma([C], D) :-
  number_codes(C, D).
join_comma([C|Cards], Acc) :-
  number_codes(C, D),
  append(D, [0',|Prev], Acc),
  join_comma(Cards, Prev).

% 文字列をパイプ (|) で連結する
join_pipe([R], R).
join_pipe([R|Rs], Acc) :-
  append(R, [0'||Prev], Acc),
  join_pipe(Rs, Prev).

% 各 2 つずつ出現しソートされた数値列
reference_cards(Length, Cards) :-
  findall(X, between(1, Length, X), XS),
  append(XS, XS, S),
  msort(S, Cards).

% 入力の文字列と 2 の剰余
paritychar_rest(0'x, 0).
paritychar_rest(0'o, 1).

% 記号列に合う数値の並び列を生成する
% card_sequence(全体の長さ, 偶奇を表す文字列, 2 つ前の数値, 1 つ前の数値, 数値の並び)
card_sequence(_, [], _, _, []).
card_sequence(Length, [P|Parities], Prev, Prev, [C|Cards]) :- % 直前二つの数値が同じ場合
  succ(Prev, Prev1),
  paritychar_rest(P, R),
  between(Prev1, Length, C),                                  % 直前の値 より大きく、長さ以下の値を選ぶ
  C mod 2 =:= R,
  card_sequence(Length, Parities, Prev, C, Cards).
card_sequence(Length, [P|Parities], Prev0, Prev1, [C|Cards]) :- % 直前二つの数値が異なる場合
  Prev0 < Prev1,
  paritychar_rest(P, R),
  between(Prev1, Length, C),                                    % 直前の値以上、長さ以下の値を選ぶ
  C mod 2 =:= R,
  card_sequence(Length, Parities, Prev1, C, Cards).
card_sequence(Length, Parities, Cards) :-
  card_sequence(Length, Parities, 0, 0, Cards).

% 上下の記号列に合う数値の並び列を生成する
lineup(Length, UpperParities, LowerParities, ReferenceCards, Result) :-
  card_sequence(Length, UpperParities, UpperCards), % 上段の数値の並び
  card_sequence(Length, LowerParities, LowerCards), % 下段の数値の並び
  append(UpperCards, LowerCards, AllCards),         % 上下段を連結
  msort(AllCards, ReferenceCards),                  % ソートしたものがリファレンスと一致する
  join_comma(AllCards, Result).                     % 文字列に変換してコンマで連結する

solve(Input, Result) :-
  append(UpperParities, [0',|LowerParities], Input), % コンマで分割
  length(UpperParities, Length),                     % 長さ
  reference_cards(Length, ReferenceCards),           % ソートした場合に得られる数値列
  findall(R, lineup(Length, UpperParities, LowerParities, ReferenceCards, R), Rs), % 正答をすべて集める
  join_pipe(Rs, Result).                             % パイプ (|) で連結する

judge(Input, Expected, Expected) :-
  format("\x1b\[32mpassed  input: ~s\x1b\[0m~n", [Input]), !.
judge(Input, Expected, Actual) :-
  format("\x1b\[31mfailed  input: ~s, expected: ~s, actual: ~s\x1b\[0m~n", [Input, Expected, Actual]), !.

test(Input, Expected) :-
  solve(Input, Actual),
  judge(Input, Expected, Actual).

main :-
  test("xxoxo,oooxo", "2,2,3,4,5,1,1,3,4,5"),
  % 略
  halt.
2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0