사이드 프로젝트 - GLotto 추첨기

사이드 프로젝트를 시작 - GLotto 개발

GLotto 개요

사이드 프로젝트를 시작하면서 제일 먼저 만들어볼 수 있는게 무엇이 있을까 생각해보았다.
그래서 생각한게 로또 추첨기를 이전 데이터를 통계적 기반으로 추첨해주는 추첨기를 만들어보면 어떨까라고 생각을 해보았다.

사실 로또라는게 독립적 수행이기 때문에 이전의 결과가 이후의 결과에 영향을 미치지는 않다는것을 알지만 이것을 통계적으로 누적된 데이터를 활용하여 번호를 뽑아낼 수 있다면 어떨까라고 생각하면서 시작하게 되었다.
이전의 데이터를 학습시키고 이 데이터를 기반으로 통계에 근거하여 좀 더 맞아 떨어지는 번호를 생성해내면 어떨까라는게 아이디어의 시작이었다.


설계

  • 클라이언트 : 안드로이드 어플리케이션
  • 서버 : 스프링 서버
  • 데이터베이스 : mariaDB

간단히 언제든지 들고다니면서 번호를 받을 수 있고, 이것으로 수익성을 낼 수 있는 방법이 뭐가 있을까 고민하다보니 안드로이드에 출시하여 사용해보면 어떨까 생각해보았다. 안드로이드 앱 출시 경험도 해보고, 서버와 데이터베이스를 연동하여 이전 실제 데이터들은 서버에 계속해서 누적하면서 만들어내는 것이다.

Synology NAS를 활용하여 Spring Server를 구동하고, MariaDB를 구축하여 해당 서비스를 구현하고자 한다.
(진작에 이렇게 좀 활용해보지 NAS사고 지금까지 그냥 저장소로만 사용하다니 멍충..)


구현

클라이언트

이 사이드 프로젝트를 제일 먼저 하게된 이유는 앞으로 사이드 프로젝트를 만들기 위해 포토샵을 필수적으로 배울수밖에 없게 되었는데, 가장 간단하게 UI를 뽑아낼 수 있을것 같아서이다.

그냥 로또 공만 그려서 번호를 뿌려주거나 로또 용지를 그려서 위에 뿌려주면 되니 참 쉽지 않은가?
그 외에 다른 앱적인 요소는 Material 3.0을 적극 활용해보려고 한다.

서버

사실 제일 걱정되는 부분이 이 부분이었다. 나만의 서버를 구축하여 서비스를 만들어낸다는것 자체가 경험이 많이 부족하고, 이전에 배웠던 Spring 내용은 내 머릿속에 거의 남아있지 않았기 때문이랄까….?
그래도 NAS에 Spring war를 올려서 구동할 수 있게끔 제공하고 있어서 다행이었다.
서버를 따로 구입하기에는 좀…😕

데이터베이스

데이터베이스는 NAS에 MediaWiki를 만들어본 경험이 있었기 때문에 MariaDB를 그대로 사용하기로 했다. 그나마 알고 있는게 SQL문이기 때문에 적극적으로 활용할 수 있고, 사실 추첨 번호만 저장하면 되기 때문에 그리 복잡하지도 않다.
CRUD만 구현해두면 앞으로 변동 가능한 부분도 많고, 손 쉽게 활용할 수 있을것 같았다.

대충 위에서 정리한대로 진행 할 예정이고, 가능하면 구현하면서 새롭게 배운 내용들도 같이 정리하려고 한다.
사이드 프로젝트 가보자구우~~~!

식의약 공공데이터 활용 공모전 1차 평가 통과

일상 기록

‘그린라이트’라는 장기 기증 활성화를 주제로 하는 캠페인의 이름을 따서 식의약 공공데이터 활용 공모전에 지난 6월에 참가를 했었다.
아이디어만큼은 자신이 있었으나 마이너한듯한 분야라서 걱정을 하고 있었는데, 오늘 1차 평가 통과 메세지가 왔다!!

67개의 팀이 참가해서 아이디어부문 8개 팀, 개발부문 4개 팀이 선정 되었는데 그 중 개발부문 한 팀으로 선정이 된 것이다!!!
기쁨도 잠시… 2차 평가의 날이 보름정도 남은 상태라 발표자료와 앱 개발을 해야하는 상황이 온 것이다.
공공데이터도 사용 신청도 미흡한 상태라 걱정이 되긴 하지만 팀원들과 모여서 합의하고 일정 및 업무 분담을 하기로 했다.

이번에도 좋은 결과를 거둘 수 있는 공모전이 되었으면 좋겠다고 생각하고 있다.

식의약 공공데이터 활용 공모전 준비

일상 기록

이번에 같은 팀의 동료와 동기 3명이서 식의약 공공데이터 활용을 해서 웹/앱 개발을 하는 공모전에 참가하려고 한다.
그래서 사전 Notion을 이용하여 일정 관리 및 아이디어 모집을 했고, 오늘에서 모여 최종 아이디어 회의를 시작했다.

다양한 아이디어들 중에서 3가지 아이디어가 가장 괜찮을것 같았고, 그 중 내가 제시한 아이디어가 가장 참신성에서 괜찮다고 투표 결과가 나와서 채택하게 되었다.
아이디어가 정해지고 나서도 활용할 공공데이터의 데이터 내용 및 배경, 기대효과, Mock-up design 등 해야 할 것들이 많이 있어서 정리하는데만 시간이 더 걸리기도 했다.

퇴근 후에 다들 힘든 상태에서 진행하기도 했고, 이번 주.. 너무 바빴어서 잠도 제대로 잔 적이 없긴 하지만 오랜만에 다시 두근거리는 프로젝트를 진행하는것 같아서 기대가 된다.
(개발하느라 고생할거 생각하면 아찔하긴 하다..)

8월 초까지 개발이 어느정도 완료가 되어야 하니 스케줄 정해진대로 완료해서 좋은 성과를 냈으면 한다.

리액트를 활용하여 영화 웹 서비스 개발

Nomadcoder의 강의를 통해 리액트를 이용해서 영화 앱 서비스를 클론 코딩 해보자.

What ReactJS ?

ReacJS를 사용하는 이유

  • interactive(상호작용을 위해)

VanilliaJS vs ReactJS

VaniliaJS

<!DOCTYPE html>
<html>
<body>
    <span>Total click: 0</span>
    <button id="btn">Click me</button>
</body>
<script>
    let counter = 0;
    const button = document.querySelector("btn");
    const span = document.querySelector("span");
    function handleClick() {
        counter += 1;
        span.innerText = `Total clicks: ${counter}`;
    }
    button.addEventListener("click", handleClick);
</script>
</html>

ReactJS

<!DOCTYPE html>
<html>
<body>
    <div id="root"></div>
</body>
<script src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<script>
    let counter = 0;
    const root = document.getElementById("root");
    const h3 = React.createElement(
        "h3",
        { 
            id: "title",
        },
        `Total click: ${counter}`
    );
    const btn = React.createElement(
        "button",
        {
            onClick: () => counter += 1,
        },
        "Click me"
    );
    const container = React.createElement("div", null, [h3, btn]);
    ReactDOM.render(container, root);
</script>
</html>

위에서 간단한 버튼을 삽입하여 카운트를 증가하는 작업을 하는 파일을 만들었다.
간단히 보더라도 body태그 안에 데이터를 넣을 태그를 선언해주고 querySelector에서 태그를 찾아서 넣고 Event Listener를 선언해서 연결해주고…

위에처럼 간단하게 만든 버튼 클릭 시, 카운트 증가하는 경우에는 비슷해보일지 모르지만 저런 기능 하나하나가 모인다고 생각해보면 보기만해도 어질어질해진다…

그래서 ReactJS를 사용해서 상호작용해주는 멋진 프레임워크를 사용한다.

Introducing JSX !

Babel compiles JSX down to React.createElement() calls.

위 문구에서 볼 수 있듯이 reactjs.org에 소개된 JSX의 첫 소개 문구이다.
JSX는 Javascript를 확장한 문법으로, createElement()를 선언하여 태그와 속성을 가져오는 부분을 더 심플하게 컴파일해주는 Babel의 기능이다.

const element = (
  <h1 className="greeting">
    Hello, world!
  </h1>
);
const element = React.createElement(
  'hi',
  {className: 'greeting'},
  'Hello, world!'
);

첫 번째 코드가 JSX를 사용한 코드이고, 아래가 그냥 JS에 createElement()를 사용해서 속성을 선언한 코드이다.

이를 반영해서 위의 간단한 카운트 코드를 변경해보았다.

<!DOCTYPE html>
<html>
<body>
    <div id="root"></div>
</body>
<script src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
    let counter = 0;
    const root = document.getElementById("root");
    const Title = (
        <h3 id="title">
            Total click: `${counter}`
        </h3>
    );
    const Button = (
        <button
            style=
            onClick={() => counter += 1}
        >
            Click me
        </button>
    );
    const container = React.createElement("div", null, [Title, Button]);
    ReactDOM.render(container, root);
</script>
</html>

위 코드에서처럼 JSX를 사용하기 위해서는 Babel을 import할 필요가 있다.
왜냐하면 브라우저는 JSX라는 형태를 인식하지 못하기 때문에 JSX를 브라우저가 알 수 있는 코드로 변형해주기 위해서이다.

그리고서 안을 보자면 일반적으로 알고 있는 HTML의 형태와 유사한 코드를 볼 수 있다.
각 태그가 있고, 그 태그 안에는 class, style등의 속성이나 event가 들어갈 수 있고, 모든 정보들을 알아보기 간편해졌다!

Understanding State

ReactJS에서 State란 무엇인가?
State는 데이터를 저장하는 장소이다.

아래 코드를 살펴보자

...
<script type="text/babel">
    const root = document.getElementById("root");
    let counter = 0;
    function countUp () {
        counter += 1;
        render();
    }
    function render() {
        ReactDOM.render(<Container />, root);
    }
    function Container() {
        return (
            <div>
                <h3>Total click: {counter}</h3>
                <button onClick={countUp}>Click me</button>
            </div>
        );
    }
    render();
</script>
</html>

위의 코드를 보면 버튼을 클릭할 때마다 다시 render() 함수를 불러와 호출하는것을 볼 수 있다.
그러나 저 방식은 코드가 많아지거나 컨테이너의 포함 범위가 많아지게되면 rerender를 어느 시점에서 다시 해줘야 하는지 설계하기도 어렵고 나중에 유지보수도 힘들게 된다.

이것을 위해 useState()를 사용하여 데이터와 해당 데이터의 변화를 감지하여 해당 데이터 부분만 리렌더링 해줄 수 있다.

아래의 코드를 살펴보자

...
<script type="text/babel">
    const root = document.getElementById("root");
    function App() {
        const [counter, setCounter] = React.useState(0);
        const onClick = () => {
            setCounter((current) => current + 1);
        }
        return (
            <div>
                <h3>Total click: {counter}</h3>
                <button onClick={onClick}>Click me</button>
            </div>
        );
    }
    ReactDOM.render(<App />, root);
</script>
</html>

간단하게 useState()를 통해 초기값 0을 가진 counter와 setCounter라는 데이터의 상태를 변화시켜주는 함수를 같이 선언할 수 있다.
그리고 버튼을 클릭할때마다 counter를 증가시켜주도록 하면, 위에서 rerender를 위해 다시 호출하여 리렌더링해주는 작업을 별도로 선언하지 않아도 해당 데이터 부분에 변화가 생기면 해당 데이터 부분만 리렌더링 된다!!
정말 멋진 기능이 아닐 수 없다!

이 동작은 modifier라고 하는 setCounter라고 명명한 부분이 실행이되면 해당 컴포넌트(여기선 App)를 재실행하게 된다.
그래서 setCounter가 실행되는 부분부터 순차적으로 아래로 실행이되며, return()되는 부분이 재실행되고, 리렌더링이 되는것이다.

바로 이 부분이 ReactJS의 기능의 가장 중점이되는 부분이다. 데이터가 바뀔때마다 컴포넌트를 리렌더링하고 UI를 refresh해준다.

이는 vanillaJS처럼 HTML의 요소를 찾을 필요없고, 이벤트리스너를 더해줄 필요도, UI를 업데이트해줄 필요도 없도록 해주었다!
JSX를 통해 바로 HTML을 넣고, 곧바로 이벤트리스너를 더해주고, state가 변화하면 자동으로 리렌더링 되는 것이다!! So Cool ❗️❗️😆

Github profile 꾸미기

Github에서 제공하는 profile 꾸미기를 이용하여 나만의 독특한 프로필s 만들기

  • 깃허브에서 제공하는 나의 profile 꾸미기 기능을 이용해 나만의 독특한 profile을 만들 수 있다.
  • 유니크함을 더해주는 badge 등을 이용하여 눈에띄는 profile을 만들 수 있다.

gitprofile


Git profile을 위한 repository 만들기

gitrepo

우선 깃허브 프로필을 설정하려면 본인의 ID와 일치하는 이름으로 레퍼지토리를 생성해야 한다.

나의 경우 github ID가 gahusb이니 gahusb으로 레포지토리를 만들어줬다.

일반적인 레퍼지토리 생성때와는 다르게 생성화면에서 고양이가 나타나며 말을 건다.

README.md 파일 생성하도록 체크하라는 내용이다. 바로 체크하고 레퍼지토리를 생성하자!

바로 이 레퍼지토리의 README.md 파일이 내 깃허브 프로필에 출력되는 것임을 알 수 있다.

Read.me 꾸미기

헤더

우선 맨 위에 Hello World를 보여주는 헤더 부분을 추가 할 수 있다.

이 부분은 ‘capsule-render’라는 오픈API를 사용하였다.

https://github.com/kyechan99/capsule-render

이 오픈 소스는 awesome 하게도 크기, 모양, 문구 등을 자신의 취향에 맞게 선택하여 사용할 수 있고, 나의 프로필을 더욱 눈에 띄게 만들어줄 수 있다.

방문자 수

프로필인란것은 결국에 나에 대한 정보를 다른 사람에게 보여주기 위한 것이라고 생각한다.

때문에 얼마나 노출되었는지 판단할 수 있다면 좋다고 생각한다.

이것을 나타내주기 위해서 Hit라는 오픈 소스를 사용했다.

https://hits.seeyoufarm.com/

Hit

위의 사이트에서 나의 github 주소를 입력하고 OPTIONS를 통해 색상과 끝의 라운드 정도 등을 선택하고

아래부분에 MARKDOWN의 주소를 COPY하여 내 read.me의 원하는 위치에 넣으면 방문하는 수에 따라서 카운트가 증가한다.

나에 대한 상세 설명

위에 간단한 방문 인사말고 나에 대해서 좀더 재미있게 표현하기 위해서 코드로 나를 표현하는 부분을 넣어 볼 수 있다.

(``` Grave + 언어)
```javascript
const jaeoh = {
    pronouns: "He" | "Him",
    code: ["Java", "C", "C++", "Javascript", "Python"],
    liveIn: Yeongdeungpo-gu, Seoul, Republic of Korea
};

위 코드처럼 내가 넣을 코드 스타일을 선택하여 나에 대한 정보를 입력해서 나를 소개하였다.

소셜 정보 & contact

나에게 연락 할 수 있는 Email이나 소셜 정보를 넣어주었다.

[![GitHub](icons/github.png)](https://github.com/gahusb)
[![LinkedIn](icons/linkedin.png)](https://www.linkedin.com/in/jaeoh-park-gahusb/)
[![Instagram](icons/instagram.png)](https://www.instagram.com/gahusb/)

위와 같이 나의 정보와 누가봐도 해당 소셜에 해당하는 아이콘으로 표시해줄 수 있다.

아이콘은 여기서 다운로드 받아서 나의 레포지토리에 넣고 사용할 수 있다.

기술 스택

뱃지(Badge)를 사용하여 나의 기술 스택을 이쁘게 표현할 수 있다.

https://shields.io/

위 shields라는 곳에서 내가 원하는 형태로 뱃지를 만들어서 사용할 수도 있고,

https://simpleicons.org/

위 simpleicons라는 사이트에서 내가 원하는 기술 스택에 해당하는 아이콘을 받아서 뱃지로 만들어 사용할 수 있다.

![Java](https://img.shields.io/badge/Java-orange?style=flat-square&logo=java)

예를 들어 JAVA의 경우 위 Java-orange라는 뱃지의 모양에 내가 원하는 뱃지의 이름을 입력하고, 스타일, 로고 등을 선택하여 사용할 수 있다.

이런 나만의 뱃지를 만들기 귀찮다면,

https://cocoon1787.tistory.com/689

이런 뱃지들을 만들어서 공유하는 오픈 소스를 가져다가 사용할 수 있다.

나만의 기술 스택들을 좀 더 자유롭고 눈에 띄게 바꿔보자

Github stat

내가 지금까지 commit한 횟수나 repositories에 있는 언어를 분석하여 어떤 언어를 가장 많이 사용했는지 표시할 수 있다. 일종의 My github analytics 이라고 할까?

자신이 깃에서 어떤 활동을 하고 있는지 자세히 보여주는 지표이다.

![아이디's github stats](https://github-readme-stats.vercel.app/api?username=아이디&show_icons=true)

아이디로 되어 있는 부분에 나의 github ID를 넣어서 사용할 수 있다.

나의 경우

<p>
  <a href="https://github.com/gahusb">
    <img src="https://github-readme-stats.vercel.app/api/top-langs/?username=gahusb&layout=compact&show_icons=true&show_owner=false&hide_title=false&theme=gruvbox" />
  </a>
  <a href="https://github.com/gahusb">
    <img src="https://github-readme-stats.vercel.app/api?username=gahusb&hide_title=false&show_icons=true&include_all_commits=false&theme=gruvbox" />
  </a>
</p>

위에 처럼 html 태그에 url을 넣어서 좀더 옵션을 제어하여 사용하였다.

나의 ID인 gahusb 부분을 지우고 자신의 github ID를 넣어서 사용하시면 됩니다.

더하기

중간중간 markdown에 나타나는 emoji의 경우 아래 글을 참고해서 사용하시면 됩니다.

https://gahusb.github.io/devlog/markdown-emoji.html

또한 더 다양한 사람들의 github profile을 참고해서 나만의 프로필을 꾸미고 싶다면

https://github.com/abhisheknaiidu/awesome-github-profile-readme

위의 사이트에서 여러 사람들의 awesome한 read.me를 참고하여 나의 프로필에 적용해보자 😊

일상의 기록 - 04월25일

일상 기록

최근에는 업무도 바빠서 늦게까지 야근하고, 그러지 않은 날에는 미뤄진 약속을 가느라고 목표로 했던 1일 1commit 100일 목표에서 많이 떨어지게 된 것 같다.

물론 채우기 위해서 중간중간 알고리즘 한 문제라도 풀어서 올리고는 있지만 예전만큼 활발하지 못한것 같아서 거의 반성의 기록을 남긴다.

처음 1일 1commit을 목표로 했을 때에는 공부도 흥미롭고, 일도 여유가 있었기 때문에 공부에 투자하는 시간도 많았는데 그러지 못하는게 너무 불편한 상황이 되었다.

이번주만 지나면 그래도 여유를 찾을 수 있을것 같으니 좀 더 공부하고 목표를 채워가는 일상을 기록 할 수 있도록 노력할 것이다.

[Programmers] 1835.단체사진 찍기

알고리즘 문제 풀이

해당 문제는 순열로 카카오 친구들의 순서를 나열하고 조건을 걸어 풀어보았습니다.

링크 : image


1. 문제

문제 보기

image

가을을 맞아 카카오프렌즈는 단체로 소풍을 떠났다. 즐거운 시간을 보내고 마지막에 단체사진을 찍기 위해 카메라 앞에 일렬로 나란히 섰다. 그런데 각자가 원하는 배치가 모두 달라 어떤 순서로 설지 정하는데 시간이 오래 걸렸다. 네오는 프로도와 나란히 서기를 원했고, 튜브가 뿜은 불을 맞은 적이 있던 라이언은 튜브에게서 적어도 세 칸 이상 떨어져서 서기를 원했다. 사진을 찍고 나서 돌아오는 길에, 무지는 모두가 원하는 조건을 만족하면서도 다르게 서는 방법이 있지 않았을까 생각해보게 되었다. 각 프렌즈가 원하는 조건을 입력으로 받았을 때 모든 조건을 만족할 수 있도록 서는 경우의 수를 계산하는 프로그램을 작성해보자.

입력형식

입력은 조건의 개수를 나타내는 정수 n과 n개의 원소로 구성된 문자열 배열 data로 주어진다. data의 원소는 각 프렌즈가 원하는 조건이 N~F=0과 같은 형태의 문자열로 구성되어 있다. 제한조건은 아래와 같다.

  • 1 <= n <= 100
  • data의 원소는 다섯 글자로 구성된 문자열이다. 각 원소의 조건은 다음과 같다.
    • 첫 번째 글자와 세 번째 글자는 다음 8개 중 하나이다. {A, C, F, J, M, N, R, T} 각각 어피치, 콘, 프로도, 제이지, 무지, 네오, 라이언, 튜브를 의미한다. 첫 번째 글자는 조건을 제시한 프렌즈, 세 번째 글자는 상대방이다. 첫 번째 글자와 세 번째 글자는 항상 다르다.
    • 두 번째 글자는 항상 ~이다.
    • 네 번째 글자는 다음 3개 중 하나이다. {=, <, >} 각각 같음, 미만, 초과를 의미한다.
    • 다섯 번째 글자는 0 이상 6 이하의 정수의 문자형이며, 조건에 제시되는 간격을 의미한다. 이때 간격은 두 프렌즈 사이에 있는 다른 프렌즈의 수이다.

출력(Output)

모든 조건을 만족하는 경우의 수를 리턴한다.

예제입출력

ndataanswer
2[“N~F=0”, “R~T>2”]3648
2[“M~C<2”, “C~M>1”]0

예제에대한 설명

첫 번째 예제는 문제에 설명된 바와 같이, 네오는 프로도와의 간격이 0이기를 원하고 라이언은 튜브와의 간격이 2보다 크기를 원하는 상황이다.

두 번째 예제는 무지가 콘과의 간격이 2보다 작기를 원하고, 반대로 콘은 무지와의 간격이 1보다 크기를 원하는 상황이다. 이는 동시에 만족할 수 없는 조건이므로 경우의 수는 0이다.


2. 문제 풀이

카카오 친구들의 일렬로 나열되는 순열의 경우를 우선적으로 구해보았다.

순열 방식을 이용해서 우선적으로 카카오 친구들의 순서를 나열한다.

nPr을 아래와 같은 방식으로 정의할 수 있다.

public void permutation(char[] arr, int depth, int n, int r) {
    if(depth == r) {
        print(arr);
        return;
    }

    for(int i = depth; i < n; i++) {
        swap(arr, depth, i);
        permutation(arr, depth + 1, n, r);
        swap(arr, depth, i);
    }
}

이렇게 모든 카카오친구들의 순열을 구했다면, 입력된 data 배열의 방식대로 조건에 따라서 카운팅을 할 것인지 그냥 return 할 것인지를 구해준다.

이때, 주의하여야 할 것은 두 친구 사이의 조건을 나타낸 것 이므로 사이 숫자는 +1을 시켜주어서 비교해야 한다.

그럼 이것을 활용하여 코드로 표현해보자.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class GroupPhoto_level2 {
    // 어피치, 콘, 프로도, 제이지, 무지, 네오, 라이언, 튜브
    static char[] kakaoFriends = {'A', 'C', 'F', 'J', 'M', 'N', 'R', 'T'};
    static int totalcnt;

    public static int findFriend(char[] kakao, char pick) {
        for(int i = 0; i < kakao.length; i++) {
            if(kakao[i] == pick) return i;
        }
        return -1;
    } 

    public static void checkOrder(char[] kakao, String[] data) {
        int len = data.length;
        for(int i = 0; i < len; i++) {
            char[] tmp = data[i].toCharArray();
            int first = findFriend(kakao, tmp[0]);
            int second = findFriend(kakao, tmp[2]);
            int gap = tmp[4] - '0';
            if(tmp[3] == '=') {
                if(Math.abs(first - second) != gap + 1) return;
            } else if(tmp[3] == '<') {
                if(Math.abs(first - second) >= gap + 1) return;
            } else if(tmp[3] == '>') {
                if(Math.abs(first - second) <= gap + 1) return;
            }
        }

        totalcnt++;
    }

    public static void swap(char[] data, int depth, int i) {
        char tmp = data[depth];
        data[depth] = data[i];
        data[i] = tmp;
    }

    public static void permutation(char[] arr, int depth, int n, int r, String[] data) {
        if(depth == r) {
            checkOrder(arr, data);
            return;
        }

        for(int i = depth; i < n; i++) {
            swap(arr, depth, i);
            permutation(arr, depth + 1, n, r, data);
            swap(arr, depth, i);
        }
    }
    public static int solution(int n, String[] data) {
        totalcnt = 0;
        permutation(kakaoFriends, 0, 8, 8, data);
        return totalcnt;
    }

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());

        int N = Integer.parseInt(st.nextToken());
        String[] data = new String[N];
        st = new StringTokenizer(br.readLine());
        for(int i = 0; i < N; i++) {
            data[i] = st.nextToken();
        }

        System.out.println(solution(N, data));
    }
}

[BOJ] 20182.골목 대장 호석 - 효율성1

백준 알고리즘 문제 풀이

해당 문제는 다익스트라 방식으로 풀어보았습니다.

링크 : image


1. 문제

문제 보기
시간 제한메모리 제한
3 초512 MB

소싯적 호석이는 골목 대장의 삶을 살았다. 호석이가 살던 마을은 N 개의 교차로와 M 개의 골목이 있었다. 교차로의 번호는 1번부터 N 번까지로 표현한다. 골목은 서로 다른 두 교차로를 양방향으로 이어주며 임의의 두 교차로를 잇는 골목은 최대 한 개만 존재한다. 분신술을 쓰는 호석이는 모든 골목에 자신의 분신을 두었고, 골목마다 통과하는 사람에게 수금할 것이다. 수금하는 요금은 골목마다 다를 수 있다.

당신은 A 번 교차로에서 B 번 교차로까지 C 원을 가지고 가려고 한다. 호석이의 횡포를 보며 짜증은 나지만, 분신술을 이겨낼 방법이 없어서 돈을 내고 가려고 한다. 하지만 이왕 지나갈 거면, 최소한의 수치심을 받고 싶다. 당신이 받는 수치심은 경로 상에서 가장 많이 낸 돈에 비례하기 때문에, 결국 갈 수 있는 다양한 방법들 중에서 최소한의 수치심을 받으려고 한다. 즉, 한 골목에서 내야 하는 최대 요금을 최소화하는 것이다.

image

예를 들어, 위의 그림과 같이 5개의 교차로와 5개의 골목이 있으며, 당신이 1번 교차로에서 3번 교차로로 가고 싶은 상황이라고 하자. 만약 10원을 들고 출발한다면 2가지 경로로 갈 수 있다. 1번 -> 2번 -> 3번 교차로로 이동하게 되면 총 10원이 필요하고 이 과정에서 최대 수금액을 5원이었고, 1번 -> 4번 -> 5번 -> 3번 교차로로 이동하게 되면 총 8원이 필요하며 최대 수금액은 6원이 된다. 최소한의 수치심을 얻는 경로는 최대 수금액이 5인 경로이다. 하지만 만약 8원밖에 없다면, 전자의 경로는 갈 수 없기 때문에 최대 수금액이 6원인 경로로 가야 하는 것이 최선이다.

당신은 앞선 예제를 통해서, 수치심을 줄이고 싶을 수록 같거나 더 많은 돈이 필요하고, 수치심을 더 받는 것을 감수하면 같거나 더 적은 돈이 필요하게 된다는 것을 알게 되었다. 마을의 지도와 골목마다 존재하는 호석이가 수금하는 금액을 안다면, 당신이 한 골목에서 내야하는 최대 요금의 최솟값을 계산하자. 만약 지금 가진 돈으로는 절대로 목표 지점을 갈 수 없다면 -1 을 출력하라.

입력(Input)

첫 줄에 교차로 개수 N, 골목 개수 M, 시작 교차로 번호 A, 도착 교차로 번호 B, 가진 돈 C 가 공백으로 구분되어 주어진다. 이어서 M 개의 줄에 걸쳐서 각 골목이 잇는 교차로 2개의 번호와, 골목의 수금액이 공백으로 구분되어 주어진다. 같은 교차로를 잇는 골목은 최대 한 번만 주어지며, 골목은 양방향이다.

출력(Output)

호석이가 지키고 있는 골목들을 통해서 시작 교차로에서 도착 교차로까지 C 원 이하로 가는 경로들 중에, 지나는 골목의 요금의 최댓값의 최솟값을 출력하라. 만약 갈 수 없다면 -1을 출력한다.

제한

  • 1 ≤ N ≤ 100,000
  • 1 ≤ M ≤ 500,000
  • 1 ≤ C ≤ 2,000,000
  • 1 ≤ 골목 별 수금액 ≤ 20
  • 1 ≤ A, B ≤ N, A ≠ B
  • 골목이 잇는 교차로의 번호는 서로 다르다.

2. 문제 풀이

이 문제는 다익스트라를 활용하여 풀어보았다.

오늘은 포스트를 작성 할 수 없어서 추후 업데이트 하겠다.

그럼 이것을 활용하여 코드로 표현해보자.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.PriorityQueue;
import java.util.StringTokenizer;

public class AlleyBossHoseok_20182 {
    static int N, M, A, B, C;
    static List<List<Pay>> map;
    static int min[];
    static int solution() {
        int answer = 0;

        PriorityQueue<Pay> pq = new PriorityQueue<>(new Comparator<Pay>(){
            @Override
            public int compare(Pay o1, Pay o2) {
                if(o1.p == o2.p) return o1.n - o2.n;
                else return o1.p - o2.p;
            }
        });

        boolean[] visited = new boolean[N + 1];

        pq.add(new Pay(A, 0, Integer.MIN_VALUE));

        while(!pq.isEmpty()) {
            Pay cur = pq.poll();
            if(visited[cur.n] || cur.n == B) continue;
            
            visited[cur.n] = true;

            for(Pay next : map.get(cur.n)) {
                int totalPay = cur.p + next.n;
                if(visited[next.n] || totalPay > C) continue;

                int curMax = cur.max;
                if(curMax < next.p) {
                    curMax = next.p;
                }
                if(min[next.n] > curMax) min[next.n] = curMax;
                pq.add(new Pay(next.n, totalPay, curMax)); 
            }
        }
        
        answer = min[B] != Integer.MAX_VALUE ? min[B] : -1;
        return answer;
    }
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());

        N = Integer.parseInt(st.nextToken());
        M = Integer.parseInt(st.nextToken());
        A = Integer.parseInt(st.nextToken());
        B = Integer.parseInt(st.nextToken());
        C = Integer.parseInt(st.nextToken());

        map = new ArrayList<>();
        min = new int[N + 1];
        Arrays.fill(min, Integer.MIN_VALUE);

        for(int i = 0; i <= N; i++) map.add(new ArrayList<>());
        for(int i = 0; i < M; i++) {
            if(!st.hasMoreTokens()) st = new StringTokenizer(br.readLine());
            int one = Integer.parseInt(st.nextToken());
            int two = Integer.parseInt(st.nextToken());
            int cost = Integer.parseInt(st.nextToken());

            map.get(one).add(new Pay(two, cost));
            map.get(two).add(new Pay(one, cost));
        }

        System.out.println(solution());
    }
}

class Pay {
    int n;
    int p;
    int max;

    public Pay(int n, int p) {
        this.n = n;
        this.p = p;
    }

    public Pay(int n, int p, int max) {
        this.n = n;
        this.p = p;
        this.max = max;
    }
}

[BOJ] 21610.마법사 상어와 비바라기 - 삼성기출문제 (from.백준알고리즘)

삼성 sw 알고리즘 문제 풀이

해당 문제는 시뮬레이션 문제로 단계별로 수행하는 함수를 이용하여 풀 수 있는 문제입니다.

링크 : image


1. 문제

문제 보기

21610. 마법사 상어와 비바라기

시간 제한메모리 제한
1 초 (추가 시간 없음)1024 MB

마법사 상어는 파이어볼, 토네이도, 파이어스톰, 물복사버그 마법을 할 수 있다. 오늘 새로 배운 마법은 비바라기이다. 비바라기를 시전하면 하늘에 비구름을 만들 수 있다. 오늘은 비바라기를 크기가 N×N인 격자에서 연습하려고 한다. 격자의 각 칸에는 바구니가 하나 있고, 바구니는 칸 전체를 차지한다. 바구니에 저장할 수 있는 물의 양에는 제한이 없다. (r, c)는 격자의 r행 c열에 있는 바구니를 의미하고, A[r][c]는 (r, c)에 있는 바구니에 저장되어 있는 물의 양을 의미한다.

격자의 가장 왼쪽 윗 칸은 (1, 1)이고, 가장 오른쪽 아랫 칸은 (N, N)이다. 마법사 상어는 연습을 위해 1번 행과 N번 행을 연결했고, 1번 열과 N번 열도 연결했다. 즉, N번 행의 아래에는 1번 행이, 1번 행의 위에는 N번 행이 있고, 1번 열의 왼쪽에는 N번 열이, N번 열의 오른쪽에는 1번 열이 있다.

비바라기를 시전하면 (N, 1), (N, 2), (N-1, 1), (N-1, 2)에 비구름이 생긴다. 구름은 칸 전체를 차지한다. 이제 구름에 이동을 M번 명령하려고 한다. i번째 이동 명령은 방향 di과 거리 si로 이루어져 있다. 방향은 총 8개의 방향이 있으며, 8개의 정수로 표현한다. 1부터 순서대로 ←, ↖, ↑, ↗, →, ↘, ↓, ↙ 이다. 이동을 명령하면 다음이 순서대로 진행된다.

  1. 모든 구름이 di 방향으로 si칸 이동한다.
  2. 각 구름에서 비가 내려 구름이 있는 칸의 바구니에 저장된 물의 양이 1 증가한다.
  3. 구름이 모두 사라진다.
  4. 2에서 물이 증가한 칸 (r, c)에 물복사버그 마법을 시전한다. 물복사버그 마법을 사용하면, 대각선 방향으로 거리가 1인 칸에 물이 있는 바구니의 수만큼 (r, c)에 있는 바구니의 물이 양이 증가한다.
    • 이때는 이동과 다르게 경계를 넘어가는 칸은 대각선 방향으로 거리가 1인 칸이 아니다.
    • 예를 들어, (N, 2)에서 인접한 대각선 칸은 (N-1, 1), (N-1, 3)이고, (N, N)에서 인접한 대각선 칸은 (N-1, N-1)뿐이다.
  5. 바구니에 저장된 물의 양이 2 이상인 모든 칸에 구름이 생기고, 물의 양이 2 줄어든다. 이때 구름이 생기는 칸은 3에서 구름이 사라진 칸이 아니어야 한다.

M번의 이동이 모두 끝난 후 바구니에 들어있는 물의 양의 합을 구해보자.

입력(Input)

첫째 줄에 N, M이 주어진다.

둘째 줄부터 N개의 줄에는 N개의 정수가 주어진다. r번째 행의 c번째 정수는 A[r][c]를 의미한다.

다음 M개의 줄에는 이동의 정보 di, si가 순서대로 한 줄에 하나씩 주어진다.

출력(Output)

첫째 줄에 M번의 이동이 모두 끝난 후 바구니에 들어있는 물의 양의 합을 출력한다.

제한

  • 2 ≤ N ≤ 50
  • 1 ≤ M ≤ 100
  • 0 ≤ A[r][c] ≤ 100
  • 1 ≤ di ≤ 8
  • 1 ≤ si ≤ 50

2. 문제 풀이

이 문제는 시뮬레이션 문제로, 나와있는 조건을 그대로 구현하기만 하면 된다.

먼저 구름의 위치를 가지고 있는 Queue를 만들어서 현재 위치 값을 가지고 있는 Cloud 객체를 만들고 큐에 넣는다.

그리고 이 구름을 이동시키는데, 여기서 중요한것은 양 옆에서 넘어갈 수 있도록 만들었다는것이다. 1에서 줄어들면 N으로 가는 방식을 2중, 3중 더 넘더라도 위치를 정확하게 찍어줘야 한다는것이다.
때문에 이동하는 si를 N의 나머지 부분으로 바꾸어서 이동하게 했다. 그리고 이동한 구름의 위치에 비가 내려 양동이의 양을 1 증가시킨다.
이 때 중요한것은 이렇게 증가된 곳을 체크해주고 이후에 새로 구름이 생성될 때, 비가 내린 곳은 생성되지 않도록 한다.

그리고 물 복사 마법이라고 (r,c) 부분의 대각선 4부분을 체크해서 물이 있으면 그 물의 수만큼 (r,c)에 위치한 양동이의 물이 증가된다.
이 액트는 격자의 범위를 넘어가면 카운트에서 제외시킨다.
이 때 주의해야 할 부분이 기존 격자에서 체크해서 증가시키면 1이었던 양동이가 2가되어서 다른 양동이에 영향을 줄 수 있기 때문에 격자를 복사해서 해당 위치의 양동이만 증가될 수 있도록 해야 한다.

이것을 코드로 표현하겠습니다.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.Queue;
import java.util.StringTokenizer;

public class MagicianShark_Windblows_21610 {
    static int N, M;
    static int[][] map;
    static boolean[][] rain;
    static int[][] dir = {  // 0은 빈칸, 좌, 좌상, 상, 우상, 우, 우하, 하, 좌하
        {0, 0, -1, -1, -1, 0, 1, 1, 1},
        {0, -1, -1, 0, 1, 1, 1, 0, -1}
    };
    static int[][] checkBucket = {
        {-1, -1, 1, 1},
        {-1, 1, -1, 1}
    };
    static int[][] command;
    static Queue<Cloud> cloud;
    public static void moveCloudnRain(int d, int s) {
        int size = cloud.size();

        rain = new boolean[N + 1][N + 1];
        while(size > 0) {
            Cloud tmp = cloud.poll();
            int nr = tmp.r + (dir[0][d] * (s % N));
            int nc = tmp.c + (dir[1][d] * (s % N));

            // 이어붙인 공간 넘어가기
            if(nr <= 0) nr = N + nr;
            if(nc <= 0) nc = N + nc;
            if(nr > N) nr = nr - N;
            if(nc > N) nc = nc - N;

            cloud.add(new Cloud(nr, nc));
            map[nr][nc]++;
            rain[nr][nc] = true;
            size--;
        }
    }
    public static void copywater() {
        int[][] copyMap = new int[N + 1][N + 1];
        for(int i = 1; i <= N; i++) {
            for(int j = 1; j <= N; j++) {
                copyMap[i][j] = map[i][j];
            }
        }

        while(!cloud.isEmpty()) {
            Cloud tmp = cloud.poll();
            rain[tmp.r][tmp.c] = true;

            // 물 복사 마법
            int cnt = 0;
            for(int i = 0; i < 4; i++) {
                int nr = tmp.r + checkBucket[0][i];
                int nc = tmp.c + checkBucket[1][i];
                if(nr <= 0 || nc <= 0 || nr > N || nc > N) continue;
                if(copyMap[nr][nc] > 0) cnt++; 
            }
            map[tmp.r][tmp.c] += cnt;
        }
    }
    public static void makeCloud() {
        for(int i = 1; i <= N; i++) {
            for(int j = 1; j <= N; j++) {
                if(!rain[i][j] && map[i][j] >= 2) {
                    cloud.add(new Cloud(i, j));
                    map[i][j] -= 2;
                }
            }
        }
    }
    public static void solution() {
        for(int i = 0; i < M; i++) {
            moveCloudnRain(command[i][0], command[i][1]);
            copywater();
            makeCloud();
        }
    }
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());

        N = Integer.parseInt(st.nextToken());
        M = Integer.parseInt(st.nextToken());

        map = new int[N + 1][N + 1];
        command = new int[M][2];

        cloud = new LinkedList<Cloud>();

        cloud.add(new Cloud(N, 1));
        cloud.add(new Cloud(N, 2));
        cloud.add(new Cloud(N - 1, 1));
        cloud.add(new Cloud(N - 1, 2));
        
        for(int i = 1; i <= N; i++) {
            if(!st.hasMoreTokens()) st = new StringTokenizer(br.readLine());
            for(int j = 1; j <= N; j++) {
                map[i][j] = Integer.parseInt(st.nextToken());
            }
        }

        for(int i = 0; i < M; i++) {
            if(!st.hasMoreTokens()) st = new StringTokenizer(br.readLine());
            command[i][0] = Integer.parseInt(st.nextToken());
            command[i][1] = Integer.parseInt(st.nextToken());
        }

        solution();

        int result = 0;
        for(int i = 1; i <= N; i++) {
            for(int j = 1; j <= N; j++) {
                result += map[i][j];
            }
        }

        System.out.println(result);
    }
}

class Cloud {
    int r, c;   // 위치 정보 초기 (N, 1), (N, 2), (N-1, 1), (N-1, 2)
    public Cloud(int r, int c) {
        this.r = r;
        this.c = c;
    }
}

코딩 테스트 기록

일상 기록

오늘의집 개발자 대규모 채용 코딩테스트

오늘은 오늘의집이라는 유니콘 기업에 프론트엔드 개발자 포지션으로 코딩테스트를 했다.

문제는 3문제 180분이었고, 나는 평소대로 JAVA로 치뤘다.

3문제 모두 문자열과 관련된 알고리즘을 사용해 푸는 문제였고, 1번 문제는 단순히 문자열에서 다음에 나오는 문자를 보고 방향전환 및 직진 거리에 대한 명령을 알려주는 프로그램이라 단순 구현력을 묻는 문제였다.

2번 문제는 반복 문자열을 제거하는 알고리즘 문제로, 이 문제와 유사한 문제를 이전에 풀었었는데.. 이게 참 내 생각처럼 쉽게 해결되지 않아서 이 문제에만 1시간 반 가까이 소비한 것 같다.

3번 문제는 문자열에 특정 변수를 두고 그것을 치환하는 문제였는데, HashSet을 이용해서 반복 유무 검사와 무한 반복인지를 체크하도록 하여 해결하였다.

지금까지 알고리즘을 이렇게 꾸준히 한 경험이 많지 않아서인지 생각보다 구현에는 어려움이 없었고, 평소에 얼마나 하느냐가 크게 다가왔었다. (심지어 전날 새벽까지 놀고 집중해서 할 수 있었다는게 놀라울 정도)

목표인 IT 대장급이라고 불리는 기업의 코테 올솔할 수 있는 실력을 갖출때까지 멈추지 않고 계속 할 것이다.

Pagination