投稿者「オオタ」のアーカイブ

PaizaAランクLvアップ問題と解説(Ruby)その6

3から続きます

※スキルチェック問題ではありません。
規約により公式の解答コードそのままはよろしくないので、
オリジナルのコードにしています。

詳しくはコチラ

なるべくわかりやすい解説を付けました。

マップの判定・縦横

1:移動が可能かの判定・方角 (paizaランク B 相当)

マップの行数 H と列数 W , 障害物を ‘#’ で、移動可能な場所を ‘.’ で表した H 行 W 列のマップ S_1 … S_H が与えられます。
続けて現在の座標 sy , sx ,1マス移動する方角 m が与えられるので、移動が可能かどうかを判定してください。

移動が可能であるということは、以下の図の通り
「移動先が障害物でない かつ 移動先がマップの範囲外でない」
ということを意味します。

なお、マスの座標系は左上端のマスの座標を ( y , x ) = ( 0 , 0 ) とし、
下方向が y 座標の正の向き、右方向が x 座標の正の向きとします。


入力される値

H W sy sx m     
S_0    
...     
S_(H-1)
・ 1 行目にはマップの行数を表す整数 H , マップの列数を表す整数 W , 現在の y, x 座標を表す sy sx , 1 マス移動する方角 m が与えられます。
・ 続く H 行のうち i 行目 (0 ≦ i < H) には、マップの i 行目の文字をまとめた文字列 S_i が与えられ、 S_i の j 文字目は、マップの i 行目の j 列目に書かれている文字を表します。(0 ≦ j < W)入力値最終行の末尾に改行が1つ入ります。
期待する出力
移動が可能である場合 “Yes” を、不可能である場合 “No” を出力してください。

Yes

または

No

条件

すべてのテストケースにおいて、以下の条件をみたします。

・ 1 ≦ H, W ≦ 20
・ 0 ≦ sy < H , 0 ≦ sx < W
・ S_i は W 文字の文字列
・ マップ上の(sy, sx)のマスは必ず '.'
・ S の各文字は '.' または '#'
・ m は、N, S, E, W のいずれかであり、それぞれ 北・南・東・西 を意味します。
入力例1

3 3 1 1 E
..#
..#
...
出力例1

No
入力例2

9 2 4 0 S
#.
#.
..
##
..
..
.#
..
.#
出力例2

Yes

解答と解説(入力例1で行います)

#方角のみ文字列なので、それ以外を数値として変数に代入して、mに文字列として代入する
h,w,y,x,m = gets.split.map.with_index { |val, i| i == 4 ? val : val.to_i }

#盤面の情報をバラバラにして代入する 結果→[[".", ".", "#"], [".", ".", "#"], [".", ".", "."]]
chart = h.times.map { gets.chomp.chars}

#移動操作した時の座標の移動パターンを作成する。
direction = { N: [-1, 0], S: [1, 0], E: [0, 1], W: [0, -1] } 
#入力例1の場合mがEなので[0, 1]yとxに加算する
y += direction[m.to_sym][0]
x += direction[m.to_sym][1]

#画面外だとアウトなのでyとxが0以上でyがh未満xがw未満であることを記述する
#移動後の座標の現在地(chart[y][x])が"."であることを満たしたときに"Yes"
  if y >= 0 && x >= 0 && y < h && x < w && chart[y][x] == "."
    puts "Yes"
  それ以外を"No"と出力する
  else puts "No"
end

2:移動が可能かの判定・方向 (paizaランク B 相当)

マップの行数 H と列数 W , 障害物を ‘#’ , 移動可能な場所を ‘.’ で表した H 行 W 列のマップ S_1 … S_H が与えられます。
続けて現在の座標 sy , sx , 現在向いている方角 d , 1マス移動する方向 m が与えられるので、移動が可能かどうかを判定してください。

移動が可能であるということは、以下の図の通り
「移動先が障害物でない かつ 移動先がマップの範囲外でない」
ということを意味します。

なお、マスの座標系は左上端のマスの座標を ( y , x ) = ( 0 , 0 ) とし、
下方向が y 座標の正の向き、右方向が x 座標の正の向きとします。


入力される値

H W sy sx d m      
S_0     
...     
S_(H-1)
・ 1 行目にはマップの行数を表す整数 H , マップの列数を表す整数 W , 現在の y, x 座標を表す sy sx , 現在向いている方角 d , 1 マス移動する方向 m が与えられます。
・ 続く H 行のうち i 行目 (0 ≦ i < H) には、マップの i 行目の文字をまとめた文字列 S_i が与えられ、 S_i の j 文字目は、マップの i 行目の j 列目に書かれている文字を表します。(0 ≦ j < W)
入力値最終行の末尾に改行が1つ入ります。
期待する出力
移動が可能である場合 “Yes” を、不可能である場合 “No” を出力してください。

Yes

または

No
条件

すべてのテストケースにおいて、以下の条件をみたします。

・ 1 ≦ H, W ≦ 20
・ 0 ≦ sy < H , 0 ≦ sx < W
・ S_i は W 文字の文字列
・ マップ上の(sy, sx)のマスは必ず '.'
・ S の各文字は '.' または '#'
・ d は、N, S, E, W のいずれかであり、それぞれ 北・南・東・西 を意味します。
・ m は、L, R のいずれかであり、それぞれ 左・右 を意味します。
入力例1

2 6 0 4 E L
####..
##..#.
出力例1

No
入力例2

7 9 6 0 S R
..#.#..##
..#..#...
#.......#
#.#...###
#.##....#
.....#...
..##..#.#
出力例2

No

解答と解説(入力例1で行います)

#dとmが文字列なので、それ以外を数値として変数に代入して、dとmに文字列として代入する 
h,w,y,x,d,m = gets.split.map.with_index { |val, i| i >= 4 ? val : val.to_i }

#盤面の情報をバラバラにして代入する  結果→[["#", "#", "#", "#", ".", "."], ["#", "#", ".", ".", "#", "."]]
chart = h.times.map { gets.chomp.chars}

#Rが来たときの移動パターンを設定する
operateR = { N: [0, 1], S: [0, -1], E: [1, 0], W: [-1, 0] } 
#Lが来たときの移動パターンを設定する
operateL ={ N: [0, -1], S: [0, 1], E: [-1, 0], W: [1, 0] } 

#入力例1の場合mがLで向いている方向がEなので[-1, 0]yとxに加算する
if m == "R"
  y += operateR[d.to_sym][0] 
  x += operateR[d.to_sym][1] 
elsif m == "L"
  y += operateL[d.to_sym][0] 
  x += operateL[d.to_sym][1] 
end

#画面外だとアウトなのでyとxが0以上でyがh未満xがw未満であることを記述する
#移動後の座標の現在地(chart[y][x])が"."である条件を満たしたときに"Yes"
  if y >= 0 && x >= 0 && y < h && x < w && chart[y][x] == "."
    puts "Yes"
  else puts "No"
end

※移動後の座標が[-1,4]で画面外なので”No”が出力された

3:移動が可能かの判定・複数回の移動 (paizaランク B 相当)

マップの行数 H と列数 W , 現在の座標 sy , sx , 移動の回数 N が与えられます。
続けて、障害物を ‘#’ で、移動可能な場所を ‘.’ で表した H 行 W 列 のマップ S_1 … S_H と N 回の移動の向き d_1 … d_N が与えられます。

移動者ははじめ北を向いています。移動者は、1 回の移動で次の行動を行います。

「移動の向きに方向転換したのち、1 マス進む。」

各移動が可能である場合、移動後の y , x 座標を出力してください。
移動が可能でない場合、移動後の座標を出力する代わりに “Stop” を出力して、以降の移動を打ち切ってください。

各移動が可能であるということは、以下の図の通り
「移動先が障害物でない かつ 移動先がマップの範囲外でない」
ということを意味します。

なお、マスの座標系は左上端のマスの座標を ( y , x ) = ( 0 , 0 ) とし、
下方向が y 座標の正の向き、右方向が x 座標の正の向きとします。


右に移動

入力される値

H W sy sx N     
S_0     
...     
S_(H-1)     
d_1     
...     
d_N
・ 1 行目にはマップの行数を表す整数 H , マップの列数を表す整数 W , 現在の y, x 座標を表す sy sx , 移動する回数 N が与えられます。
・ 続く H 行のうち i 行目 (0 ≦ i < H) には、マップの i 行目の文字をまとめた文字列 S_i が与えられ、 S_i の j 文字目は、マップの i 行目の j 列目に書かれている文字を表します。(0 ≦ j < W)
・ 続く N 行のうち i 行目 (1 ≦ i ≦ N) には、i 回目の移動の向き d_i が与えられます。
入力値最終行の末尾に改行が1つ入ります。
期待する出力
M (1 ≦ M ≦ N) 行の出力

・ k (1 ≦ k ≦ M) 回目の移動後の y , x 座標、y_k, x_k を出力してください。
・ ただし、M 回目で移動しきれない場合、”Stop” を出力してください。

y_1 x_1
...
y_k x_k
...
y_M x_M

または

y_1 x_1
...
y_k x_k
...
y_(M-1) x_(M-1)
Stop
条件

すべてのテストケースにおいて、以下の条件をみたします。

・ 1 ≦ H, W ≦ 20
・ 1 ≦ N ≦ 100
・ 0 ≦ sy < H , 0 ≦ sx < W
・ S_i は W 文字の文字列
・ マップ上の(sy, sx)のマスは必ず '.'
・ S_i の各文字は '.' または '#'
・ d_i は、L, R のいずれかであり、それぞれ 左・右 を意味します。
入力例1

7 3 2 1 5
..#
...
...
...
..#
.#.
##.
L
L
L
L
L
出力例1

2 0
3 0
3 1
2 1
2 0
入力例2

7 11 1 5 43
.##........
.#......##.
.#....#...#
.###......#
#......###.
..#....###.
#.#........
L
L
R
L
R
L
R
L
L
R
L
R
L
L
L
L
R
R
R
L
R
L
R
L
L
R
L
L
R
L
R
L
R
R
R
R
L
R
L
L
L
R
R
出力例2

1 4
2 4
2 3
Stop

解答と解説(入力例2で行います)

直感的でわかりやすいけどあまりよろしくないかも

#数値として各変数に代入する
h,w,y,x,n = gets.split.map(&:to_i)

#盤面の情報をバラバラにして代入する 実行 → [[".", "#", "#", ".", ".", ".", ".", ".", ".", ".", "."], [".", "#", ".", ".", ".", ".", ".", ".", "#", "#", "."], [".", "#", ".", ".", ".", ".", "#", ".", ".", ".", "#"], [".", "#", "#", "#", ".", ".", ".", ".", ".", ".", "#"], ["#", ".", ".", ".", ".", ".", ".", "#", "#", "#", "."], [".", ".", "#", ".", ".", ".", ".", "#", "#", "#", "."], ["#", ".", "#", ".", ".", ".", ".", ".", ".", ".", "."]]
chart = h.times.map { gets.chomp.chars}

# Rが来たときの移動パターンを設定する
operateR = { N: [0, 1], S: [0, -1], E: [1, 0], W: [-1, 0] } 

#Lが来たときの移動パターンを設定する
operateL ={ N: [0, -1], S: [0, 1], E: [-1, 0], W: [1, 0] } 

#最初は北を向いているのでdに”N”を代入する
d = "N"

#コマンドがn回あるのでn回繰り返す
n.times do
  #RかLの入力値を受け取り代入する
  command = gets.chomp

#自分が向いている向きdとコマンドL,Rによってyとxを加算する
  if command == "R"
     y += operateR[d.to_sym][0] 
     x += operateR[d.to_sym][1] 
  elsif command == "L"
     y += operateL[d.to_sym][0] 
     x += operateL[d.to_sym][1] 
  end

#自分の向いている向きとコマンドのLかRかで自分の向いている向きを変更する。
  if d == "N" && command == "L" || d == "S" && command == "R"
     d = "W"
  elsif d == "W" && command == "L" || d == "E" && command == "R"
     d = "S"
  elsif d == "E" && command == "L" || d == "W" && command == "R"
     d = "N"
  elsif d == "S" && command == "L" || d == "N" && command == "R"
     d = "E"
  end

#画面外だとアウトなのでyとxが0以上でyがh未満xがw未満であることを記述する
#移動後の座標の現在地(chart[y][x])が"."である条件を満たしたときに"Yes"
  if y >= 0 && x >= 0 && y < h && x < w && chart[y][x] == "."
     puts y.to_s + " " + x.to_s

条件を満たさなかったときに”Stop”を出力して繰り返しを終わらせる(break)
  else puts "Stop"
     break
  end
end

別解

#数値として各変数に代入する
h, w, y, x, n = gets.split.map(&:to_i)

#盤面の情報をバラバラにして代入する 実行 → [[".", "#", "#", ".", ".", ".", ".", ".", ".", ".", "."], [".", "#", ".", ".", ".", ".", ".", ".", "#", "#", "."], [".", "#", ".", ".", ".", ".", "#", ".", ".", ".", "#"], [".", "#", "#", "#", ".", ".", ".", ".", ".", ".", "#"], ["#", ".", ".", ".", ".", ".", ".", "#", "#", "#", "."], [".", ".", "#", ".", ".", ".", ".", "#", "#", "#", "."], ["#", ".", "#", ".", ".", ".", ".", ".", ".", ".", "."]]
chart = h.times.map { gets.chomp.chars }

#移動するパターンを作成する 北 東 南 西 の順番
direction = [[-1, 0], [0, 1], [1, 0], [0, -1]]

#状態を定義する。最初は0
current = 0

#コマンドがn回あるのでn回繰り返す
n.times do
 #RかLの入力値を受け取り代入する
  d = gets.chomp
 #Lの時−1してRのときに+1する
  d == "L" ? current -= 1 : current += 1
  y += direction[current % 4][0]
  x += direction[current % 4][1]

  if y >= 0 && x >= 0 && y < h && x < w && chart[y][x] == "."
    puts y.to_s + " " + x.to_s
# 条件を満たさなかったときに”Stop”を出力して繰り返しを終わらせる(break)
  else puts "Stop"
    break
  end
end

入力例2

1ループ目

最初の状態はcurrent = 0でコマンドがL、yとx が[1,5]
currentが  -1になり
-1 % 4 は 3なので
direction[3]は[0,-1]
yとxに [0,-1]を加算して
yとx が[1,4]

2ループ目

最初の状態はcurrent = -1でコマンドがL、yとx が[1,4]
currentが  -2になり
-2 % 4 は 2なので
direction[2]は[1,0]
yとxに [1,0]を加算して
yとx が[2,4]

3ループ目

最初の状態はcurrent = -2でコマンドがR、yとx が[2,4]
currentが  -1になり
-1 % 4 は 3なので
direction[3]は[0,-1]
yとxに [0,-1]を加算して
yとx が[2,3]

4ループ目

最初の状態はcurrent = -2でコマンドがL、yとx が[2,3]
currentが  -2になり
-2 % 4 は 2なので
direction[2]は[1,0]
yとxに [1,0]を加算して
yとx が[3,4]
移動先が”#”があるので”Stop”を出力してループを抜ける

※ポイント

d == ‘L’ ? current -= 1 : current += 1
でcurrentの値を変えるだけで移動操作が表現できる。

4:移動が可能かの判定・幅のある移動 (paizaランク B 相当)

マップの行数 H と列数 W , 障害物を ‘#’ で移動可能な場所を ‘.’ で表した H 行 W 列のマップ S_1 … S_H , 現在の座標 sy, sx, 移動の回数 N が与えられます。
続けて、 N 回の移動の向き d_1 … d_N と移動するマス数 l_1 … l_N が与えられます。

プレイヤーははじめ北を向いています。

各移動が可能である場合、移動後の y , x 座標 を出力してください。
移動が可能でない場合(移動しきれない場合)、移動できるところまで移動した後の座標を出力した後に “Stop” を出力して、以降の移動を打ち切ってください。

各移動が可能であるということは、以下の図の通り
「今いるマスから移動先のマスまでに障害物がない かつ 移動先がマップの範囲外でない」
ということを意味します。

なお、マスの座標系は左上端のマスの座標を ( y , x ) = ( 0 , 0 ) とし、
下方向が y 座標の正の向き、右方向が x 座標の正の向きとします。


入力される値

H W sy sx N        
S_0         
...     
S_(H-1)     
d_1 l_1     
...     
d_N l_N
・ 1 行目にはマップの行数を表す整数 H , マップの列数を表す整数 W , 現在の y, x 座標を表す sy sx , 移動する回数 N が与えられます。
・ 続く H 行のうち i 行目 (0 ≦ i < H) には、マップの i 行目の文字をまとめた文字列 S_i が与えられ、 S_i の j 文字目は、マップの i 行目の j 列目に書かれている文字を表します。(0 ≦ j < W)
・ 続く N 行のうち i 行目 (1 ≦ i ≦ N) には、i 回目の移動の向き d_i と移動するマス数 l_i が与えられます。入力値最終行の末尾に改行が1つ入ります。
期待する出力
M (1 ≦ M ≦ N+1) 行の出力

・ k (1 ≦ k ≦ M) 回目の移動後の y , x 座標、y_k, x_k を出力してください。
・ M 回目で移動しきれない場合、移動できるところまで移動した後の y , x 座標、y_M, x_M を出力した後に “Stop” を出力してください。

y_1 x_1
...
y_k x_k
...
y_M x_M

または

y_1 x_1
...
y_k x_k
...
y_M x_M
Stop
条件

すべてのテストケースにおいて、以下の条件をみたします。

・ 1 ≦ H, W ≦ 20
・ 1 ≦ N ≦ 100
・ 0 ≦ sy < H, 0 ≦ sx < W
・ 1 ≦ l_i ≦ 20
・ S_i は W 文字の文字列
・ マップ上の(sy, sx)のマスは必ず '.'
・ S の各文字は '.' または '#'
・ d_i は、L, R のいずれかであり、それぞれ 左・右 を意味します。
入力例1

10 10 6 4 3
..#.....#.
..........
##.#......
#.##....#.
.##.#.....
........#.
.#......#.
.#........
...#......
#.#.......
L 2
R 1
L 4
出力例1

6 2
5 2
5 0
Stop
入力例2

15 15 6 4 7
.......#.......
....#.......#.#
.......#.....#.
.......#.#...#.
#......#.......
#.........#....
..............#
..#...#....#..#
............#..
..#...##......#
##..#..#.#.....
#..............
............#..
...#...........
.#.........#.#.
L 4
L 3
R 4
R 5
L 3
L 2
R 1
出力例2

6 0
9 0
9 0
Stop

解答と解説

少し無駄な部分があるがとりあえずできたほう

#数値として各変数に代入する
h, w, y, x, n = gets.split.map(&:to_i)

#盤面の情報をバラバラにして代入する
chart = h.times.map { gets.chomp.chars }

#移動するパターンを作成する 北 東 南 西 の順番
direction = [[-1, 0], [0, 1], [1, 0], [0, -1]]

#状態をそれぞれ定義する。最初は0
current,stop = 0,0

#コマンドがn回あるのでn回繰り返す
n.times do

 #文字列と数値をそれぞれの変数に代入
  d,move = gets.split.map.with_index { |val, i| i == 0 ? val : val.to_i }

 #Lの時−1してRのときに+1する
  d == 'L' ? current -= 1 : current += 1

 #移動する回数繰り返す
  move.times do |i|

#currentの状態によってyとxを加算する
  y += direction[current % 4][0]
  x += direction[current % 4][1]

#画面外だとアウトなのでyとxが0以上でyがh未満xがw未満であることを記述する
#移動後の座標の現在地(chart[y][x])が"."で繰り返しの最後のときに出力
  if y >= 0 && x >= 0 && y < h && x < w && chart[y][x] == "."
    puts y.to_s + " " + x.to_s if move == i + 1

#画面外や現在地が”#”のとき前の状態に戻してStopを1にして繰り返しを終わらせる
  else y -= direction[current % 4][0]
       x -= direction[current % 4][1]
       stop = 1
       break
  end
  end
# stopが1のときに現在地を出力してから改行して”Stop”を出力して繰り返しを終わらせる
  if stop == 1
     puts y.to_s + " " + x.to_s + "\n" +"Stop"
  break
end
end

PaizaAランクLvアップ問題と解説(Ruby)その5

※スキルチェック問題ではありません。
規約により公式の解答コードそのままはよろしくないので、
オリジナルのコードにしています。

詳しくはコチラ

なるべくわかりやすい解説を付けました

その5  座標系での向きの変わる移動 (paizaランク B 相当)

開始時点の x , y 座標、移動の回数 N が与えられます。
続くN行で移動の向き d1 … dN が与えられるので、与えられた順に移動をしたときの各移動後の x , y 座標 を答えてください。
移動者ははじめ北を向いています。

なお、マスの座標系は下方向が y 座標の正の向き、右方向が x 座標の正の向きとします。

・ 移動をするごとに向く方角が変わること
・ 移動前に向いている方角によって同じ移動の向きでも座標の変化が違うこと
の 2 点に気をつけてください。
例えば、上の図の状態から右に移動を行った場合、下の図のような状態になります。


入力される値
X Y N       
d1      
...     
dN

・ 1 行目には、開始時点の x , y 座標を表す X , Y, 移動の回数 N が与えられます。
・ 続く N 行 (1 ≦ i ≦ N) には、盤面の i 回目の移動の向きを表す文字 d_i が与えられます。入力値最終行の末尾に改行が1つ入ります。

期待する出力
N 行での出力

・ 各移動後の x , y 座標を出力してください。

x_1 y_1
...
x_N y_N
条件
すべてのテストケースにおいて、以下の条件をみたします。
・ -100 ≦ X, Y ≦ 100
・ 1 ≦ N ≦ 100
・ d は、L, R のいずれかでそれぞれ 左・右 に 1 マス進むことを表す。
入力例1

3 5 1
L
出力例1

2 5
入力例2

-18 45 6
L
L
R
R
L
R
出力例2

-19 45
-19 46
-20 46
-20 45
-21 45
-21 44

解答と解説

x, y, n = gets.split.map(&:to_i)

#初期位置が北なので以下のように配置[北, 東, 南, 西]
operate = [[-1, 0], [0, 1], [1, 0], [0, -1]]

#LとRで移動したときの状態を表す
current = 0
n.times do
  d = gets.chomp

#LかRかで状態を変える
  d == 'L' ? current -= 1 : current += 1

#状態によって移動を変更する
  y += operate[current % 4][0]
  x += operate[current % 4][1]
  puts x.to_s + ' ' + y.to_s
end

※ポイント

初期状態が北向きなのでRだったら東で[0, 1]に進み
Lだったら西なので[0, -1]に進む
-1 % 4 は 3
-3 % 4 は 1
1 % 4 は 1
3 % 4 は 3
となるので
d == ‘L’ ? now -= 1 : now += 1
でnowの値を変えるだけで移動操作が表現できる。

その6に続きます

PaizaAランクLvアップ問題と解説(Ruby)その4

その3から続いてます

※スキルチェック問題ではありません。

規約により公式の解答コードそのままはよろしくないので、
オリジナルのコードにしています。

詳しくはコチラ

なるべくわかりやすい解説を付けました。

3:座標系での移動・向き (paizaランク B 相当)

開始時点の y , x 座標 と向いている方角 D が与えられます。
続く 1 行で移動の向き d が与えられるので、その向きに移動した後の y , x 座標 を答えてください。
移動前に向いている方角によって同じ移動の向きでも座標の変化が違うことに気をつけてください。

なお、マスの座標系は左上端のマスの座標を ( y , x ) = ( 0 , 0 ) とし、
下方向が y 座標の正の向き、右方向が x 座標の正の向きとします。
以下の図を参考にしてみてください。




入力される値
Y X D       
d

・ 1 行目には、開始時点の y , x 座標を表す Y , X, 現在の向いている方角を表す文字 D が与えられます。
・ 2 行目には、移動の向きを表す文字 d が与えられます。

期待する出力
1 行での出力

・ 移動した後の y , x 座標を出力してください。

y x

 条件

すべてのテストケースにおいて、以下の条件をみたします。
・ -100 ≦ X, Y ≦ 100
・ D は、N, S, E, W のいずれかでそれぞれ 北・南・東・西 を意味する。
・ d は、L, R のいずれかでそれぞれ 左・右 に 1 マス進むことを表す。

入力例1

4 2 N
R

出力例1

4 3

入力例2

6 9 E
R

出力例2

7 9

解答と解説

#1行目の入力値を各変数に代入yとxは数値に変換してDは文字列として代入
y, x, D = gets.split.map.with_index { |val, i| i != 2 ? val.to_i : val }

#移動の向きを表す文字を代入
d = gets.chomp

#Rが来たときの移動パターンを設定する
operateR = { N: [0, 1], S: [0, -1], E: [1, 0], W: [-1, 0] } 
#Lが来たときの移動パターンを設定する
operateL ={ N: [0, -1], S: [0, 1], E: [-1, 0], W: [1, 0] } 
  if d == "R"
    y += operateR[D.to_sym][0] 
    x += operateR[D.to_sym][1] 
  elsif d == "L"
    y += operateL[D.to_sym][0] 
    x += operateL[D.to_sym][1] 
  end
#移動後の座標を出力する
puts y.to_s + ' ' + x.to_s

別解 3項演算子でLとRのときに値を変数に代入するだけ

y, x, now = gets.split.map.with_index { |val, i| i != 2 ? val.to_i : val }
d = gets.chomp
d == 'L' ? direction = -1 : direction = 1
if now == 'N'
  x += direction
elsif now == 'S'
  x -= direction
elsif now == 'E'
  y += direction
elsif now == 'W'
  y -= direction
end
puts y.to_s + ' ' + x.to_s

4:座標系での規則的な移動 (paizaランク B 相当)

開始時点の x , y 座標と移動の歩数 N が与えられます。
以下の図のように時計回りに渦を巻くように移動を N 歩行った後の x , y 座標 を答えてください。

なお、マスの座標系は下方向が y 座標の正の向き、右方向が x 座標の正の向きとします。

入力される値
X Y N

・ 1 行で、開始時点の x , y 座標を表す X , Y, 移動の歩数 N が与えられます。
入力値最終行の末尾に改行が1つ入ります。

期待する出力
1行での出力

・ 移動を N 歩行った後の x , y 座標を出力してください。

x y
条件
すべてのテストケースにおいて、以下の条件を満たします。
・ -100 ≦ X, Y ≦ 100
・ 0 ≦ N ≦ 100
入力例1

0 0 3
出力例1

0 1
入力例2

38 47 27
出力例2

41 47

解答

 

 

 

その5に続きます

PaizaAランクLvアップ問題と解説(Ruby)その3

その2から続いています

※スキルチェック問題ではありません。

規約により公式の解答コードそのままはよろしくないので、
オリジナルのコードにしています。

詳しくはコチラ

なるべくわかりやすい解説を付けました。

1:マップからの座標取得 (paizaランク C 相当)

マップの行数 H と列数 W とマップを表す H 行 W 列の文字列 S_1 …S_H が与えられます。
要素が ‘#’ になっているマスが 1 つあるので、その y , x 座標 を答えてください。

なお、マスの座標系は左上端のマスの座標を ( y , x ) = ( 0 , 0 ) とし、
下方向が y 座標の正の向き、右方向が x 座標の正の向きとします。

入力される値

H W     
S_0     
...     
S_(H-1)

・ 1 行目には盤面の行数を表す整数 H , 盤面の列数を表す整数 W が与えられます。
・ 続く H 行のうち i 行目 (0 ≦ i < H) には、盤面の i 行目の文字をまとめた文字列 S_i が与えられ、 S_i の j 文字目は、盤面の i 行目の j 列目に書かれている文字を表します。(0 ≦ j < W)
入力値最終行の末尾に改行が1つ入ります。

期待する出力
1行の出力

・ 要素が ‘#’ になっているマスの y , x 座標を 1 行で出力してください。

y x
条件
すべてのテストケースにおいて、以下の条件をみたします。

・ 1 ≦ H, W ≦ 20
・ S は W 文字の文字列
・ S の各文字は '.' または '#'
・ '#' のマスは必ず1つ
入力例1

1 1
#
出力例1

0 0
入力例2

3 3
.#.
...
...
出力例2

0 1

解答と解説(入力例2の場合)

#1行目の入力値を各変数に代入
h,w = gets.split.map(&:to_i)

#縦(盤面の行数)の回数分繰り返す
h.times do |i|

#入力値を受け取り改行を打ち消しバラバラにする(例;[".", "#", "."])
  line = gets.chomp.chars
 
  #横(盤面の列数)の回数分繰り返す
  w.times do |j|
  #"#"のときに現在地を出力する(後置if)
    puts i.to_s + " " + j.to_s if line[j] == "#"
  end
end

2:座標系での移動・方角 (paizaランク C 相当)

開始時点の y , x 座標 と移動の回数 N が与えられます。
続く N 行で移動の方角 d_1 … d_N が与えられるので、与えられた順に移動をしたときの各移動後の y , x 座標 を答えてください。

ただし、図の通り、上側( y 軸の負の向き)を北とします。

なお、マスの座標系は左上端のマスの座標を ( y , x ) = ( 0 , 0 ) とし、
下方向が y 座標の正の向き、右方向が x 座標の正の向きとします。

入力される値
Y X N       
d_1     
...     
d_N

・ 1 行目には、開始時点の y , x 座標を表す Y , X, 移動の回数 N が与えられます。
・ 続く N 行 (1 ≦ i ≦ N) には、盤面の i 回目の移動の方角を表す文字 d_i が与えられます。入力値最終行の末尾に改行が1つ入ります。

期待する出力
N 行での出力

・ 各移動後の y , x 座標を出力してください。

y_1 x_1
...
y_N x_N
条件
すべてのテストケースにおいて、以下の条件をみたします。

・ 0 ≦ Y, X, N ≦100
・ d_i は、N, S, E, W のいずれかでそれぞれ 北・南・東・西 を意味する。
入力例1

0 0 1
N
出力例1

-1 0
入力例2

5 10 4
N
W
E
S
出力例2

4 10
4 9
4 10
5 10

解答 普通にやる場合

y,x,n = gets.split.map(&:to_i)
command = readlines.map(&:chomp)
command.size.times do |i|
  if command[i] == "N"
     puts (y-1).to_s + " " + x.to_s 
      y += - 1
  elsif command[i] == "W"
     puts y.to_s + " " + (x-1).to_s
     x += - 1
  elsif command[i] == "E"
     puts y.to_s + " " + (x+1).to_s 
     x += + 1
  elsif command[i] == "S"
     puts (y + 1).to_s + " " + x.to_s 
     y += + 1
  end
end

別解 ハッシュを使う場合(こっちのほうがいいかも)

y, x, n = gets.split.map(&:to_i)
direction = { N: [-1, 0], S: [1, 0], E: [0, 1], W: [0, -1] }
n.times do
  command = gets.chomp
  y += direction[command.to_sym][0]
  x += direction[command.to_sym][1]
  puts y.to_s + ' ' + x.to_s
end

その4に続きます

VueとRailsで作成したアプリで得た知見と備忘録5(Firebase)

目標:すぐに思い出せるようにざっくり解説する。

Firebaseのプロジェクト作成

0:Firebaseとは?

iOS/AndroidアプリWebアプリケーションの開発に活用できるプラットフォームです。
利用できるサービスはデータベース、分析、認証、メッセージ送信機能など、様々です。主に認証機能を利用します。

1:プロジェクトの作成

https://firebase.google.com/?hl=ja

↑で公式サイトにアクセスします。

新規登録をして、使ってみるをクリックします。

プロジェクトを新規作成します。

2:NuxtとFirebaseのプロジェクトを関連づけ
</>

↑のようなアイコンをクリックします。

アプリ名を入れ、
FirebaseのHostingにチェックを入れ、
アプリを登録をクリックします。

3:Firebase CLIのインストール

frontディレクトリで以下のコマンドをします。

npm install -g firebase-tools

4:Firebase SDKのインストール

npm install firebase

5:設定ファイルの作成

front/pluginsのディレクトリの中にfirebase.jsという新規ファイルを作成し、
以下のように記述します。

front/plugins/firebase.js

import firebase from "firebase/compat/app"
import "firebase/compat/auth"

const fbConfig = {
    apiKey: process.env.API_KEY,
    authDomain: process.env.AUTH_DOMAIN,
    projectId: process.env.PROJECT_ID,
};
firebase.initializeApp(fbConfig)

export default firebase

firebase.initializeApp(fbConfig)で各種設定を引数にしてFirebase アプリオブジェクト作成しています。

それをexportすることによって、
このnuxtのプロジェクト内からFirebaseへアクセスできるようになります。
それぞれの設定の情報は外部から見えないよう.envファイルに定義します。

FireBaseの歯車のアイコンからプロジェクトの設定

下にスクロールすることで設定情報がわかるので

.envに記述します””の間にコピペする。

API_ENDPOINT="http://localhost:3000"

API_KEY="*************************"
AUTH_DOMAIN="*********************"
PROJECT_ID="**********************"

6:設定が反映できているかを確認

front/pages/index.vueに以下の記述をします。

  export default {
    components: {
      AddTodo,
      TodoList,
    },
    data() {
      return {
        todos: [],
      };
    },
    // 追加
    created() {
      console.log("API_KEY:", process.env.API_KEY);
    },
    methods: {
      async addTodo(title) {
        await axios.post("/v1/todos", { title })
        this.todos.push({
          title
        });
      },
    },
  };

サーバーを立ち上げて確認します。

npm run dev

http://localhost:8080/

↑にアクセスして検証ツールでapikeyが出力されていればOKです!

7:Firebaseの機能を用いた新規登録機能の実装

Firebase Authenticationとは

Firebase Authenticationとはユーザー認証の機能を提供してくれて、
ユーザの情報をFirebase上に保存してくれるサービスです。
メールアドレスパスワードを使った認証方法を使用していきます。

8:Firebase Authenticationのメールアドレス認証方式をオンにする

Firebase→作成したプロジェクトを選択

左側のナビゲーションペインからAuthenticationを選択し、始めるをクリックします。

Sign-in methodをクリックして、次にメール/パス…となっている部分をクリックします。

有効にするチェックをONにした後で保存を押します。

9:新規登録した際にFirebaseの認証を行い、RailsAPIのDBにもユーザーのレコードが保存されるようにする。

新規登録画面の作成

新規登録の画面を作成します。
front/pagesの配下に、signup.vue作成して、フロント部分を記述します

front/pages/signup.vue

  <v-btn class="mr-4" @click="signup">submit</v-btn>
  const res = await firebase
    .auth()
    .createUserWithEmailAndPassword(this.email, this.password)

↑新規登録用の情報を入力してsubmitボタンを押したとき、async signup()が発火します。これにより、メールアドレスとパスワードを使用して、Authenticationの機能を利用できます。

 .catch(error => {
    this.error = (code => {
      switch (code) {
        case "auth/email-already-in-use":
          return "既にそのメールアドレスは使われています";
        case "auth/wrong-password":
          return "※パスワードが正しくありません";
        case "auth/weak-password":
          return "※パスワードは最低6文字以上にしてください";
        default:
          return "※メールアドレスとパスワードをご確認ください";
      }
    })(error.code);
  });

.catchは、エラーがあった際の実装です。各種エラーに対してメッセージを設定し、
<p v-if="error" class="errors">{{ error }}</p>で表示をしています。

v-ifに関しては、指定している値が存在しているときにのみ、
タグで囲んでいる部分が描画されます。
エラーが発生した場合、data()で定義されているerrorプロパティの値が入るので、
この部分が描画されるという訳です。
エラーがない場合はもちろん、表示はされません。

  const user = {
    email: res.user.email,
    name: this.name,
    uid: res.user.uid
  };

  await axios
    .post("/v1/users", {
      user
    })
    .catch(err => {
      console.log({
        err
      });
    });

Firebase側の処理が終わったら、const user =の部分でFirebaseから帰ってきた値を元に、userのオブジェクト情報を作成しています。

その後、await axiosを使用して、作成したユーザー情報をもとに、
APIへpostのリクエストを行なっています。

/v1/usersへのPOSTリクエストはどのアクションに対応しているか調べてみると、UsersControllerのcreateアクションに対応していることがわかります。

api/app/controllers/v1/users_controller.rb
  def create
    user = User.new(user_params)
    if user.save
      render json: user
    else
      render json: user.errors
    end
  end

  # 中略
  private

  def user_params
    params.require(:user).permit(:name, :email, :uid)
  end

Userクラスのインスタンスを、ストロングパラメータで受け取った引数を元に作成し、保存しています。
これでバックエンド側でもユーザーが作成され、DBに保存されます。

Submitをクリックしたら、ブラウザはTODO一覧の画面に移動できます。
こちらは、async signup()の処理の最後にあるthis.$router.push(“/”);の部分が実行されるからです。

VueとRailsで作成したアプリで得た知見と備忘録4(AxiosとCORS)

3から続いています

目標:すぐに思い出せるようにざっくり解説する。

流れ

  1. フロントエンドからサーバーサイドのリクエスト用にAxiosを設定します。
  2. サーバーサイドでリクエストを受け入れられる用にCORSの設定をします。
  3. TODOの登録のリクエストを送り、保存されるか確認します。

前提知識

Axiosとは?

フロントエンドからバックエンドにリクエストを送るときに使っていくライブラリで
ブラウザやNode.js上で動くPromiseベースのHTTPクライアントです。

Promiseとは

JavaScriptの非同期処理の完了もしくは失敗を表すオブジェクトで、
非同期処理が完了したときに結果を返してくれます。

HTTPクライアントとは

GET・POST・PATCHなどのHTTPリクエストを送信して、
そのレスポンスを受信できるものです。

Axiosを利用するJavaScript非同期処理を行う際にHTTPリクエスト送って、レスポンス返してもらい、何らかの処理行うことができるようになります。

4章:APIの連携をする

1:フロントからサーバーサイドのリクエスト用にaxiosを設定する

Axiosの設定:baseURLの設定

baseURLは環境変数を設定することで設定できます。
Nuxt.jsで環境変数を扱う際は@nuxtjs/dotenvというライブラリを入れます。

frontのディレクトリで行います。

npm install --save-dev @nuxtjs/dotenv

front/nuxt.config.jsにbuildModulesという部分があるので、そこに追記します。

'@nuxtjs/dotenv', // 追記

.envを以下のような階層構造で作成します。

nuxt-rails-todo
├── api
└── front
  └── .env

そして以下のように記述します。

API_ENDPOINT="http://localhost:3000"

Railsはサーバーを起動すると、デフォルトでポート3000が使用されるためです。

Nuxtでもポート3000を使っているため衝突してしまいます。

front/nuxt.config.jsを変更することでNuxt側を変更します。

front/nuxt.config.js

import colors from 'vuetify/es5/util/colors'

export default {
 server: {
  port: 8080 
},

これで、npm run devを実行すると、8080番ポートが使用され、
Railsと同じポートでぶつかることもなくなります。

npm run devを実行して、http://localhost:8080/にアクセスして、
ToDoの一覧画面が出ればOKです。

2:サーバーサイドでリクエストを受け入れられるようにCORSの設定をする

CORSとは?

Cross-origin Resource Sharingの略称(オリジン間のリソース共有)

役割は同一オリジンポリシー(Same-Origin Policy)というルールによって
設けられた制限を緩めるものです。

同一制限ポリシーとは?

同一オリジンポリシーとは、ウェブブラウザに設けられた、
セキュリティ攻撃などの防止のための仕組みです。
異なるオリジン間のリソースへのアクセスに制約をかけるものです。

オリジンとは、URIのスキーム、ホスト、ポート番号の組み合わせのことです。

例えば、http://localhost:3000というURIがあったときに、オリジンは以下のように分解できます。

http: スキーム
localhost: ホスト
3000: ポート番号

異なるオリジン間でリソースにアクセスしようとした時に、ウェブブラウザが制限をかけてくれる仕組みのことです。

CORSは何のために設定する?

同一オリジンポリシーというルールによって設けられた制限を緩めるためで
作成しているアプリは、フロントエンドバックエンドが分かれています。
フロントエンドからバックエンドへ何かリクエストを行うと、この同一制限ポリシーに引っかかってしまい、アプリが意図したように動作しなくなってしまいます。
そのため、CORSという部分的に同一オリジンポリシーを解除する設定をしてあげる必要があります。

3:RailsAPIでのCORS設定方法

Gemのインストール

RailsでCORSを許可するために必要なGemを入れていきます。

デフォルトでコメントアウトされているので、それを外します。

api/Gemfile

# Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible
gem 'rack-cors'

以下のコマンドでインストールします

cd ../api
bundle install

4:CORSの設定ファイルの編集

api/config/initializers/cors.rbで以下のように記述します。

#コメントアウトを外して8080にする
Rails.application.config.middleware.insert_before 0, Rack::Cors do
 allow do
  origins 'localhost:8080' 

  resource '*',
 headers: :any,
 methods: [:get, :post, :put, :patch, :delete, :options, :head]
end
end

5:Nuxt側のリクエスト部分を修正する

front/pages/index.vueを開いて、<script>の中に以下のコードを追加します。

front/pages/index.vueのスクリプト部分から追記
<script>
  import AddTodo from "@/components/AddTodo";
  import TodoList from "@/components/TodoList";
  import axios from "@/plugins/axios";  // サーバーサイドにHTTPリクエストをするために、axiosを追加

  export default {
    components: {
      AddTodo,
      TodoList,
    },
    data() {
      return {
        todos: [],
      };
    },
    methods: {
      async addTodo(title) { // フロントエンドの処理を動かしつつ、サーバーサイドでもToDoの登録を行いたいため、非同期でサーバーサイドにリクエストを送る
        await axios.post("/v1/todos", { title }); // awaitで下の処理が先に実行されるのを防ぐ。
        this.todos.push({
          title
        });
      },
    },
  };
</script>

↑のasyncawaitは作成ボタンを押したら、先にサーバーサイド側でToDoを登録する処理を確実に行いたいためです。

axios.post("/v1/todos", { title });

↑ではTODOのタイトルを値として、サーバーサイド(API)のv1/todosというURLにPOSTリクエストが送信できるようにしています。

その5に続く

VueとRailsで作成したアプリで得た知見と備忘録3(ToDoのフロント作成)

2から続いています

目標:処理の流れを忘れた時にすぐに思い出せるようにざっくり解説する。

この章で目指すこと

・AddTodo.vue作成ボタンを押し、$emitを使ってイベント名と共に値を親コンポーネントへ渡す
・親コンポーネントのindex.vue$emitを使って渡ってきた、イベントをトリガーに、methodsと紐づけて、値を子コンポーネントに渡す

・子コンポーネントTodoList.vueで、渡された値をprops登録しておく事で動的な値として扱えるようにする

3章:ToDo機能のページ作成

0:Nuxtの基礎知識(コンポーネント)

Nuxt(Vue.js)のコンポーネントとは?

コンポーネントとは、再利用可能な部品群を指すことが多いです。

Nuxt(Vue.js)におけるコンポーネントは、
再利用可能なVueのインスタンスで、一度作成すれば他の場所で自由に呼び出すことができます。

例えば「ボタンのコンポーネント」を一度作成すれば、
どこのページでも呼び出すことができるようになり
それぞれのページで、毎回ボタンのコードを書く必要がなくなります。
Railsにある部分テンプレートという機能と非常に似ているのがこのコンポーネントです。

0.5:v-タグとVuetifyについて

.vueファイルのコンポーネントでは、<template>というタグの中へ、
コンポーネントとして表示したい内容を書きます。
<v-form>というタグで囲まれたToDo追加のフォームが、
コンポーネントとして表示されます。

例↓

<v-text-field v-model="title" :counter="10" label="todo" required></v-text-field>

v-model="title"とすることで、<script>data()の中で宣言されているtitleとデータを紐づけることができます。

1:AddTodoコンポーネントの作成

frontディレクトリの中に、componentsというディレクトリがあります。
Nuxtのコンポーネントはこのディレクトリの中に作成します。
componentsの中に、AddTodo.vueというファイルを作成し表示したい部分を記述します。

front/components/AddTodo.vue

<template>
 <v-form>
  <v-container>
   <v-row>
    <v-col cols="12" md="4">
     <v-text-field v-model="title" counter="10" label="todo" required></v-text-field>
   </v-col>
    <v-col cols="12" md="4">
      <v-btn @click="handleSubmit">作成</v-btn>
    </v-col>
   </v-row>
  </v-container>
 </v-form>
</template>

<script>
export default {
 data() {
  return {
   title: ""
  };
},
methods: {
  handleSubmit() {
   this.title = "";
  }
 }
};
</script>
<style>
</style>

2:ToDo登録の流れ(↑ので説明)

1:v-model="title"<script>data()の中で宣言されているtitleの値は空なので、
このフォームには初期値として何も入りません。
2:<v-btn @click="handleSubmit">作成</v-btn>
@clickという記法を使って、ボタンをクリックしたとき、
methodsの中で定義した関数を呼び出すことができます。
具体的には、以下の部分が呼び出されます。

methods: {
  handleSubmit() {
  this.title = "";
 },
},

3:ToDoリストコンポーネントの作成

TodoList.vueというファイルを作成します。TodoList.vueに以下の記述を追加します。

<template>
  <v-card>
    <v-card-title>
      Todoリスト
    <v-spacer></v-spacer>
     <v-text-field v-model="search" label="Search" single-line hide-details></v-text-field>
    </v-card-title>
     <v-data-table :headers="headers" :items="todos" :search="search"></v-data-table>
 </v-card>
</template>

<script>
export default {
 data() {
  return {
  todos: [
{
title: "test",
username: "suzutuki"
}
],
search: "",
headers: [
{
  text: "タイトル",
  align: "left",
  sortable: false,
  value: "title"
},
 { text: "ユーザー名", 
 value: "username"
}
]
};
}
};
</script>

<style>
</style>

<v-card>の中にToDoリストのタイトルテーブルを表示しています。

<v-text-field v-model="search" label="Search" single-line hide-details></v-text-field>

v-modelで渡している値は検索バーに入っている初期値です。

a:コロン記法(v-bindの省略)

各プロパティに:(コロン)がたくさんついています。
Vueのv-bindの省略記法で、Vueオブジェクトの変数の値とHTMLを結びつけることができます。
:headersというプロパティにはvueのheadersという変数の値が当てられていますが、
このコロンを消してしまうとエラーが起こります。
vueのオブジェクトが渡されるはずの場所に、”headers”という文字列が渡されてしまうためです。データを渡したいのに、”headers”という文字列が渡されてしまいます。

front/components/TodoList.vue

headers: [
{
 text: "タイトル",
 align: "left",
 value: "title"
},
{ 
 text: "ユーザー名", 
 value: "username" 
}
]

4-1:各プロパティについて(headers)

テーブルのヘッダーとなるプロパティを指定できます。

front/components/TodoList.vue

headers: [
{
text: "タイトル",  // 先頭のヘッダーの内容
align: "left",   // テーブルに対して文字を左寄せ
value: "title"   // どの値をその列に表示させるか
},
{ text: "ユーザー名",
value: "username"
}
]

textには実際にヘッダーの部分に出力させる文字が入りalignは文字の詰め方を指定、
valueは、itemsのプロパティに渡される名前と同様の値が入ります。

※ここを間違えると、値が表示されません(エラーが起こっているようには見えないので注意)。

itemsとして以下のオブジェクトが渡されてきます。

front/components/TodoList.vue

todos: [
{
  title: "test",
  username: "suzutuki"
}
],

titleusernameというプロパティがあります。

titleheadersvalueがtitleの列に、
usernameheadersのvalueがusernameの列へ表示されます。

4-2:各プロパティについて(items)

実際にテーブルへ表示させる値を指定します。
この時、表示させたいプロパティ名を、headersvalueと揃えないとエラーになります!

4-3:各プロパティについて(search)

表示されているToDo(items)をフィルターするときの文字列を指定しています。
v-bindされてsearchという値が指定されています。
これは<v-text-field>で指定されている値です。

front/components/TodoList.vue

<v-text-field v-model="search" label="Search" single-line hide-details></v-text-field>

v-modelsearchという値を指定しています。
このテキストフィールドで入力した値が、searchという値に入り
それがそのまま<v-text-table>searchプロパティの値として利用される訳です。

<v-text-field>に入れた値で、<v-text-table>のToDoをフィルタリングできるという訳です。

5:コンポーネントを表示させるページの作成

index.vueの作成

ページのファイルを作成します。

front/pagesというディレクトリが見つかります。
このディレクトリは、アプリケーションのビューおよび、ルートが格納される場所です。
Nuxtはこのディレクトリ内のすべての .vueファイルを読み込んで、
ルーターの設定を自動的に作成してくれます。

サーバーを立てている状態でhttp://localhost:3000/にアクセスするとindex.vueが表示されます。
これはNuxtで元々の設定で、ルートページに当たるファイルです。

front/pages/index.vueに以下の記述を追加します。

<template>
 <div>
  <AddTodo @submit="addTodo" />
  <TodoList :todos="todos" />
 </div>
</template>

<script>
import AddTodo from "@/components/AddTodo";
import TodoList from "@/components/TodoList";

export default {
 components: {
 AddTodo,
 TodoList,
},
 data() {
  return {
  todos: [],
};
},
methods: {
  addTodo(title) {
   this.todos.push({
    title
  });
 },
},
};
</script>
<style>
</style>

コンポーネントの登録

<script>の中でimportし、importしたコンポーネント
exportの中でcomponentsに登録します。
このように記述することで、<template>の中で作成したコンポーネント
HTMLタグのように使用できます。

@エイリアスと言って、プロジェクトのトップディレクトリを指せる、Nuxtで使える便利な記号
componentsディレクトリの中で作成したAddTodo.vueとTodoList.vueをimportしています。

コンポーネントは、以下のようにタグとして使用しています

front/pages/index.vue

<template>
 <div>
  <AddTodo @submit="addTodo" />
  <TodoList :todos="todos" />
 </div>
</template>

サーバーを立ち上げて表示がおかしくないか確認します。

cd front
npm run dev

http://localhost:3000/

その4に続きます。

6:ToDo登録から表示までの実装

ToDoの追加コンポーネントで作成したToDoを親コンポーネントに伝達してToDoリストコンポーネントに表示

index.vueを親コンポーネントと呼びます。
<AddTodo>コンポーネントと、<TodoList>コンポーネントが、
index.vueに表示されている状態です。
ページの中で、さらにコンポーネントを呼び出しているので、親に当たるのです。

AddTodo.vueから親コンポーネントに登録したいデータを渡す

AddTodo.vue

this.$emit("submit", this.title); // この行を追加します。

↑イベントをトリガーしたいときに使用します。

引数を2つとっており、第一引数のsubmitイベント名、第二引数のthis.titleがイベントとして渡される値になります。
submitというイベント名が親コンポーネントであるindex.vueに渡されます。
親コンポーネント側で「submitと言うイベントが起こったらこの処理を実行する」と言う処理を書いておけば、トリガーになってくれると言う訳です。

7:ToDoの情報を親コンポーネントで受け取って、TodoList.vueに渡す

index.vue

<template>
 <div>
  <!-- divの部分 -->
  <AddTodo @submit="addTodo" /> 
  <TodoList :todos="todos" /> 
 </div>
</template>

@submit=”addTodo”について

v-on:@submit=”addTodo”の省略形です。
AddTodo.vueからsubmitというイベントが来たら、methodsに登録したaddTodo(title)を呼び出します。

リスナーと呼ばれたりします。

このリスナーは、AddTodo.vueを表示するためのコンポーネントに書いてあるので、
AddTodo.vueのイベントを拾ってくれます。

addTodo(title)の引数には自動的にAddTodo.vueで渡したthis.titleが引数として渡されます。

リスナーのコールバック関数に渡されるというのはこれら一連の流れのことです。
$emitを使うだけで、親コンポーネントへ、イベントとコールバックで呼ばれる
メソッドに引数を渡すことができます。

これら一連の動作により、↓の処理が実行されます。

front/pages/index.vue

this.todos.push({
  title
});

data()で宣言されているtodosという配列の中に、AddTodo.vue登録しようとしたToDoの
タイトルが入ります。

最後にそのtodosという値を:todos=”todos”の部分で、TodoList.vue渡しています
=の右側のtodosが、今回渡している値で、左側の:todosは、TodoList.vueで使っている値になります。

8:TodoList.vue内で親コンポーネントから受け取ったTODOを表示させる

front/components/TodoList.vueのscriptの部分を変更します。

<script>
export default {
 props: ["todos"],
 data() {
  return {
  search: "",
// 中略

props: [“todos”]というコードを追加して、todosというプロパティを削除しました。
propsと言うのは、親コンポーネントから子コンポーネントに値を渡す時に使います。

親コンポーネントindex.vueでは:todos=”todos”の部分で、
todosというデータをTodoList.vueに渡していました。
この左側の:todosに当たる部分が、propsに定義しているコードになります。

親から渡してきた値を紐づけることで動的にpropsに登録した値を書き換えています。

親がtodosを更新すると、:todosとpropsにtodosが登録してあることによって、
<v-data-table>の:items=”todos”の部分にて、テーブルのToDoが動的に追加されます。

サーバーを起動して、トップ画面でToDoの値を入れ、送信ボタンを押して
登録できていたらOKです。

その4に続きます

VueとRailsで作成したアプリで得た知見と備忘録2(Nuxtプロジェクトの作成)

1から続いています

目標:忘れた時にすぐに思い出せるようにざっくり解説する。

2章:Nuxtプロジェクトの作成 (Front)

ホームディレクトリに移動し、pwdで場所を確認します。

pwd
/Users/<ユーザー名>/Desktop/アプリ名

1:frontという名前でアプリを作成する(名前はわかりやすければOKです。)

npx create-nuxt-app front

様々な質問に答えていきます。

Need to install the following packages:
create-nuxt-app
Ok to proceed? (y)

yを入力しreturnを入力してください。
Nuxtのプロジェクト作成に必要なパッケージが入っていない場合に出ます。

create-nuxt-app v3.6.0
✨ Generating Nuxt.js project in front
? Project name: (front)

プロジェクト名の決定についてです。returnを入力します。

? Programming language: (Use arrow keys)
❯ JavaScript 
TypeScript

JavaScriptを使用するので、そのままreturnを入力します。

? Package manager: (Use arrow keys)
❯ Yarn 
Npm

Npmを使用したいので、Npmを選択します。

? UI framework: (Use arrow keys)
None 
Ant Design Vue 
BalmUI 
Bootstrap Vue 
Buefy 
Chakra UI 
Element 
Framevuerk 
Oruga 
Tachyons 
Tailwind CSS 
Windi CSS 
Vant 
View UI 
Vuesax 
consoleWindi CSS 
Vant 
View UI 
Vuesax 
❯ Vuetify.js 
None 
Ant Design Vue 
BalmUI 
Bootstrap Vue

Vuetify.jsを選択します。

? Nuxt.js modules: (Press <space> to select, <a> to toggle all, <i> to invert selection)
❯◯ Axios - Promise based HTTP client
◯ Progressive Web App (PWA)
◯ Content - Git-based headless CMS

何のモジュールを使うかに関してです。
Axiosを使用してapi側と通信をするので、Axiosを選択します。

? Linting tools: (Press <space> to select, <a> to toggle all, <i> to invert selection)
❯◯ ESLint
◯ Prettier
◯ Lint staged files
◯ StyleLint
◯ Commitlint

潜在的なエラーについてコードを分析するリンティングツールの選択です。
使用しないため、スペースキーを押さずにreturnを入力します。

? Testing framework: (Use arrow keys)
❯ None 
Jest 
AVA 
WebdriverIO 
Nightwatch

テストのフレームワークの選択です。
使用しないため、Noneを選択します。

? Rendering mode: (Use arrow keys)
❯ Universal (SSR / SSG) 
Single Page App

レンダリングの設定です。
SPAを作成するので、Single Page Appを選択します。

? Deployment target: (Use arrow keys)
❯ Server (Node.js hosting) 
Static (Static/Jamstack hosting)

デプロイホスティングという、デプロイ後のファイルの公開に関する方法の設定です。
Serverを選択します。

? Development tools: (Press <space> to select, <a> to toggle all, <i> to invert selection)
❯◯ jsconfig.json (Recommended for VS Code if you're not using typescript)
◯ Semantic Pull Requests
◯ Dependabot (For auto-updating dependencies, GitHub only)

開発者ツールです。特に使用しないので、retuenを入力します。

? What is your GitHub username? (登録されている場合はあなたのユーザー名が表示されます)

Githubとの連携設定です。名前が表示されている場合はそのままreturnを入力、
ない場合はidを入力してreturnを入力します。

? Version control system: (Use arrow keys)
❯ Git 
None

いよいよ最後です。長かったですね!

バージョン管理システムの確認です。apiとfrontを同時にGitで管理するため、
このプロジェクト内にはGitのリポジトリは作りません。Noneにカーソルを合わせてreturnを入力します。

続きを読む

VueとRailsで作成したアプリで得た知見と備忘録1(環境構築+RailsAPI実装方法)

目標:忘れた時にすぐに思い出せるようにざっくり解説する。

0章:Rails環境構築

0:Homebrewをインストール

Homebrewからインストールする

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
brew -v

1:rbenvをインストール

brew install rbenv
rbenv --version

2:rbenvにPATHを通す

echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
echo 'if which rbenv > /dev/null; then eval "$(rbenv init -)"; fi' >> ~/.bash_profile
source ~/.bash_profile

3:Rubyをインストール

rbenv install 3.0.0

4:ローカルで使うRubyのバージョンを指定

rbenv local 3.0.0
ruby -v

5:Bundlerをインストール

gem install bundler
bundler -v

6:yarnをインストール(yarnはJavaScriptのライブラリの利用に必要なパッケージマネージャ)

brew install yarn
yarn -v

7:Railsをインストール

gem install rails -v 6.1.3.1
rails -v

1:NuxtJSの環境構築(macOS)

パッケージ管理ツールのインストール

Nuxtというフレームワークを使用するためには、
様々なプログラムをインストールする必要があります。

他のフレームワーク(Railsとか)と同じように、パッケージ管理ツールをインストールしていきます。

Railsにはパッケージ管理ツールにGem、
さらにそのGemを管理するBundlerなどが存在しています。
Nuxtの環境構築としてはNpmを利用します。

Npmの説明

Npmの正式名称はNode Package Managerです。

NpmはNode.jsというプログラムに組み込まれています。

Node.jsはJavaScriptを実行する環境です。

本来、JavaScriptはWebブラウザに組み込まれていて、
ブラウザ上でしか実行できませんでした。

しかし、Node.jsはその場でのスクリプトの実行を可能にしてくれます。
さまざまなライブラリが存在しているのですが、それらのインストールを行うツールとして、Node.jsに組み込まれているのが今回使用するNpmなのです。

他のJavaScriptライブラリNpmを使ってインストールができるようになってきています。
NuxtもNpmでインストールできるため、Node.jsをインストールするというわけです。

8:Node.jsとNpmのインストールと確認

brew install node
node -v
npm -v

以上で環境構築は完了です。

1章:作り方の概要(RailsAPI の実装)

1:Todoモデルとテーブルの作成

Todoモデルの作成

rails g model Todo title:string user_id:integer

マイグレートして反映します

rails db:migrate

2:Todoのコントローラーの作成
nuxt-rails-todo/apiで行う

rails g controller v1::todos

v1にした理由:バージョンを指定してできるようにした

3:コントローラーに基本的なCRUD機能を追加してjson形式に変換してユーザーに返すようにする

render json: todo

理由:JavaScriptで非同期通信を行ってSPAを作成する際に、フロントエンド側でJSONのデータを使用するからです。

4:Todoのルーティングを作成する

routes.rbで行う

Rails.application.routes.draw do
  namespace :v1 do
  resources :todos, only: [:index, :create, :destroy]
end
end

Railsのルーティングではnamespaceという機能を使うことができ、URLのHelperとPathに指定した文字がつきます。

http://localhost:3000/v1/todos

↑で[]の配列が表示されていればAPIとして機能していることが確認できます!

5:Userモデルとテーブルの作成

apiディレクトリで行う

モデルの作成とmigrate

rails g model User name:string email:string uid:string
rails db:migrate

6:Userのコントローラーの作成

rails g controller v1::users

indexとcreateの機能とストロングパラメーターを実装して必ず以下の記述を追加する(JSON形式で返す)

render json: user
# create失敗したときはエラーを返す
render json: user.errors

7:Userのルーティングを作成する

api/config/routes.rbで行う

Rails.application.routes.draw do

namespace :v1 do
  resources :todos, only: [:index, :create, :destroy]
  resources :users, only: [:index, :create]
end
end

http://localhost:3000/v1/users

↑にアクセスして空配列[]を確認できればOKです。

8:JSON形式のデータを整形する(active_model_serializer)

active_model_serializersの導入

Gemfileに以下の記述を追加します。

gem 'active_model_serializers'

Gemをインストールします。

bundle install

9:JSON形式のデータを整形する(apiディレクトリで行う)

rails g serializer user

api/app/serializers/user_serializer.rbで以下の記述を追加します。

class UserSerializer < ActiveModel::Serializer
  attributes :id, :name, :email
  has_many :todos 
end

todoのserializerも作成します。

rails g serializer todo

user.nameと毎回書かないようにusernameというメソッドを作り、名前の値をTodoと一緒に返せるように設定します。

api/app/serializers/todo_serializer.rb

class UserSerializer < ActiveModel::Serializer
  attributes :id, :title, :user_id, :username
  belongs_to :user

  def username
    object.user.name
  end
end

10:モデルのアソシエーションの実装

api/app/models/todo.rb

class Todo < ApplicationRecord
  belongs_to :user
end

users.rb

class User < ApplicationRecord
  has_many :todos
end

javascript

{ 
  "user" : {
  "user_id" : "ユーザーのID",
  "username": "ユーザーの名前",
  "todo" : 
  {
    "title" : "todoのタイトル"
  }
}

↑のような、綺麗なJSONを返すことができるようになりました。

http://localhost:3000/v1/users

↑にアクセスして[]という配列が帰っていれば正しい実装ができています。

2に続きます。

PaizaAランクLvアップ問題と解説その2(Ruby)マップの判定(横・縦)

1から続いています

※スキルチェック問題ではありません。

規約により公式の解答コードそのままはよろしくないので、
オリジナルのコードにしています。

詳しくはコチラ

なるべくわかりやすい解説を付けました。

問題3:マップの判定・横 (paizaランク C 相当)

行数 H , 列数 W の盤面があり、盤面の各マスには文字が 1 つだけ書かれています。
盤面が与えられるので、「左右のマスが “#” 」であるようなマスの座標を全て出力してください。

ただし、左端のマスの場合は「右のマスが “#” 」であれば、右端のマスの場合は「左のマスが “#” 」であれば条件を満たすものとします。

なお、マスの座標系は左上端のマスの座標を ( y , x ) = ( 0 , 0 ) とし、
下方向が y 座標の正の向き、右方向が x 座標の正の向きとします。

入力される値
H W     
S_0     
...     
S_(H-1)

・ 1 行目には盤面の行数を表す整数 H , 盤面の列数を表す整数 W が与えられます。
・ 続く H 行のうち i 行目 (0 ≦ i < H) には、盤面の i 行目の文字をまとめた文字列 S_i が与えられ、 S_i の j 文字目は、盤面の i 行目の j 列目に書かれている文字を表します。(0 ≦ j < W)

期待する出力
N (1 ≦ N ≦ H×W) 行の出力

・ 条件を満たすマスの y , x 座標を出力してください。
・ 左上 (y = 0, x = 0) のマスから順に、x 座標 , y 座標の順で増加するように出力してください。詳しくは入出力例を参考にしてください。

y_1 x_1
...
y_N x_N

 

条件
すべてのテストケースにおいて、以下の条件をみたします。

・ 1 ≦ H, W ≦ 20
・ S は W 文字の文字列
・ S の各文字は "." または "#"
・ 条件を満たすマスが少なくとも 1 つ以上存在します

 

入力例1
3 3
#.#
.#.
...
出力例1
0 1
1 0
1 2
入力例2
4 4
####
####
####
####
出力例2
0 0
0 1
0 2
0 3
1 0
1 1
1 2
1 3
2 0
2 1
2 2
2 3
3 0
3 1
3 2
3 3

何が問われているか?(問題の抽象化)

座標系問題のx軸の判定

解答 ※ifと||で3パターンに対応している

#1行目の半角スペース刻みの入力値を変数に多重代入する
h, w = gets.split.map(&:to_i)

#盤面の情報を配列として代入していく
chart = h.times.map { gets.chomp.chars }

chart.each.with_index do |yoko, line|
  yoko.each_index do |index|

# 左端にいるかどうか(x == 0)または左に#があるかどうか確認することで右端(yoko[x - 1])も対応している
  if index == 0 || yoko[index - 1] == '#'

# 右端(x == w - 1)にいるかどうかまたは右に#があるかどうか(yoko[x + 1] == '#')確認している
# 以上により左右に#があるかがわかる
  if index == w - 1 || yoko[index + 1] == '#'

#条件を満たしたy軸とx軸を半角スペース刻みで出力する
    puts line.to_s + ' ' + index.to_s 
  end
  end
  end
end

問題4: マップの判定・縦 (paizaランク C 相当)

マップの行数 H と列数 W とマップを表す H 行 W 列の文字列 S_1 … S_H が与えられるので、
上下のマスがどちらも ‘#’ であるようなマスの y , x 座標 を答えてください。

ただし、上端のマスの場合は「下のマスが ‘#’」であれば、下端のマスの場合は「上のマスが ‘#’」であれば条件を満たすものとします。

なお、マスの座標系は左上端のマスの座標を ( y , x ) = ( 0 , 0 ) とし、
下方向が y 座標の正の向き、右方向が x 座標の正の向きとします。

入力される値
H W     
S_0     
...     
S_(H-1)

・ 1 行目には盤面の行数を表す整数 H , 盤面の列数を表す整数 W が与えられます。
・ 続く H 行のうち i 行目 (0 ≦ i < H) には、盤面の i 行目の文字をまとめた文字列 S_i が与えられ、 S_i の j 文字目は、盤面の i 行目の j 列目に書かれている文字を表します。(0 ≦ j < W)

期待する出力
N (1 ≦ N ≦ H×W) 行の出力

・ 条件を満たすマスの y , x 座標を出力してください。
・ 左上 (y = 0, x = 0) のマスから順に、x 座標 , y 座標の順で増加するように出力してください。詳しくは入出力例を参考にしてください。

y_1 x_1
...
y_N x_N
条件
すべてのテストケースにおいて、以下の条件をみたします。

・ 1 ≦ H, W ≦ 20
・ S は W 文字の文字列
・ S の各文字は '.' または '#'
・ 条件を満たすマスが少なくとも 1 つ以上存在します
入力例1
3 3
###
...
###
出力例1
1 0
1 1
1 2
入力例2
4 4
#.#.
.#.#
.#.#
#.#.
出力例2
0 1
0 3
3 1
3 3

何が問われているか?(問題の抽象化)

座標系問題のy軸の判定

解答と解説

#1行目の半角スペース刻みの入力値を変数に多重代入する
h, w = gets.split.map(&:to_i)

#盤面の情報を配列として代入していく
chart = h.times.map { gets.chomp.chars }
chart.each.with_index do |yoko, line|
  yoko.each_index do |index|

# 上端(line == 0)にいるかまたは上(a[line - 1 ][index] == '#')に#があるかどうか確認することで下端も対応している
  if line == 0 || a[line - 1 ][index] == '#' 

# 下端(line == h - 1)にいるかまたは下(a[line + 1 ][index] == '#')に#があるかどうか確認している 
  if line == h - 1 || a[line + 1 ][index] == '#'

#条件を満たしたy軸とx軸を半角スペース刻みで出力する
     puts line.to_s + ' ' + index.to_s 
  end
  end
  end
end

※配列は0から始まるので下端が(line == h – 1)となっています・

問題5:マップの判定・縦横 (paizaランク B 相当)

マップの行数 H と列数 W とマップを表す H 行 W 列の文字列 S_1 … S_H が与えられるので、
隣接する上下左右のマスが全て ‘#’ であるマスの y , x 座標 を答えてください。

ただし、左端のマスの場合は「右のマスが ‘#’ 」であれば、右端のマスの場合は「左のマスが ‘#’ 」であれば隣接する左右のマスが全て ‘#’ であるものとします。
また、上端のマスの場合は「下のマスが ‘#’ 」であれば、下端のマスの場合は「上のマスが ‘#’ 」であれば隣接する上下のマスが全て “#” であるものとします。

なお、マスの座標系は左上端のマスの座標を ( y , x ) = ( 0 , 0 ) とし、
下方向が y 座標の正の向き、右方向が x 座標の正の向きとします。

入力される値
H W     
S_0     
...     
S_(H-1)

・ 1 行目には盤面の行数を表す整数 H , 盤面の列数を表す整数 W が与えられます。
・ 続く H 行のうち i 行目 (0 ≦ i < H) には、盤面の i 行目の文字をまとめた文字列 S_i が与えられ、 S_i の j 文字目は、盤面の i 行目の j 列目に書かれている文字を表します。(0 ≦ j < W)

期待する出力
N (1 ≦ N ≦ H×W) 行の出力

・ 条件を満たすマスの y , x 座標を出力してください。
・ 左上 (y = 0, x = 0) のマスから順に、x 座標 , y 座標の順で増加するように出力してください。詳しくは入出力例を参考にしてください。

y_1 x_1
...
y_N x_N
条件
すべてのテストケースにおいて、以下の条件をみたします。

・ 1 ≦ H, W ≦ 20
・ S は W 文字の文字列
・ S の各文字は '.' または '#'
・ 条件を満たすマスが少なくとも 1 つ以上存在します

入力例1

3 3
##.
###
...

出力例1

0 0
0 2

入力例2

10 10
##########
..........
##########
##########
..........
#.#.#.#.#.
.#.#.#.#.#
#.#.#.#.#.
.#.#.#.#.#
..........

出力例2

6 0
6 2
6 4
6 6
6 8
7 1
7 3
7 5
7 7
7 9

何が問われているか?(問題の抽象化)

座標系問題のx軸とy軸の判定

解答と解説

#1行目の半角スペース刻みの入力値を変数に多重代入する
h, w = gets.split.map(&:to_i)

#盤面の情報を配列として代入していく
chart = h.times.map { gets.chomp.chars }


chart.each.with_index do |yoko, line|
  yoko.each_index do |index|

# 左端にいるかどうかまたは左に#があるかどうか確認することで右端も対応している   
if index == 0 || yoko[index - 1] == '#' 

# 右端にいるかどうかまたは右に#があるかどうか確認することで左端も対応している  
  if index == w - 1 || yoko[index + 1] == '#'

# 上端にいるかまたは上に#があるかどうか確認することで下端も対応している
    if line == 0 || a[line - 1 ][index] == '#'

# 下端にいるかまたは下に#があるかどうか確認している
      if line == h - 1 || a[line + 1 ][index] == '#'
        puts line.to_s + ' ' + index.to_s
      end
    end
  end
end
end
end

その3に続きます