[C++] Codeforces Round #739 (Div. 3)

Codeforces Round #739 (Div. 3)

결과

submission rating

Rating : 1272 -> 1239 (-33)

풀이

A

a

A - Dislike of Threes

3의 배수 혹은 마지막 자리 수가 3인 수를 제외한 수 중 k번째 수가 어떤 수 인지 출력하는 문제

k가 최대 1000으로 수가 작기 떄문에 1000번째 수 까지 모든 경우를 저장하고 있다가 출력하면 정답

#pragma warning(disable : 4996)
#include <bits/stdc++.h>
#define all(x) (x).begin(), (x).end()
using namespace std;
typedef long long ll;
typedef long double ld;
typedef vector<ll> vll;
typedef vector<ld> vld;
typedef pair<ll, ll> pll;
typedef pair<ld, ld> pld;
typedef tuple<ll, ll, ll> tl3;
#define FOR(a, b, c) for (int(a) = (b); (a) < (c); ++(a))
#define FORN(a, b, c) for (int(a) = (b); (a) <= (c); ++(a))
#define rep(i, n) FOR(i, 0, n)
#define repn(i, n) FORN(i, 1, n)
#define tc(t) while (t--)
// https://codeforces.com/contest/1560/problem/0
bool is_pass(ll n){
    if(n % 3 == 0) return true;
    if(n % 10 == 3) return true;
    return false;
}
int main() {
    ios_base::sync_with_stdio(0);
    cin.tie(0);

    ll n;
    cin >> n;
    ll a[1001] = {0};
    a[1] = 1;
    for(int i = 2;i<=1000;i++){
        ll val = a[i-1];
        val++;
        while(is_pass(val)) val++;
        a[i] = val;
    }
    tc(n){
        ll x;
        cin >> x;
        cout << a[x] << "\n";
    }

    return 0;
}
B

b

B - Who’s Opposite?

a,b,c 세 정수를 입력으로 받는다. a와 b는 원탁에서 정 반대의 위치에 있다고 할 때 c의 정 반대의 위치에 있는 수가 무엇인지 찾는 문제

우선 a와 b사이에 몇개의 수가 존재하는지 구한다. 그것을 토대로 원탁에 최대 몇 까지의 수가 있는지 구한다.

그렇게 되면 자연스럽게 c의 반대쪽에 있는 수를 알 수 있다.

하지만 답을 구할 수 없는 경우가 있는데

        ll s = max(a, b);
        ll e = min(a, b);
        ll dif = s - e - 1;
        ll end = s + dif - e + 1;

를 토대로 a와 b사이의 수 dif개가 있고 원탁에 있는 최대 수는 end 라고 했을 떄 만약 end가 a혹은 b보다 크다면 그것은 답이 존재하지 않는다.

혹은 c가 end 보다 크면 c는 원탁에 존재할 수 없는 수 이므로 그 경우 또한 답이 존재하지 않는다.

그리고 a와 b사이에 수가 dif개가 있는데 어떤 경우라도 1이 들어갈 자리가 없다면 그 경우도 답이 존재하지 않는다.

이러한 경우를 제외한 모든 경우에는 답이 존재하게 된다.

#pragma warning(disable : 4996)
#include <bits/stdc++.h>
#define all(x) (x).begin(), (x).end()
using namespace std;
typedef long long ll;
typedef long double ld;
typedef vector<ll> vll;
typedef vector<ld> vld;
typedef pair<ll, ll> pll;
typedef pair<ld, ld> pld;
typedef tuple<ll, ll, ll> tl3;
#define FOR(a, b, c) for (int(a) = (b); (a) < (c); ++(a))
#define FORN(a, b, c) for (int(a) = (b); (a) <= (c); ++(a))
#define rep(i, n) FOR(i, 0, n)
#define repn(i, n) FORN(i, 1, n)
#define tc(t) while (t--)
// https://codeforces.com/contest/1560/problem/B
int main() {
    ios_base::sync_with_stdio(0);
    cin.tie(0);

    ll n;
    cin >> n;
    tc(n) {
        ll a, b, c;
        cin >> a >> b >> c;
        ll s = max(a, b);
        ll e = min(a, b);
        ll dif = s - e - 1;
        ll end = s + dif - e + 1;
        if (end < s || end < e || end < c) {
            cout << -1 << "\n";
            continue;
        }
        if ((e + dif + 1) % end != s % end) {
            cout << -1 << "\n";
            continue;
        }
        ll ans = (c + end / 2) % end;
        if (ans == 0) ans = end;
        cout << ans << "\n";
    }

    return 0;
}
C

c

C - Infinity Table

2차원 행렬에서 정수가 일정한 패턴대로 작성되어 있다. 그럼 x는 해당 행렬에 어느 좌표에 있을지를 구하는 문제

해당 규칙은 간단한 규칙이므로 쉽게 눈에 보일 것이다.

(1,1) -> (1,2) -> (2,2) -> (2,1) -> (1,3) -> …. 과 같은 순서로 진행된다.

정해진 규칙대로 입력받은 수가 있는 좌표를 구하면 정답

#pragma warning(disable : 4996)
#include <bits/stdc++.h>
#define all(x) (x).begin(), (x).end()
using namespace std;
typedef long long ll;
typedef long double ld;
typedef vector<ll> vll;
typedef vector<ld> vld;
typedef pair<ll, ll> pll;
typedef pair<ld, ld> pld;
typedef tuple<ll, ll, ll> tl3;
#define FOR(a, b, c) for (int(a) = (b); (a) < (c); ++(a))
#define FORN(a, b, c) for (int(a) = (b); (a) <= (c); ++(a))
#define rep(i, n) FOR(i, 0, n)
#define repn(i, n) FORN(i, 1, n)
#define tc(t) while (t--)
// https://codeforces.com/contest/1560/problem/C
int main() {
    ios_base::sync_with_stdio(0);
    cin.tie(0);

    ll n;
    cin >> n;
    ll a[31624] = {0};
    a[1] = 1;
    a[2] = 2;
    a[3] = 5;
    ll add = 5;
    for (int i = 4; i < 31624; i++) {
        a[i] = a[i - 1] + add;
        add += 2;
    }
    tc(n) {
        ll x;
        cin >> x;
        if (x == 1) {
            cout << "1 1\n";
            continue;
        }
        ll start = 0;
        for (int i = 1;; i++) {
            if (a[i] <= x && a[i + 1] > x) {
                start = i;
                break;
            }
        }
        if (x < a[start] + start - 1) {
            cout << x - a[start] + 1 << " " << start << "\n";
        } else if (x == a[start] + start - 1) {
            cout << start << " " << start << "\n";
        } else {
            cout << start << " " << start - x + a[start] + start - 1 << "\n";
        }
    }

    return 0;
}

Husky + Github Action + commitlint을 사용하여 commit message 관리하기

ss

commitlint 는 commit message를 linting해주는 모듈입니다. 이를 husky 혹은 github action과 함께 사용한다면 더욱 편리하게 commit message를 관리할 수 있습니다.

https://commitlint.js.org/#/

설치

yarn add @commitlint/cli @commitlint/config-conventional -D

configure

echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js

Husky를 이용한 Commitlint 적용법

    "husky": {
        "hooks": {
            "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
        }
    }

위와 같이 husky를 설정하게 되면 git commit 수행 시 커밋 메시지를 불러와 commitlint를 수행하게 됩니다.

    "husky": {
        "hooks": {
            "commit-msg": "if git-branch-is dev; then commitlint -E HUSKY_GIT_PARAMS; fi"
        }
    },

또한 git-branch-is 와 결합하여 특정 브랜치에서만 commitlint가 수행되게 할 수도 있습니다.

위 명령어는 dev 브랜치에 대해서만 commitlint를 수행하게 하는 명령어 입니다.

Github Workflow를 이용한 Commitlint 적용법

Github Action을 사용하여 commitlint를 수행할 수 있습니다.

이러한 Workflow를 사용하여 PR시 commit convention을 준수하지 않은 PR에 대해 merge를 막는다거나 하는 방법으로 사용할 수 있습니다.

# Run commitlint on Pull Requests and commits
name: commitlint
on:
  pull_request:
      types: ['opened', 'edited', 'reopened', 'synchronize']
  push:
      branches:
        - '**'

jobs:
  lint-pull-request-name:
    # Only on pull requests
    if: github.event_name == 'pull_request'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v1
      - run: yarn add @commitlint/config-conventional
      - uses: JulienKode/pull-request-name-linter-action@v0.1.2
  lint-commits:
    # Only if we are pushing or merging PR to the master
    if: (github.event_name == 'pull_request' && github.base_ref == 'refs/heads/master') || github.event_name == 'push'
    runs-on: ubuntu-latest
    env:
      GITHUB_TOKEN: $
    steps:
      - uses: actions/checkout@v2
        with:
          fetch-depth: 30 # Its fine to lint last 30 commits only
      - run: yarn add @commitlint/config-conventional
      - uses: wagoid/commitlint-github-action@v1

Nestjs 튜토리얼 따라하기 9편

Nestjs 튜토리얼 따라하기 9편

Interceptors

Interceptors

  • Interceptors는 @Injectable() 데코레이터를 사용하는 클래스이며 NestInterceptor 인터페이스를 구현하여 정의 된다.

  • Interceptors를 통하여 아래와 같은것을 실행할 수 있다.

    • method의 전후에 추가 로직 구현
    • 함수의 반환값을 변환하여 결과로 낼 수 있음

Aspect interception

아래는 간단한 LoggingInterceptor 예제이다.

import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    console.log('Before...');

    const now = Date.now();
    return next
      .handle()
      .pipe(
        tap(() => console.log(`After... ${Date.now() - now}ms`)),
      );
  }
}

Binding interceptors

Interceptor를 적용하기 위하여 @UseInterceptors() 데코레이터를 사용할 것이다. Interceptor도 controller에 적용할 수 있고 혹은 전역으로 적용할 수 있다.

@UseInterceptors(LoggingInterceptor)
export class CatsController {}

전역으로 적용하려면 아래와 같이 구성한다.

const app = await NestFactory.create(AppModule);
app.useGlobalInterceptors(new LoggingInterceptor());
import { Module } from '@nestjs/common';
import { APP_INTERCEPTOR } from '@nestjs/core';

@Module({
  providers: [
    {
      provide: APP_INTERCEPTOR,
      useClass: LoggingInterceptor,
    },
  ],
})
export class AppModule {}

Nestjs 튜토리얼 따라하기 8편

Nestjs 튜토리얼 따라하기 8편

Guards

Guards

  • Guard는 @Injectable() 데코레이터를 사용하는 클래스이며 CanActivate 인터페이스를 구현하여 정의 된다.

  • Guard는 권한부여에 대한 결과를 처리한다. 이는 일반적으로 express의 미들웨어에 의해 처리 되었다.

express의 미들웨어는 next()를 호출하게 되면 그 이후에 무슨 함수가 호출될 지 모르지만 Guards는 ExecutionContext에 접근할 수 있으므로 다음에 실행될 항목을 정확하게 알 수 있다.

Authorization guard

import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const request = context.switchToHttp().getRequest();
    return validateRequest(request);
  }
}

Execution context

```canActive() 함수는 파라미터로 ExecutionContext 를 갖는다. ExecutionContextArgumentHost`로 부터 상속된다.

Role-based authentication

import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';

@Injectable()
export class RolesGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    return true;
  }
}

Guard 적용하기

Pipe처럼 Guard도 Controller에 적용하거나, 전역으로 적용할 수 있다. Controller에 적용하기 위해서 @UseGuards() 데코레이터를 사용해야 한다.

@Controller('cats')
@UseGuards(RolesGuard)
export class CatsController {}

전역으로 적용하기 위해서 useGlobalGuards() 메소드를 사용한다.

const app = await NestFactory.create(AppModule);
app.useGlobalGuards(new RolesGuard());
import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';

@Module({
  providers: [
    {
      provide: APP_GUARD,
      useClass: RolesGuard,
    },
  ],
})
export class AppModule {}

Setting roles per handler

위에서 구현한 RolesGuard가 잘 작동하기 위해서 각 Controller Route마다 어떤 권한을 가지고 있을 때 권한을 허가할건지 설정해야한다.

@Post()
@SetMetadata('roles', ['admin'])
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}

혹은 직접 데코레이터를 생성해서 권한을 설정할 수 있다.

import { SetMetadata } from '@nestjs/common';

export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
@Post()
@Roles('admin')
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}

위에서 구현한 것을 모두 합치면

import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';

@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    const roles = this.reflector.get<string[]>('roles', context.getHandler());
    if (!roles) {
      return true;
    }
    const request = context.switchToHttp().getRequest();
    const user = request.user;
    return matchRoles(roles, user.roles);
  }
}

Nestjs 튜토리얼 따라하기 7편

Nestjs 튜토리얼 따라하기 7편

Pipes

Pipes

  • Pipe 는 @Injectable() 데코레이터를 쓰는 클래스로서 PipeTransform 인터페이스를 구현한 클래스

  • Pipe에는 두가지 일반적인 사용법이 있다.

    1. 입력 데이터를 원하는 형식으로 변환
    2. 입력 데이터가 유효한지 확인

이 두 경우 모두 Pipe는 Controller route handler 에서 작동한다.

Nest에는 내장 Pipe가 구현되어 있다. 또한 Custom Pipe도 구현할 수 있다.

내장 Pipe

  • ValidationPipe
  • ParseIntPipe
  • ParseFloatPipe
  • ParseBoolPipe
  • ParseArrayPipe
  • ParseUUIDPipe
  • ParseEnumPipe
  • DefaultValuePipe

모든 내장 Pipe들은 @nestjs/common 패키지에서 import 된다.

Pipe 활용

Pipe를 사용하기 위해서 다음과 같이 사용된다.

@Get(':id')
async findOne(@Param('id', ParseIntPipe) id: number) {
  return this.catsService.findOne(id);
}

findOne() 함수에서 idParseIntPipe로 변환하여 사용한다. 만약 id가 number 타입이 아니라면 아래와 같은 에러를 발생시킨다.

{
  "statusCode": 400,
  "message": "Validation failed (numeric string is expected)",
  "error": "Bad Request"
}

아래와 같이 statusCode를 변경하는것도 가능하다.

@Get(':id')
async findOne(
  @Param('id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE }))
  id: number,
) {
  return this.catsService.findOne(id);
}

Custom Pipe

위에서 언급했듯이 Custom Pipe를 생성할 수 있다.

import { PipeTransform, Injectable, ArgumentMetadata } from '@nestjs/common';

@Injectable()
export class ValidationPipe implements PipeTransform {
  transform(value: any, metadata: ArgumentMetadata) {
    return value;
  }
}

모든 Pipe는 transform() 메소드를 구현해야 한다. 이를 위해서 2개의 파라미터가 필요하다

  • value
  • metadata

value는 현재 처리되고 있는 파라미터이며 metadata는 value의 메타데이터 이다.

export interface ArgumentMetadata {
  type: 'body' | 'query' | 'param' | 'custom';
  metatype?: Type<unknown>;
  data?: string;
}

Schema 기반의 유효성 검사

@Post()
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}
export class CreateCatDto {
  name: string;
  age: number;
  breed: string;
}

위와 같은 create() 메소드와 CreateCatDto가 있을 때 유효성을 검사할 수 있도록 Pipe를 수정해 보도록 하겠다.

오브젝트 Schema 유효성 검사

여기서는 joi를 기반의 스키마를 사용하는 유효성 검사 Pipe를 구현한다.

먼저 아래의 패키지를 설치한다

$ npm install --save joi
$ npm install --save-dev @types/joi
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
import { ObjectSchema } from 'joi';

@Injectable()
export class JoiValidationPipe implements PipeTransform {
  constructor(private schema: ObjectSchema) {}

  transform(value: any, metadata: ArgumentMetadata) {
    const { error } = this.schema.validate(value);
    if (error) {
      throw new BadRequestException('Validation failed');
    }
    return value;
  }
}

Validation Pipe 적용

위에서 만든 JoiValidationPipe를 적용하기 위해서 아래와 같은 과정을 따른다.

  • JoiValidationPipe 인스턴스를 생성한다.
  • joi schema를 생성자의 파라미터로 넣는다.
  • 메소드에 해당 pipe를 바인딩한다.

이를 수행하면 아래와 같은 method처럼 구성된다.

@Post()
@UsePipes(new JoiValidationPipe(createCatSchema))
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}

Class validator

Class validator를 사용하기 위하여 아래의 패키지를 설치한다.

$ npm i --save class-validator class-transformer

위 패키지가 설치되면 CreateCatDto 클래스에 몇가지 데코레이터를 추가할 수 있다.

import { IsString, IsInt } from 'class-validator';

export class CreateCatDto {
  @IsString()
  name: string;

  @IsInt()
  age: number;

  @IsString()
  breed: string;
}

이를 사용하게 되면 별도의 유효성 검사를 위한 클래스를 만들 필요가 없다.

Global scoped pipes

ValidationPipe를 일반화 시켜 만들고 이를 전역으로 선언하고 싶다면 아래와 같이 구성한다.

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe());
  await app.listen(3000);
}
bootstrap();
import { Module } from '@nestjs/common';
import { APP_PIPE } from '@nestjs/core';

@Module({
  providers: [
    {
      provide: APP_PIPE,
      useClass: ValidationPipe,
    },
  ],
})
export class AppModule {}