N * M 크기의 두 행렬 A와 B가 주어졌을 때, 두 행렬을 더하는 프로그램을 작성하시오.
입력
첫째 줄에 행렬의 크기 N과 M이 주어진다. 둘째 줄부터 N개의 줄에 행렬 A의 원소 M개가 차례대로 주어진다.
이어서 N개의 줄에 행렬 B의 원소 M개가 차례대로 주어진다. N과 M은 100보다 작거나 같고, 행렬의 원소는 절댓값이 100보다 작거나 같은 정수이다.
출력
첫째 줄부터 N개의 줄에 행렬 A와 B를 더한 행렬을 출력한다. 행렬의 각 원소는 공백으로 구분한다.
C++
#include<iostream>#include<vector>usingnamespace std;
intmain(){
int n, m;
cin >> n >> m;
vector<vector<int>> arr_2d(n, vector<int>(m));
for (int i = 0; i < n; i++){
for(int j = 0; j < m; j++){
cin >> arr_2d[i][j];
}
}
for (int i = 0; i < n; i++){
for(int j = 0; j < m; j++){
int val = 0;
cin >> val;
cout << arr_2d[i][j] + val << " ";
}
cout << "\n";
}
}
C#
using System;
using System.Collections;
using System.Collections.Generic;
classProgram{
staticvoidMain(string[] args){
string input = Console.ReadLine();
string[] input_arr = input.Split();
int n = int.Parse(input_arr[0]);
int m = int.Parse(input_arr[1]);
int[][] arr_2d = newint[n][];
for (int i = 0; i < n; i++){
arr_2d[i] = newint[m];
}
for (int i = 0; i < n; i++)
{
string[] arr_val = Console.ReadLine().Split();
for (int j = 0; j < m; j++)
{
arr_2d[i][j] = int.Parse(arr_val[j]);
}
}
for (int i = 0; i < n; i++)
{
string[] arr_val = Console.ReadLine().Split();
for (int j = 0; j < m; j++)
{
arr_2d[i][j] += int.Parse(arr_val[j]);
Console.Write(arr_2d[i][j] + " ");
}
Console.WriteLine();
}
}
}
Python
n, m = map(int, input().split())
arr_2d_1 = [list(map(int, input().split())) for _ inrange(n)]
arr_2d_2 = [list(map(int, input().split())) for _ inrange(n)]
result = [[arr_2d_1[i][j] + arr_2d_2[i][j] for j inrange(m)] for i inrange(n)]
for row in result:
print(' '.join(map(str, row)))
Node.js
const fs = require('fs');
const input = fs.readFileSync('/dev/stdin', 'utf8').trim().split('\n');
const [n, m] = input[0].split(' ').map(Number);
const arr1 = [];
const arr2 = [];
for (let i = 1; i <= n; i++){
arr1.push(input[i].split(' ').map(Number));
}
for (let i = n + 1; i <= 2 * n; i++){
arr2.push(input[i].split(' ').map(Number));
}
var result = [];
for (let i = 0; i < n; i++){
const row = [];
for (let j = 0; j < m; j++){
row.push(arr1[i][j] + arr2[i][j]);
}
result.push(row);
}
result.forEach(row =>console.log(row.join(' ')));
2번 최댓값
문제
<그림 1>과 같이 9×9 격자판에 쓰인 81개의 자연수 또는 0이 주어질 때, 이들 중 최댓값을 찾고 그 최댓값이 몇 행 몇 열에 위치한 수인지 구하는 프로그램을 작성하시오. 예를 들어, 다음과 같이 81개의 수가 주어지면
https://www.acmicpc.net/problem/2566
이들 중 최댓값은 90이고, 이 값은 5행 7열에 위치한다.
입력
첫째 줄부터 아홉 번째 줄까지 한 줄에 아홉 개씩 수가 주어진다. 주어지는 수는 100보다 작은 자연수 또는 0이다.
출력
첫째 줄에 최댓값을 출력하고, 둘째 줄에 최댓값이 위치한 행 번호와 열 번호를 빈칸을 사이에 두고 차례로 출력한다. 최댓값이 두 개 이상인 경우 그중 한 곳의 위치를 출력한다.
이차원 배열에 저장하고 순회하면서 가장 큰 숫자를 찾고 그 숫자와 인덱스를 출력하면 되는 문제다. 그런데 이차원 배열을 굳이 만들지 않아도 입력을 처리할 때 바로 큰 수와 인덱스를 찾을 수 있긴 하다. 처음 풀이는 의도에 맞게 풀고 그 이후는 간단한 방법을 사용해 본다.
C++
#include<iostream>#include<vector>usingnamespace std;
intmain(){
int n = 9;
vector<vector<int>> arr_2d(n, vector<int>(n, 0));
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
cin >> arr_2d[i][j];
}
}
int max_val = arr_2d[0][0];
int max_row = 0;
int max_col = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (arr_2d[i][j] > max_val) {
max_val = arr_2d[i][j];
max_row = i;
max_col = j;
}
}
}
cout << max_val << "\n";
cout << max_row + 1 << " " << max_col + 1;
return0;
}
C#
using System;
classProgram{
staticvoidMain(string[] args){
int n = 9;
int maxVal = 0;
int row = 0;
int col = 0;
for (int i = 0; i < n; i++){
string[] strArr = Console.ReadLine().Split();
for (int j = 0; j < n; j++){
int tmpVal = int.Parse(strArr[j]);
if (tmpVal > maxVal){
maxVal = tmpVal;
row = i;
col = j;
}
}
}
Console.WriteLine($"{maxVal}");
Console.WriteLine($"{row + 1}{col + 1}");
}
}
Python
max_val = 0;
col = 0;
row = 0;
for i inrange(9):
arr = list(map(int, input().split()))
for j inrange(9):
tmp_val = arr[j]
if max_val < tmp_val:
max_val = tmp_val
row = i;
col = j;
print(f"{max_val}\n{row + 1}{col + 1}")
Node.js
const fs = require('fs');
const input = fs.readFileSync('/dev/stdin', 'utf8').trim().split('\n');
let col = 0;
let row = 0;
let max_val = 0;
for (let i = 0; i < 9; i++){
const arr = input[i].split(' ').map(Number);
for (let j = 0; j < 9; j++){
if (arr[j] > max_val){
max_val = arr[j];
row = i;
col = j;
}
}
}
console.log(`${max_val}\n${row + 1}${col + 1}`);
3번 세로 읽기
문제
아직 글을 모르는 영석이가 벽에 걸린 칠판에 자석이 붙어있는 글자들을 붙이는 장난감을 가지고 놀고 있다. 이 장난감에 있는 글자들은 영어 대문자 ‘A’부터 ‘Z’, 영어 소문자 ‘a’부터 ‘z’, 숫자 ‘0’부터 ‘9’이다. 영석이는 칠판에 글자들을 수평으로 일렬로 붙여서 단어를 만든다. 다시 그 아래쪽에 글자들을 붙여서 또 다른 단어를 만든다. 이런 식으로 다섯 개의 단어를 만든다. 아래 그림 1은 영석이가 칠판에 붙여 만든 단어들의 예이다.
A A B C D D
a f z z
0 9 1 2 1
a 8 E W g 6
P 5 h 3 k x
<그림 1>
한 줄의 단어는 글자들을 빈칸 없이 연속으로 나열해서 최대 15개의 글자들로 이루어진다. 또한 만들어진 다섯 개의 단어들의 글자 개수는 서로 다를 수 있다. 심심해진 영석이는 칠판에 만들어진 다섯 개의 단어를 세로로 읽으려 한다. 세로로 읽을 때, 각 단어의 첫 번째 글자들을 위에서 아래로 세로로 읽는다. 다음에 두 번째 글자들을 세로로 읽는다. 이런 식으로 왼쪽에서 오른쪽으로 한 자리씩 이동하면서 동일한 자리의 글자들을 세로로 읽어 나간다. 위의 그림 1의 다섯 번째 자리를 보면 두 번째 줄의 다섯 번째 자리의 글자는 없다. 이런 경우처럼 세로로 읽을 때 해당 자리의 글자가 없으면, 읽지 않고 그다음 글자를 계속 읽는다. 그림 1의 다섯 번째 자리를 세로로 읽으면 D1gk로 읽는다. 그림 1에서 영석이가 세로로 읽은 순서대로 글자들을 공백 없이 출력하면 다음과 같다: Aa0aPAf985Bz1EhCz2W3D1gkD6x 칠판에 붙여진 단어들이 주어질 때, 영석이가 세로로 읽은 순서대로 글자들을 출력하는 프로그램을 작성하시오.
입력
총 다섯 줄의 입력이 주어진다. 각 줄에는 최소 1개, 최대 15개의 글자들이 빈칸 없이 연속으로 주어진다. 주어지는 글자는 영어 대문자 ‘A’부터 ‘Z’, 영어 소문자 ‘a’부터 ‘z’, 숫자 ‘0’부터 ‘9’ 중 하나이다. 각 줄의 시작과 마지막에 빈칸은 없다.
출력
영석이가 세로로 읽은 순서대로 글자들을 출력한다. 이때, 글자들을 공백 없이 연속해서 출력한다.
C++
#include<iostream>#include<sstream>#include<string>#include<vector>usingnamespace std;
intmain(){
int max_col = 15;
string line;
vector<vector<char>> matrix;
for (int i = 0; i < 5; i++){
getline(cin, line);
vector<char> row(line.begin(), line.end());
matrix.push_back(row);
}
for (int col = 0; col < max_col; col++){
for (int row = 0; row < matrix.size(); row++){
if (col < matrix[row].size()){
cout << matrix[row][col];
}
}
}
return0;
}
C#
using System;
using System.Collections.Generic;
using System.Linq;
classProgram{
staticvoidMain(string[] args){
int max_col = 15;
string line;
List<List<char>> matrix = new List<List<char>>();
while((line = Console.ReadLine()) != null){
matrix.Add(line.ToList());
}
for (int col = 0; col < max_col; col++){
for (int row = 0; row < matrix.Count; row++){
if (matrix[row].Count > col){
Console.Write(matrix[row][col]);
}
}
}
}
}
Python
max_row = 5;
max_col = 15;
matrix = [list(input()) for _ inrange(max_row)]
for col inrange(max_col):
for row inrange(max_row):
if col < len(matrix[row]):
print(matrix[row][col], end="")
Node.js
const fs = require('fs');
const max_row = 5;
const max_col = 15;
const input = fs.readFileSync('/dev/stdin', 'utf8').trim().split('\n');
for (let col = 0; col < max_col; col++){
for (let row = 0; row < max_row; row++){
if (col < input[row].length) {
process.stdout.write(input[row][col]);
}
}
}
4번 색종이
문제
가로, 세로의 크기가 각각 100인 정사각형 모양의 흰색 도화지가 있다. 이 도화지 위에 가로, 세로의 크기가 각각 10인 정사각형 모양의 검은색 색종이를 색종이의 변과 도화지의 변이 평행하도록 붙인다. 이러한 방식으로 색종이를 한 장 또는 여러 장 붙인 후 색종이가 붙은 검은 영역의 넓이를 구하는 프로그램을 작성하시오.
https://www.acmicpc.net/problem/2563
예를 들어 흰색 도화지 위에 세 장의 검은색 색종이를 그림과 같은 모양으로 붙였다면 검은색 영역의 넓이는 260이 된다.
입력
첫째 줄에 색종이의 수가 주어진다. 이어 둘째 줄부터 한 줄에 하나씩 색종이를 붙인 위치가 주어진다. 색종이를 붙인 위치는 두 개의 자연수로 주어지는데 첫 번째 자연수는 색종이의 왼쪽 변과 도화지의 왼쪽 변 사이의 거리이고, 두 번째 자연수는 색종이의 아래쪽 변과 도화지의 아래쪽 변 사이의 거리이다. 색종이의 수는 100 이하이며, 색종이가 도화지 밖으로 나가는 경우는 없다.
출력
첫째 줄에 색종이가 붙은 검은 영역의 넓이를 출력한다.
2차원 배열 카테고리의 마지막 문제라서 그런가 단순 값 처리가 아닌 응용이 필요한 문제이다.
100 x 100 크기의 도화지란 배열의 최대 크기를 정한 것으로 [100][100] 크기의 2차원 배열 안에서 이루어지는 작업이다.
색종이는 모두 동일한 크기로 최대 100개까지 붙인다. 색종이 크기는 모두 일정한 10 x 10이며 왼쪽 하단의 꼭짓점에 대한 좌표가 주어진다.
당장은 두 가지 방법이 떠오른다.
1. 도화지에서 색종이가 없는 영역 구하기
2. 전체 색종이 개수의 영역에서 겹치는 부분 제외하기
2차원 배열의 구조를 기준으로 생각해 본다.
100 x 100 크기에서 입력된 색종이 범위만큼 인덱스를 체크해 놓고 최종적으로 체크한 부분만 계산하면 색종이가 덮은 영역이 나온다.
C++
#include<iostream>#include<vector>usingnamespace std;
intmain(){
int count = 0;
int matrix[100][100] = {0};
cin >> count;
for (int i = 0; i < count; i++){
int x1, y1, x2, y2 = 0;
cin >> x1;
cin >> y1;
x2 = x1 + 10;
y2 = y1 + 10;
for (int x = x1; x < x2; x++){
for (int y = y1; y < y2; y++){
matrix[x][y] = -1;
}
}
}
int area = 0;
for (int row = 0; row < 100; row++){
for (int col = 0; col < 100; col++){
if (matrix[row][col] == -1){
area++;
}
}
}
cout << area;
return0;
}
중복으로 겹쳐진 부분을 체크하는 부분을 조건을 주고 확인하는 걸 추가해도 될 것 같다.
C#
C++로 코드를 쓰면서 방식에 문제가 없으니 좀 더 정리해서 작성해 본다.
using System;
classProgram{
staticvoidMain(string[] args){
int[,] matrix = newint[100, 100];
int count = int.Parse(Console.ReadLine()!);
for (int i = 0; i < count; i++){
string? input = Console.ReadLine();
if (string.IsNullOrEmpty(input)) return;
string[] xy = input.Split();
int x = int.Parse(xy[0]);
int y = int.Parse(xy[1]);
for (int dx = x; dx < x + 10; dx++){
for (int dy = y; dy < y + 10; dy++){
matrix[dx, dy] = -1;
}
}
}
int area = 0;
for (int row = 0; row < 100; row++){
for (int col = 0; col < 100; col++){
if (matrix[row, col] == -1){
area++;
}
}
}
Console.WriteLine(area);
}
}
Python
count = int(input());
matrix = [[0] * 100for _ inrange(100)]
for _ inrange(count):
x, y = map(int, input().split())
for dx inrange(x, x + 10):
for dy inrange(y, y + 10):
matrix[dx][dy] = 1
area = sum(row.count(1) for row in matrix)
print(area)
Node.js
const fs = require('fs');
const input = fs.readFileSync('/dev/stdin', 'utf8').trim().split('\n');
const count = parseInt(input[0]);
let matrix = Array.from({length : 100 }, () =>Array(100).fill(0));
for (let i = 1; i <= count; i++){
const xy = input[i].split(" ").map(Number);
const x = xy[0];
const y = xy[1];
for (let dx = x; dx < x + 10; dx++){
for (let dy = y; dy < y + 10; dy++){
matrix[dx][dy] = 1;
}
}
}
let area = 0;
for (let row = 0; row < 100; row++){
for (let col = 0; col < 100; col++){
if (matrix[row][col] == 1){
area++;
}
}
}
console.log(area);
Input System은 Input Actions와 PlayerInput으로 구성된다. Input Actions는 입력과 행동의 연결을 정의하는 구조이고, PlayerInput은 그 정의를 바탕으로 실제 입력을 감지하고 동작을 실행하는 컴포넌트다.
동작의 주체가 되는 Player 게임 오브젝트에 PlayerInput 컴포넌트를 추가하고, Actions 필드에 사용할 Input Actions 에셋을 지정하면 PlayerInput을 사용할 준비가 완료된다.
unity - PlayerInput
Default Scheme
어떤 입력 장치(키보드마우스, 게임패드 등)에 대한 입력을 처리할지 지정할 수 있는 설정이다.
Player Input - Scheme
기본값인 Any는 어떤 장치에서든 입력을 받을 수 있다. 특정 장치를 지정하더라도 Auto-Switch가 활성화되어 있다면, 다른 장치의 입력이 감지되었을 때 자동으로 해당 장치로 전환되어 입력을 처리한다. 반대로 Auto-Switch가 비활성화되어 있다면, 처음 감지된 장치만 계속 사용하게 된다.
예를 들어 Default Scheme을 Any로 설정하면 먼저 사용된 키보드마우스로 Control Scheme이 잡힌다.
PlayerInput - Keyboard&Mouse
이 상태에서 게임패드를 연결하여 조작을 하면 Auto-Switch 덕분에 자동으로 게임패드로 입력이 감지되어 처리된다.
PlayerInput - GamePad detection
Default Map
Default Map은 Input Action Maps 중에서 기본으로 사용할 Map을 지정하는 설정이다.
Input Actions - Default Map
Input Action은 상황에 따라 다른 Map으로 전환할 수 있다.
예를 들어 캐릭터 조작 시에는 Player 맵을 사용하고, 메뉴를 열었을 때는 UI 맵으로 전환하여 입력을 UI 조작에만 반응하도록 만들 수 있다. 이러한 방식은 게임패드나 조이스틱처럼 여러 입력이 혼합되는 환경에서 특히 유용하다.
UI Input Module
UI와의 상호작용은 EventSystem에 연결된 Input System UI Input Module 컴포넌트를 통해 처리된다.
기본 Unity UI 시스템은 단일 입력만 처리하도록 설계되어 있지만 멀티플레이 게임(로컬)에서는 각 플레이어가 자신의 UI를 조작해야 하는 경우가 생기기 때문에 각 플레이어에게 UI 입력용 시스템도 별도로 연결해주어야 한다.
Event System
PlayerInput 컴포넌트가 사용하는 Input Action Asset은 UI Input Module에도 동일하게 적용되어, 동일한 동작과 디바이스 설정으로 UI와 게임 조작을 일관되게 제어할 수 있다.
멀티플레이 환경에서는 MultiplayerEventSystem 컴포넌트를 사용하여 화면에 여러 UI 인스턴스를 동시에 표시하고 각 UI를 서로 다른 플레이어가 독립적으로 제어할 수 있게 만들 수 있다.
MultiPlayer Event System
Camera
Camera 필드는 멀티 플레이어 상황에서, 플레이어 관리에 사용되는 PlayerInputManager 컴포넌트의 Split-Screen 기능이 활성화된 경우에 의미를 갖는다.
PlayerInputManager
이 기능이 켜지면 각 플레이어는 자신만의 카메라를 통해 분할된 화면을 보게 되며, 이때 PlayerInput의 Camera 필드에 각 플레이어의 카메라를 연결해주어야 한다.
Mario Kart 2P
이렇게 설정하면, UI의 입력 처리도 해당 카메라를 기준으로 이루어지므로 플레이어마다 올바른 UI 포커스 및 이벤트 처리가 가능해진다.
Behavior
이벤트가 발생했을 때 어떤 방식으로 처리할지 결정하는 옵션이다.
Send Messages
PlayerInpt - Behavior
Send Message는 Unity의 고전적인 메시지 전달 방식으로, SendMessage("함수명", 파라미터) 형태로 특정 메서드를 실행한다.
PlayerInput 컴포넌트는 Input Action이 발생했을 때, 해당 액션 이름을 기반으로 구성된 함수명을 자동으로 호출한다. 이 메서드는 GameObject에 연결된 MonoBehaviour 스크립트 내에 정확한 이름으로 존재해야 하며, 그렇지 않으면 호출되지 않고 무시된다.
예를 들어 Jump라는 액션이 정의되어 있다면, PlayerInput은 OnJump()라는 함수명을 찾아 호출한다.
이처럼 Input Action에서 정의된 Action 이름 앞에 On을 붙인 함수명이 호출 대상이 되며 위 이미지에서 텍스트로 사용가능한 함수명이 표시된다. 이 함수명 텍스트는 Input Actions 에셋에서 Action의 이름을 추가하거나 편집하고 Asset을 저장하면 자동으로 수정되어 보인다.
SendMessage 방식의 장점은 간단하고 빠르게 연결 가능하기 때문에 코드 구조가 가볍지만 메서드명이 정확히 일치해야 작동한다는 점과 동적 호출 방식이기 때문에 컴파일 타임에서 오류 체크가 불가능하며 함수의 파라미터가 InputValue만 전달되기 때문에 복합적인 처리나 Context 정보 전달, 다중 파라미터 기반 로직등의 처리가 어렵다.
Broadcast Message 방식도 Unity의 고전적인 메시지 전달 방식으로 Send Message와 동일한 형식을 따르지만, 차이점은 현재 GameObject 뿐만 아니라 모든 자식 오브젝트들에게도 메시지를 전파한다는 점이다.
예를 들어 계층 구조 내 여러 컴포넌트가 동일한 함수명을 가지고 있다면, 모두 호출되기 때문에 예상치 못한 중복 동작이 발생할 수 있다. 또한 자식 오브젝트가 많거나 계층이 깊을 경우, 성능 저하의 원인이 될 수 있어 주의가 필요하다.
Invoke Unity Events
이벤트 기반 정적 연결방식으로, 함수명을 신경 쓸 필요 없이 에디터에서 간편하게 메서드를 지정할 수 있다.
Behavior - Invoke Unity Events
Input Actions에서 정의한 Action들은 PlayerInput 컴포넌트 내에서 자동으로 이벤트로 생성되며, 인스펙터 창에서 해당 이벤트에 호출할 메서드를 직접 할당할 수 있다. 이러한 이벤트 - 리스너 구조는 코드 간의 결합도를 낮추고, 동작을 시각적으로 구성할 수 있어 직관적이고 유지보수도 용이하다.
Invoke C Sharp Events
Invoke C# Events는 코드 기반으로 입력을 처리하는 방식으로, PlayerInput이 제공하는 onActionTriggered 이벤트에 리스너를 등록하여 모든 입력 액션을 하나의 이벤트에서 감지할 수 있다.
action.name을 기준으로 원하는 액션을 구분해서 처리할 수 있으며, Unity Events 방식은 인스펙터에서 함수명을 문자열로 참조하기 때문에 함수명이 바뀌면 참조가 끊길 수 있는 반면 Invoke C# Events는 코드에서 직접 리스너를 등록하기 때문에 함수명을 변경하더라도 안전하게 리팩토링이 가능하며, 유지보수에 강점을 가진다.
정리
각 Behavior 방식은 특징이 다르기 때문에 상황에 따라 적절히 선택해서 사용하는 것이 중요하며 다음과 같이 요약할 수 있다.
- Send Message : 간단한 구현이 필요할 때 유용
- Broadcast Message : 하위 오브젝트까지 포함하여 입력 처리를 해야 하는 특수한 경우에 사용