はじめてのNim(Nim初心者によるAtcoder精選過去問10問)

どーも三十三間党です。皆さんNimという言語をご存知でしょうか。

Nimは爆速で動くPythonです。嘘です。

 なないろ言語で競プロまとめ(https://qiita.com/drken/items/6edb1c0542d4c3b7179c)を見ながら、C++ばりの速さが出て、Pythonみたいな書き方できる言語ないかなーと探していたら、2つほど候補が見つかりました。CrystalとNimです。しかしコードを見ていると、やはりCrystalはRubyをリスペクトされて書かれた言語なので、Rubyを学んだことのない僕には少し読みづらかったです。

そこでNimのコードを見るとあらびっくり、これPythonじゃね?と思うほど個人的にはコードが似ていたので,勉強しようと思いました。

今回はそのNimを使って、「AtCoderに登録したら次にやること ~ これだけ解けば十分闘える!過去問精選 10 問 ~」

https://qiita.com/drken/items/fd4e5e3630d0f5859067

を解きました。一応Pythonで一回解いたことはあります。

なお、ACしているので、動くコードとは思いますが、Nim初心者(というかAtCoderも初心者)なので冗長だったり、間違った文章を書いたりしている等あると思います。ご了承願います。

今回からはMarkdown方式で書いているので、コードの見づらさは多少改善されているかと思います。

Nimならこういう書き方良いよ!っていうのがあれば教えてください! 

1.Welcome to AtCoder

import sequtils, strutils
var
  a = parseInt(readline(stdin))
  bc = map(split(readline(stdin)),parseInt)
  s = readline(stdin)
echo (a+bc[0]+bc[1]," ",s

標準入力の()多いかな〜と思いましたが、まあpythonでもこんなもんですよね。空白区切りの標準入力はimport要るんですね。stdinはstandard inputの略か。変数宣言に型は必須ではありませんが、変数の場合varは必須そう。

リストで受け取ってしまいましたが、

var
  a, b, c: int
(a, b, c) = stdin.readLine.split.map(parseInt)

という分割代入の方法もあるそうな。

2.Product

import sequtils,strutils
var ab = map(split(readline(stdin)),parseInt)<200b>
if (ab[0] * ab[1]) mod 2 == 0:
  echo "Even"
else:
  echo "Odd"

 %ではなくてmod。ifの書き方Pythonと一緒ですね。

3.Placing Marble

var S = stdin.readLine
var c = 0
for s in S:
  if s == '1':
    c += 1
echo c

for s in S(文字列)という書き方もできちゃう。ちなみに文字列はmutableだそうで。

 4.Shift Only 

import strutils
import sequtils
var
  N = stdin.readline.parseint
  A = stdin.readline.split.map(parseint)
ans = 0
block myblock:
  while true:
    for i in 0..N-1:
      if A[i] mod 2 != 0:
        break myblock
    A[i] = int(A[i]/2)
    ans += 1
echo ans

なんか急に標準入力受け取る書き方変えてしまいました(やってることはおそらく同じですが)。なんか()の部分を.で書き換えられる?らしい。

block文が個人的には新しい。最初に自分でblockを作っておくと、breakでどのblockから抜けるか選べます。多重ループ 抜け方 でググることもなくなりますね。

while文も書き方同じですが、Trueではなくてtrueですね。ちなみにNimは大文字小文字を区別しないようですが、最初の一文字目だけ区別するようです。

for文の中の、0..N-1という書き方で、Pythonのrange(N)と同じみたい。

/は何があろうとfloat型返すらしく、int型に変換しないと元の変数に代入することができません。そんなもんか。

2進法に書き換えて云々で解きたかったのですが2進数への書き換えが意外と面倒くさい?のかも  

5.Coins 

import strutils
var
  A = stdin.readline.parseint
  B = stdin.readline.parseint
  C = stdin.readline.parseint
  X = stdin.readline.parseint
ans = 0
for a in 0..A:
  for b in 0..B:
    for c in 0..C:
      if 500*a + 100*b + 50*c == X:
        inc(ans)
echo ans

ans += 1はinc(ans)でもおっけー。今まで全部実行時間1msですね。

6.Some Sums

import sequtils, strutils, math
var
  N,A,B:int
ans = 0
(N,A,B) = stdin.readline.split.map(parseint)
for i in 1..N:
  var
    c = 0
    x = i
  while x > 0:
    c += x mod 10
    x = int(x/10)
  if A <= c and c <= B:
    ans += i
echo ans

気づいてませんでしたが、

import ho
import ge

import ho, ge

という書き方もできるようです。 

 Pythonで書いていたときは文字列リストに変換してから整数に変換し、その和を取るという解き方をしていたので、その書き方をしようとしたら思った以上に面倒くさかったのでやめました。型を変えずに処理できるときはそうしたほうが良さそうかも。

7.Card Game for Two

import strutils
import sequtils
import algorithm
 
proc getInt() : int =
  parseInt(readLine(stdin))
 
proc getInts() : seq[int] =
  readLine(stdin).split().map(parseInt)
var
  n = getInt()
  a = getInts()
  ans = 0
a.sort(cmp,Descending)
for i in 0..<n:
  if i mod 2 == 0:
    ans += a[i]
  else:
    ans -= a[i]
echo ans

標準入力長いなーと思っていたら(普通の長さかもしれませんが)、関数にしてまとめている方がいらっしゃったので真似させていただきました。

sortのDescendingで降順に並べ替えて(引数cmpはよくわかりませんでした)解きました。

8.Kagami Mochi

import strutils
import sequtils
import sets
proc getInt() : int =
  parseInt(readLine(stdin))
var
  n = getInt()
  d = initset[int](8)
for i in 1..n:
  d.incl(getInt())
echo d.card

集合の要素数を数えます。initsetintで2の冪乗サイズの整数型が入る集合になるようです。

9.Otoshidama

import strutils
import sequtils
var
  ny = stdin.readline.split.map(parseint)
  n = ny[0]
  y = ny[1]
block myblock:
  for i in 0..n:
    for j in 0..n-i:
      if i * 10000 + j * 5000 + (n-i-j) * 1000 == y :
        echo (i," ",j," ",n-i-j)
        break myblock
echo("-1 -1 -1")

実行時間が3msになりました。

個人的には3重ループ間に合わねーぜ!っていう問題だと思っていたのですが、

import strutils
import sequtils
var
  ny = stdin.readline.split.map(parseint)
  n = ny[0]
  y = ny[1]
block myblock:
  for i in 0..n:
    for j in 0..n-i:
      for k in 0..n-i-j:
        if i * 10000 + j * 5000 + k * 1000 == y and i+j+k == n:
          echo (i," ",j," ",k)
          break myblock
echo("-1 -1 -1")

3重ループ間に合っちゃった……(実行時間905ms)速さは正義。

10.白昼夢/Daydream

import strutils,unicode
var
  s = readline(stdin)
words = ["eraser","dreamer","erase","dream"]
while true:
  for word in words:
    removesuffix(s,word)
      if s == "":
        echo "YES"
          break
      else:
        if not endswith(s,words[0]) and not endswith(s,words[1]) and not endswith(s,words[2]) and not endswith(s,words[3]):
          echo "NO"
          break

速え(当社比、実行時間3ms)。文字列操作強力ですね!

11.Traveling

import strutils,sequtils
var N = stdin.readline.parseint
for i in 1..N:
  var
    t,x,y:int
  (t,x,y) = stdin.readline.split.map(parseInt)
  if x + y > t or (((x+y) mod 2) != (t mod 2)):
    echo "No"
      quit()
echo "Yes"

実行時間59msと最長の結果になったのですが、他の人の提出結果も見たところ、こんなもんかと思われますが,Nimの最速は11msでした。

感想 速い、Pythonっぽい、好き(小並感)