💻

AtCoder に登録したら解くべき精選過去問 10 問を V言語 で改めて解いてみた

2023/03/27に公開

どうも。

私は4年前(2023/3/27現在)にAtCoder に登録したら解くべき精選過去問 10 問を V言語 で解いてみたという記事を投稿しました。
これを書いたころはまだまだV言語は未熟な言語でしたが、現在は段々安定してきておりほぼほぼ実用レベルに達していきつつあります。

ところでAtCoderは2023年の言語アップデートを始めたそうで、その中にはV言語があります。
https://docs.google.com/spreadsheets/d/1HXyOXt5bKwhKWXruzUvfMFHQtBxfZQ0047W7VVObnXI/edit
そして以下のコンテストからなんと現在V言語を試すことができます。AtCoderにV言語があるが?????????
https://atcoder.jp/contests/language-test-202301

そこで、もう一回この問題たちを解いてみようと思います。前の記事は現在のV言語に沿わないものもありますので、その点を踏まえていきます。

V言語ホームページ https://vlang.io/
V言語GitHub https://github.com/vlang/v
ABS(AtCoder) https://atcoder.jp/contests/abs

入力

前の記事を書いたころはC言語とのFFIを用いて書いていました。勿論今でもそれをすることはできるのですが、今回はそれを避けようと思います。現在使える入力方法は2つあります。

os.get_line()

1行1行標準入力からstringを入力していってそれを地道にintに変換する方法です。
どこぞのJavaScriptみたいな感じです。

import os

a := os.get_line().int() // 1行を1つのintにする
bc := os.get_line().split(' ').map(it.int()) // 1行の2つ以上の数字をintにする
ss := os.get_lines() // 複数行を入力し文字の配列にする

ただし、入力フォーマット(スペースや改行の位置)を問題ごとに確認する手間が発生します。
そこで、今回は次の方法を用います。

proconio ライブラリを使う

手前味噌ですが、拙作のproconioライブラリを使う方法です。詳しい解説は以下にあります。
https://zenn.dev/lemoncmd/articles/dd3b4eaa629cc7
Language Testsで使えるようになったため、今回はこちらの手法を用いて解説していきます。

解答

0問目 PracticeA - はじめてのあっとこーだー(Welcome to AtCoder)

import lemoncmd.proconio as pio
 
struct Input {
    a int
    b int
    c int
    s string
}
 
i := pio.input[Input]()
 
println('${i.a + i.b + i.c} ${i.s}')

入出力を問われている問題です。

V言語のスクリプト記法を用いています。
これは、コードをベタに書くことでさもmain関数の中に書かれているかのように扱ってくれる機能です。
せっかち競プロerには嬉しい機能ですね。

また、文字列リテラルの中で${式}と書くことで、式を文字列化して文字列の中に入れてくれます。
これは出力には便利ですね。

1問目 ABC086A - Product

import lemoncmd.proconio as pio
 
struct Input {
    a int
    b int
}
 
i := pio.input[Input]()
 
println(if i.a * i.b % 2 == 0 { 'Even' } else { 'Odd' })

簡単な数値演算です。

V言語のif文はRustやKotlinみたいな最近の言語らしく、三項演算子のように値を返すことができます。

2問目 ABC081A - Placing Marbles

import lemoncmd.proconio as pio
import arrays
 
s := pio.input[string]().split('').map(it.int())
println(arrays.sum(s)!)

文字列処理...のはずですが、今回は少しズルをしています。

stringsplitメソッドによって文字を1文字ずつ分割をしてそれを数値化し、arrays.sum関数でその総和を取っています。

なお、!はエラー処理をする演算子で、関数の実行時にエラーが起こった際にpanicします。

3問目 ABC081B - Shift only

import lemoncmd.proconio as pio
import math
 
struct Input {
    n int
    a []int [n]
}
 
mut a := pio.input[Input]().a

mut min := 1000
for mut ai in a {
    mut x := 0
    for ai % 2 == 0 {
    	x++
        ai >>= 1
    }
    min = math.min(min, x)
}
 
println(min)

2で割れる回数を求めてその最小値を出します。

V言語では変数を何回でも代入可能にするためには変数宣言の前にmutキーワードを付ける必要があります。

また、V言語にはfor文が4種類あり、それぞれ無限ループ、while、イテレーション、Cスタイルです。
今回は外側のfor文は要素が代入可能なイテレーション、内側のfor文は他の言語で言えばwhile文のような条件指定を行っています。

少ない値がどちらかを判定するのにmath.min関数を使っています。
この関数はジェネリクスを使っているため、どの比較可能な型でも使えます。

4問目 ABC087B - Coins

import lemoncmd.proconio as pio
 
struct Input {
    a int
    b int
    c int
    x int
}
 
i := pio.input[Input]()
 
mut count := 0
for aa in 0..i.a+1 {
    for bb in 0..i.b+1 {
    	for cc in 0..i.c+1 {
            if 500*aa + 100*bb + 50*cc == i.x {
                count++
            }
        }
    }
}
 
println(count)

有り得る全ての枚数について全探索します。
制約と計算量からして愚直解で通ります。

5問目 ABC083B - Some Sums

import lemoncmd.proconio as pio
import arrays
 
struct Input {
    n int
    a int
    b int
}

i := pio.input[Input]()

mut count := 0
for j in 0..i.n+1 {
    sum := arrays.sum(j.str().split('').map(it.int()))!
    if i.a <= sum && sum <= i.b {
    	count += j
    }
}

println(count)

1からnの数について、全ての桁を足します。
今回は数値を文字列に変換し、Placing Marblesと同じ方法で各桁を足す方法を用いました。

6問目 ABC088B - Card Game for Two

import lemoncmd.proconio as pio
 
struct Input {
    n int
    a []int [n]
}

mut a := pio.input[Input]().a
a.sort(a > b)
 
mut ans := 0
mut t := 1
for aa in a {
    ans += t * aa
    t = -t
}
 
println(ans)

降順にソートして足し引きをします。

ソートはarraysortメソッドを利用します。
sortは特殊なメソッドです。abという特殊な変数が渡され、括弧内に式を書けばそれがソートの条件文として機能します。

7問目 ABC085B - Kagami Mochi

import lemoncmd.proconio as pio
 
struct Input {
    n int
    d []int [n]
}

mut d := pio.input[Input]().d
d.sort()
 
mut count := 0
mut before := -1
for dd in d {
    if before != dd {
    	count++
    }
    before = dd
}
 
println(count)

昇順にクイックソートして被りを抜いて数えます。

sort関数の条件文を省略すると勝手に昇順になります。

8問目 ABC085C - Otoshidama

import lemoncmd.proconio as pio
 
struct Input {
    n int
    y int
}

i := pio.input[Input]()
n := i.n
y := i.y

mut a := y / 10000
mut b := (y - a * 10000) / 5000
mut c := (y - a * 10000 - b * 5000) / 1000
if a + b + c > n || y / 1000 < n {
    println('-1 -1 -1')
    return
}
for n > a + b + c {
    if n - a - b - c >= 9 && a > 0 {
        a--
        c += 10
    } else if n - a - b - c >= 4 && b > 0 {
        b--
        c += 5
    } else if a > 0 {
        a--
        b += 2
    } else {
        println('-1 -1 -1')
        return
    }
}
println('${a} ${b} ${c}')

前回のようにお札の枚数で全探索してもよかったのですが、今回は真面目に貪欲法でお札の枚数を求めてからお札を両替していきます。

スクリプト構文で途中でプログラムを終了するには、main関数で書くときと同様にreturn文を書きます。

9問目 ABC049C - 白昼夢 / Daydream

import lemoncmd.proconio as pio
 
println(if pio.input[string]().replace('eraser', '').replace('erase', '').replace('dreamer', '').replace('dream', '') == '' {
	'YES'
} else {
	'NO'
})

いつもの嘘解法です。

10問目 ABC086C - Traveling

import lemoncmd.proconio as pio
import math

pub struct Time {
    t int
    x int
    y int
}

struct Input {
    n int
    ts []Time [n]
}

ts := pio.input[Input]().ts

mut before := Time{0, 0, 0}
for t in ts {
    dt := t.t - before.t
    dxdy := math.abs(t.x - before.x) + math.abs(t.y - before.y)
    if dxdy > dt || (dt - dxdy) % 2 == 1 {
        println('No')
        return
    }
    before = t
}
println('Yes')

歩数が足りるかどうかと、往復で時間をつぶしてたどり着けるか(偶奇でわかる)を判定します。
なお、ライブラリの都合でstructを入れ子にする場合はpubを内側のstruct宣言の手前に書いてください。

あとがき

このままV言語がAtCoderに実装されたらうれしいですね。
簡単な構文で書けてしかも実行速度もC言語並みに速いV言語、競プロにいかがでしょうか。

Discussion