🦬

Emacs + Ruby での AtCoder 環境が良すぎる

2024/01/29に公開

Emacs と xmpfilter の組み合わせが最高

v = "Hello, world!"                  # => "Hello, world!"
v = v.chars                          # => ["H", "e", "l", "l", "o", ",", " ", "w", "o", "r", "l", "d", "!"]
v = v.collect { |e| e.ord.to_s(2) }  # => ["1001000", "1100101", "1101100", "1101100", "1101111", "101100", "100000", "1110111", "1101111", "1110010", "1101100", "1100100", "100001"]
v = v.join                           # => "1001000110010111011001101100110111110110010000011101111101111111001011011001100100100001"
v = v.chars                          # => ["1", "0", "0", "1", "0", "0", "0", "1", "1", "0", "0", "1", "0", "1", "1", "1", "0", "1", "1", "0", "0", "1", "1", "0", "1", "1", "0", "0", "1", "1", "0", "1", "1", "1", "1", "1", "0", "1", "1", "0", "0", "1", "0", "0", "0", "0", "0", "1", "1", "1", "0", "1", "1", "1", "1", "1", "0", "1", "1", "1", "1", "1", "1", "1", "0", "0", "1", "0", "1", "1", "0", "1", "1", "0", "0", "1", "1", "0", "0", "1", "0", "0", "1", "0", "0", "0", "0", "1"]
v = v.chunk(&:itself).to_a           # => [["1", ["1"]], ["0", ["0", "0"]], ["1", ["1"]], ["0", ["0", "0", "0"]], ["1", ["1", "1"]], ["0", ["0", "0"]], ["1", ["1"]], ["0", ["0"]], ["1", ["1", "1", "1"]], ["0", ["0"]], ["1", ["1", "1"]], ["0", ["0", "0"]], ["1", ["1", "1"]], ["0", ["0"]], ["1", ["1", "1"]], ["0", ["0", "0"]], ["1", ["1", "1"]], ["0", ["0"]], ["1", ["1", "1", "1", "1", "1"]], ["0", ["0"]], ["1", ["1", "1"]], ["0", ["0", "0"]], ["1", ["1"]], ["0", ["0", "0", "0", "0", "0"]], ["1", ["1", "1", "1"]], ["0", ["0"]], ["1", ["1", "1", "1", "1", "1"]], ["0", ["0"]], ["1", ["1", "1", "1", "1", "1", "1", "1"]], ["0", ["0", "0"]], ["1", ["1"]], ["0", ["0"]], ["1", ["1", "1"]], ["0", ["0"]], ["1", ["1", "1"]], ["0", ["0", "0"]], ["1", ["1", "1"]], ["0", ["0", "0"]], ["1", ["1"]], ["0", ["0", "0"]], ["1", ["1"]], ["0", ["0", "0", "0", "0"]], ["1", ["1"]]]
v = v.collect { |_, a| a.join }      # => ["1", "00", "1", "000", "11", "00", "1", "0", "111", "0", "11", "00", "11", "0", "11", "00", "11", "0", "11111", "0", "11", "00", "1", "00000", "111", "0", "11111", "0", "1111111", "00", "1", "0", "11", "0", "11", "00", "11", "00", "1", "00", "1", "0000", "1"]
v = v.shuffle                        # => ["00", "0", "1", "00", "00", "0", "0", "11", "0", "0", "00", "1", "0", "11", "111", "11", "1", "111", "0", "11", "00", "00", "1111111", "1", "0", "00", "11111", "11", "00", "11", "00000", "00", "1", "11", "0", "11", "000", "11", "1", "1", "11111", "1", "0000"]
v = v.join                           # => "0001000000110000101111111111101100001111111100011111110011000000011101100011111111110000"
v = v.to_i(2)                        # => 19573028124074184323514352

るびきち氏の開発した xmpfilter (rcodetools) は、各行の評価結果をコメントに入れてくれる。これによって書いている途中で実行結果を確認しながら進めることができる。

入力はソースコードに書く

if $0 == "-"
  require "stringio"
  $stdin = StringIO.new(<<~EOS)
hello
EOS
end

S = gets.strip  # => "hello"
p S.length      # => 5

$0 == "-" のとき、すなわち xmpfilter で実行したときだけ $stdin を置き換えるようにすれば、gets は StringIO のバッファの内容を読む。もちろん、そのまま提出すればよく、ジャッジシステム側では標準入力から読み込まれる。

問題が標準入力からの受け取りになっているからといって、コマンドラインで実行する必要もないし、入力例をソースコードに含めたまま提出できるので、この柔軟性も最高である。

入力例を __END__ の下に置く方法もある

S = gets.strip  # => "hello"
p S.length      # => 5
__END__
hello

上のコードを xmpfilter に通すと gets に __END__ の下のテキストが入ってくる。$stdin = DATADATA.gets などと書く必要はない。これもそのまま提出できる。

昔はこの方法を多用していたが、一つのソースコードに複数書けないため、現在はあまり使っていない。

テストを書きたい場合

if ENV["ATCODER"] != "1"
  require "rspec/autorun"
  RSpec.configure do |config|
    config.expect_with :test_unit
  end

  RSpec.describe do
    it "works" do
      assert { 1 + 2 == 3 }
    end
  end
end

これも $0 == "-" の条件下に入れてしまえばいいのだけど「コピペ用ライブラリファイル単体は xmpfilter で実行するとは限らないけど、AtCoder 側では実行されたくない」という、ようするに、ちょっとややこしい状況があったりする。

この場合、AtCoder の実行環境では環境変数 ATCODER"1" で定義されているので、ENV["ATCODER"] != "1" の条件下に入れておけば提出先で実行される心配がない。

したがって、競プロで TDD したっていい。提出するコードに思う存分テストを書けばいい。

関連

こちらではセットアップや一瞬で提出する手順を紹介している。

https://zenn.dev/megeton/articles/72d8bf71da39cb

Discussion