반응형

 

전공 수업에서 노드와 자바스크립트를 이용해 Express와 연동하여 웹 프로그램을 만든 적이 있다.

그땐 자바스크립트랑 노드의 개념을 잘 모르고 무작정 따라하기만 했다.

 

깃허브를 정리하던 중 수업 때 만든 프로그램을 보고, 갑자기 Node.js가 언어인지, Javascript가 언어인지 헷갈리기 시작했다.

 

자바스크립트를 프론트엔드 언어, 노드를 백엔드 언어라고 믿고 있었는데..결론은 아니다! 

 

 

💡 Node.js란?

 

Node.js는 Chrome V8 Javascript 엔진으로 빌드 된 JavaScript 런타임이다.

 

한마디로, Node.js를 통해 다양한 자바스크립트 애플리케이션을 실행할 수 있고, 특히 서버를 실행하는 데 가장 많이 사용된다!

  • Node.js는 JavaScript를 서버에서도 사용할 수 있도록 만든 프로그램이다.
  • Node.js는 V8이라는 JavaScript 엔진 위에서 동작하는 자바스크립트 런타임(환경)이다.
  • Node.js는 서버사이트 스크립트 언어가 아니다. 프로그램(환경)이다.
  • Node.js는 웹서버와 같이 확장성 있는 네트워크 프로그램을 제작하기 위해 만들어졌다.

단순히 정적인 홈페이지, 웹에서 실행가능한 게임을 만들려면 javascript만으로도 가능하다.

하지만 웹 브라우저가 아닌 환경에서는 javascript는 동작하지 않는다.

따라서 웹 외에 독립적인 공간에서도 javascript만으로 프로그램이 실행되도록 하기 위해 Node.js라는 환경이 개발된 것이다!!

 

Node.js를 통해 정적인 홈페이지 뿐만 아니라 데이터가 변해가는 사이트(쇼핑몰, 티켓예매사이트, 블로그 등)를 만들 수 있으며, 여러 개발자가 만든 프로그램과 게임을 웹상에서 구동시켜 안드로이드 폰, 아이폰, 윈도우 pC 등 플랫폼의 제약에서도 벗어날 수 있게되었다.

 

물론 단순히 웹에서 실행 가능한 게임을 만들려면 JavaScript 만으로도 가능하지만 좀 더 진화 된 프로그램으로 실시간 온라인 채팅, 실시간 온라인 게임 등 실시간 기능을 넣거나, 로그인 기능을 넣어 유저를 관리하고 점수를 관리하는 데이터베이스 기능을 Node.js를 통해 만들 수가 있다.

 

💡 Node.js를 사용하는 이유

  • Node.js를 사용하려면 먼저 JavaScript를 배워야한다.
  • Node.js는 JavaScript를 사용하기 위해  만들어진 것이기 때문이다.
  • JavaScript는 C/C++, Java 와 같은 프로그래밍 언어이다.
  • 하지만 이름에서 알 수 있듯 JavaScript는 독립적인 언어가 아닌 스크립트 언어이다.
  • 스크립트 언어는 특정한 프로그램 안에서 동작하는 프로그램이기 때문에 웹 브라우저 프로그램 안에서만 동작을 한다.
  • 즉, 웹 브라우저(크롬, 사파리, 익스플로러, 파이어폭스 등)가 없으면 사용할 수 없는 프로그램이다.
  • 여기서 Node.js가 나오는 이유가 된다.
  • 즉, JavaScript 를 웹 브라우저에서 독립시킨 것으로 Node.js를 설치하게 되면 터미널프로그램(윈도우의 cmd, 맥의 terminal 등)에서 Node.js를 입력하여 브라우저 없이 바로 실행할 수 있다.
  • 하지만 JavaScript에서 분리된 언어이기 때문에 문법은 같다.
  • 이렇게 Node.js를 이용하여 웹 브라우저와 무관한 프로그램을 만들 수 있게 되었다.
  • 중요한 것은 Node.js를 이용하여 서버를 만들 수 있다는 것이다.
  • 중요한 이유는 이전까지 Server-Client 웹사이트를 만들 때 웹에서 표시되는 부분은 JavaScript 를 사용하여 만들어야만 했으며, 서버는 Reby, Java 등 다른 언어를 써서 만들었어야 했는데 마침내 한 가지 언어로 전체 웹 페이지를 만들 수 있게 된 것이다.

 

✅ 자바스크립트 런타임

  • 런타임이란 특정 언어로 만든 프로그램을 실행할 수 있는 환경이다.
  • 따라서 노드는 자바스크립트 프로그램을 컴퓨터에서 실행할 수 있게 하는 자바스크립트 실행기!!

 

⭐ 결론

Node.js는 Javascript 만으로도 웹 브라우저에서 독립된 환경에서 프로그램을 실행가능하게 해주는 환경(프로그램)이다.

즉, Node.js=환경, Javascript=언어

Javascript를 사용하기 때문에 JSON형식과 쉽게 호환된다.

Node.js의 프레임워크가 Express이다.

 

 

 

한 블로그 글을 통해 두 개념을 이해하는데 많은 도움이 되었다.

 

https://hanamon.kr/nodejs-%ea%b0%9c%eb%85%90-%ec%9d%b4%ed%95%b4%ed%95%98%ea%b8%b0/#respond

 

Node.js 노드 개념 이해하기 자바스크립트 JavaScript 런타임 이벤트

Node.js 노드 개념 이해하기 JavaScript 런타임 - 노드는 다양한 자바스크립트 애플리케이션을 실행할 수 있으며, 서버를 실행하는데 제일 많이 사용된다. 이벤트 기반 이벤트 루프 논블로킹 I/O 싱글

hanamon.kr

 

반응형

'Web Application' 카테고리의 다른 글

REST API  (0) 2023.12.29
HTTP status code (HTTP 상태 코드)  (0) 2023.12.28
HTTP Request Methods  (0) 2023.12.23
쿠키(cookie)와 세션(session)의 차이  (1) 2023.12.22
브라우저 동작 방법  (0) 2023.12.20
반응형

이 문제는 배열의 유효범위를 이해하고, 입력으로 받은 문자열에서 한 글자씩 접근하여 장애물(X)이면 해당 이동을 진행하지 않고, 장애물이 아니면 이동을 진행하도록 하는 쉬운 문제였다.

 

로직을 이해하고 짜는데에는 금방이었는데, 3가지 케이스를 제외하고 모두 실패가 떠서 왜 그런가.. 테스트 케이스 결과를 보니, 처음 start 위치를 찾고 난 후부터는 전혀 이동하지 못하고 그대로 반환되는 것을 확인할 수 있었다.

 

원인은 너무 단순한 거였는데, string에서 이동 칸 수를 char로 읽어와서 int로 변경하는 과정에, ASCII 형식을 고려하지 않은 채로 static_case를 씌워 단순히 <int>로 형변환 하려고 해서 정확한 숫자값을 읽어오지 못한 것이었따...

 

char타입을 레퍼런스로 가져와서 const char 포인터에 저장해 준 후 char->int로 바꾸어주는 atoi함수를 통해 쉽게 해결하였다!!

 

 

const char *jump_char = &routes[i][2];
		int jump = atoi(jump_char);

 

 

전체 코드는 아래와 같다.

 

#include <string>
#include <vector>

using namespace std;

vector<int> solution(vector<string> park, vector<string> routes) {
	vector<int> answer;

	int start_row = 0;
	int start_col = 0;

	bool isbreak = false;

	for (int i = 0; i < park.size(); i++) {//행길이
		for (int j = 0; j < park[i].size(); j++) {//열길이
			if (park[i][j] == 'S') {
				start_row = i;
				start_col = j;
				isbreak = true;
				break;
			}
		}
		if (isbreak) break;
	}

	for (int i = 0; i < routes.size(); i++) {
		const char *jump_char = &routes[i][2];
		int jump = atoi(jump_char);//char를 읽어와 숫자로 변환하는 과정에서 오류가 발생했음. 이렇게 수정하니 해결됨
		switch (routes[i][0])
		{
		case 'N':
			if (start_row - jump >= 0){
				bool flag = false;
				for (int j = 1; j <= jump; j++) {
					if (park[start_row - j][start_col] == 'X') {
						flag = true;
						break;
					}
				}
				if (!flag) {
					start_row -= jump;
				}
			}
			break;
			
		case 'S':
			if (start_row + jump < park.size()) {
				bool flag = false;
				for (int j = 1; j <= jump; j++) {
					if (park[start_row + j][start_col] == 'X') {
						flag = true;
						break;
					}
				}
				if (!flag) {
					start_row += jump;
				}
			}
			break;

		case 'W':
			if (start_col - jump >= 0) {
				bool flag = false;
				for (int j = 1; j <= jump; j++) {
					if (park[start_row][start_col-j] == 'X') {
						flag = true;
						break;
					}
				}
				if (!flag) {
					start_col -= jump;
				}
			}
			break;

		case 'E':
			if (start_col + jump < park[0].size()) {
				bool flag = false;
				for (int j = 1; j <= jump; j++) {
					if (park[start_row][start_col + j] == 'X') {
						flag = true;
						break;
					}
				}
				if (!flag) {
					start_col += jump;
				}
			}
			break;

		default:
			break;
		}
	}

	answer.push_back(start_row);
	answer.push_back(start_col);

	return answer;
}

 

 

반응형
반응형

앞의 달리기 경주를 풀고 난 뒤 이 문제를 보니, 어떻게 풀어야 할지 바로 감이 잡혔다.

이 문제 역시 map을 사용하여 해결할 수 있다.

 

string과 int가 짝을 이루고, 주어진 string을 통해 탐색하는 과정이 필요하다면 map을 사용할 것!

 

풀이는 아래와 같다.

 

#include <string>
#include <vector>
#include <map>

using namespace std;

vector<int> solution(vector<string> name, vector<int> yearning, vector<vector<string>> photo) {
    vector<int> answer;
    
    
	map <string, int> m1;

	for (int i = 0; i < name.size(); i++) {
		m1[name[i]] = yearning[i];
	}

	for (int i = 0; i < photo.size(); i++) {
		int cnt = 0;
		for (int j = 0; j < photo[i].size(); j++) {
			cnt += m1[photo[i][j]];
		}
		answer.push_back(cnt);
	}
    
    return answer;
}

string을 인덱스로 갖는 map을 하나 생성하여 주어진 값들을 대입해주었다.

그리고 photo를 한 줄 단위로 읽으며 값을 카운트 해주면 된다!

 

반응형
반응형

이 문제를 풀려고 했을 당시 처음엔 callings vector에 저장되어 있는 이름들을 players에서 찾아 내야 한다는 생각은 했으나, 이름과 등수를 바로바로 매치할 수 있는 방법이 떠오르지 않아서.. 단순히 while/for 반복문으로 하나씩 접근할 수 밖에 없었다.

그 코드는 아래와 같았고, 역시 몇개의 case에서 시간초과 문제가 발생하였다.

 

#include <string>
#include <vector>

using namespace std;

vector<string> solution(vector<string> players, vector<string> callings) {

	int sz = players.size();
	int cnt = 1;

	for (int i=0; i<callings.size(); i++){
		string temp = callings[i];

		if (i < callings.size() - 1 && temp == callings[i + 1]) {
			cnt++;
			continue;
		}

		for (int j = 0; j < sz; j++) {
			if (players[j] == temp) {
				int target = j - cnt;
				while (target < j) {
					swap(players[j], players[j - 1]);
					j--;
				}
				cnt = 1;
				break;
			}
		}
	}
	return players;
}

시간 초과가 발생한 부분은 [players를 일일이 탐색하는 부분]+[vector의 위치를 swap하는 부분] 이다. 

vector도 제법 빠른 자료구조라 생각했는데, 자리를 바꾸는 과정은 역시 O(N)이다.

 

 

 

이 문제의 핵심은 map을 사용하는 것이었다!!!!

 

map을 총 두개 사용하여 해결하였는데, 하나는 등수(int)를 기준으로 이름을 저장한 것과, 하나는 이름(string)을 기준으로 등수를 저장한 것이다.

이렇게 하면 반복문을 돌면서 callings에 있는 이름을 일일이 찾지 않고, 이름 자체를 인덱스로써 활용하여 현재 등수를 알아 낼 수 있다!!

 

해결한 코드는 아래와 같다.

#include <string>
#include <vector>
#include <map>

using namespace std;

vector<string> solution(vector<string> players, vector<string> callings) {
	//답 : vector라는 자료구조를 사용하는 것보다 map으로 정렬과정을 거친 후 vector에는 최종 결과 삽입만 하기.

	vector<string> answer;

	int sz = players.size();
	map <int, string> m1;//등수 별 이름 기억해두기
	map <string, int> m2;//이름 별 등수 기억해두기

	for (int i = 0; i<players.size(); i++) {
		m1[i] = players[i];
		m2[players[i]] = i;
	}

	for (int i = 0; i < callings.size(); i++) {
		string temp = callings[i];
		int tempIdx = m2[temp];
		m1[tempIdx] = m1[tempIdx - 1];//swap과정
		m1[tempIdx - 1] = temp;
		m2[temp] = tempIdx - 1;
		m2[m1[tempIdx]] = tempIdx;

	}

	for (auto temp : m1) {
		answer.push_back(temp.second);//m1의 두번째 요소(이름)들을 넣는다.
	}

	return answer;
}

그 결과 map을 활용해서 탐색 시간을 확실히 줄일 수 있었다!!

 

반환하고자 하는 것은 vector이므로, m1에 등수대로 적힌 이름들을 answer에 push_back하였고 이를 반환하도록 하였다.

 

 

반응형
반응형

1. 'strcpy()' vs '=' / 'assign()' 

 

c++로 문제를 해결하던 중 string 문자열을 복사하는 코드에서 'strcpy()'를 사용해서 문자열을 대입하려고 했더니, strcpy의 인자인 const char* 타입은 string과 어긋나면서 오류가 발생하였다!

 

더보기

strcpy 함수는 C 스타일의 문자열을 복사하는 함수로서, 다음과 같은 형태를 가집니다

: char* strcpy(char* destination, const char* source). 

 

c++에서 string문자열을 복사/대입하려면 std::string멤버 함수인 assign() 또는 = 연산자를 사용하면 해결된다!

확실히 c보다 c++은 문자열에서도 제약을 덜 받는 느낌이다,,

 

2. 'strcmp()' vs '=='

 

strcmp역시 c언어 스타일에서 사용하는 방식이다. 위에서와 동일한 오류가 발생한다.

c++에서는 단순히 ==을 통해 문자열을 비교할 수 있다!

 

3.vector의 맨 앞 원소를 삭제할 때

 

vector.erase(vector.front())

 

4.vector의 원하는 위치에 원소를 삽입할 때

 

vector.insert(vector.begin()+i, temp);

 

인덱스 정보를 넣는 곳에 정수를 입력해서 오류가 발생하였다!

iterator를 인자로 받기 때문에, 위와같이 vector.begin()으로 인덱스 정보를 가져와야 한다.

i는 원하는 인덱스번호라고 생각할 수 있고, temp는 넣고자 하는 값이다.

 

5. auto 자료형 추론

auto는 변수 선언 시 자료형을 명시하지 않아도, 대입된 값을 통해서 컴파일러가 알아서 자료형을 추론한다.

코드가 간결해지고 유지보수에는 좋으나, 가독성이 떨어질 수 있음!

 

 

vector의 기본 쓰임에 대해 복습할 필요가 있음을 느낀 문제..

 

 

 

반응형
반응형

한 페이지에서 큰 단위로 조회 결과가 나오는 경우 한눈에 보기 어렵고 원하는 결과를 찾기도 어렵다.

이를 위해 일정한 단위만큼 나누어 페이지를 넘기도록 하는 기능을 구현하고자 한다.

 

회원 정보를 출력하는 메인 페이지에서 페이징 기능을 추가하도록 하였다.

 

스프링 프레임워크에서 제공하는 Pageable 인터페이스를 사용해보자.

 


Pageable 인터페이스?

 

Pageable 인터페이스는 페이지 번호, 페이지 크기, 정렬 조건 등을 설정할 수 있는 메서드를 제공한다. 이를 통해 검색 결과를 페이지 단위로 나누고 원하는 페이지를 가져오는 등의 작업을 수행할 수 있다.

 

  • 주요 메서드와 기능
  • getPageNumber(): 현재 페이지 번호를 반환합니다. 0부터 시작합니다.
  • getPageSize(): 페이지 크기(한 페이지에 포함되는 아이템 수)를 반환합니다.
  • getOffset(): 현재 페이지의 시작 인덱스(오프셋)를 반환합니다. 페이지 번호와 페이지 크기를 이용하여 계산됩니다.
  • getSort(): 정렬 조건을 반환합니다.
  • next(): 다음 페이지를 반환합니다.
  • previousOrFirst(): 이전 페이지를 반환합니다. 첫 번째 페이지인 경우 첫 번째 페이지를 반환합니다.
  • first(): 첫 번째 페이지를 반환합니다.
  • hasNext(): 다음 페이지가 있는지 여부를 확인합니다.
  • hasPrevious(): 이전 페이지가 있는지 여부를 확인합니다.

페이징 기능 구현을 위해 아래의 글을 참고하였다.

 

9. 스프링부트와 타임리프로 게시판 만들기 - 페이징

페이징 - 백엔드 개념 부분 페이징이란? 페이징이란 여러 게시물을 볼 때 일정 갯수 이상이 넘어가면 다음 페이지에 존재할 수 있게 하는 것을 의미합니다. 위와 같이 게시글이 일정 갯수가 넘어

velog.io

 

1. @PageableDefault으로 페이지 정보 view로 전달

환자 정보 리스트를 화면에 출력하는 main페이지가 요청될 때 페이지 정보를 view에 함께 전달해야 한다.

@GetMapping("/main") //환자 정보 리스트
public String main(Model model, HttpSession session, @PageableDefault(page = 0, size = 10, sort = "id", direction = Sort.Direction.ASC) Pageable pageable) {

    Page<PatientEntity> patientEntityList = memberService.findAll(session, pageable);
    int nowPage = patientEntityList.getPageable().getPageNumber()+1;
    int startPage = Math.max(nowPage-4, 1);
    int endPage = Math.min(startPage+9, patientEntityList.getTotalPages());

    model.addAttribute("nowPage", nowPage);
    model.addAttribute("startPage", startPage);
    model.addAttribute("endPage", endPage);
    model.addAttribute("patientList", patientEntityList);
    return "main2";
}
  • @PageableDefault(page = 0, size = 10, sort = "id", direction = Sort.Direction.ASC) Pageable pageable
    : Pageable 인터페이스에서 page는 0부터 시작한다. 한 페이지에 보일 레코드의 개수(size)를 10으로 설정하였다. sort는 정렬 기준으로, PatientEntity의 id 필드를 기준으로 정렬한다. direction은 정렬 방식으로 DESC(내림차순)와 ASC(오름차순)으로 설정할 수 있다. 이 초기값을 바탕으로 Pageable 인터페이스를 생성한다.
  • Page<PatientEntity> patientEntityList = memberService.findAll(session, pageable);
    : 페이지 기능으로 하기 전에는 List형태였으나, 이제 Pageable을 사용하기 때문에, Page로 가져올 것이다. 타입 역시 Entity타입으로 가져온다. 
    서비스의 findAll함수를 pageable을 추가한 것에 맞게 수정해야 한다!!!
  • int nowPage = patientEntityList.getPageable().getPageNumber()+1
    : 현재페이지에 대한 정보이다. pageable은 페이지가 0부터 시작하므로 +1을 해줌으로써 1페이지부터 시작하도록 한다.
  • int startPage = Math.max(nowPage-4, 1)
    : 표시될 시작페이지에 대한 정보이다. 현재 페이지 앞에 최대 4개까지 보이도록 하고, 4개보다 적다면 1페이지부터 표시되도록 한다.
  • int endPage = Math.min(startPage+9, patientEntityList.getTotalPages())
    :표시될 마지막페이지에 대한 정보이다. 시작페이지에 9를 더한 값의 페이지까지 보이도록 하여 총 10개만 보이도록 하였고, 최대 페이지를 넘지는 않도록 하였다.
  • 각 값들을 model의 attribute로 view에 전달한다.

 

2. 서비스의 findAll()함수에 Pageable 기능을 하도록 수정

public Page<PatientEntity> findAll(HttpSession session, Pageable pageable){
        Long Id = (Long) session.getAttribute("loginId");
//        List<PatientEntity> patientEntityList = patientRepository.findByMemberEntity_Id(Id);
//        List<PatientDTO> patientDTOList = new ArrayList<>();
//        for(PatientEntity patientEntity: patientEntityList){//여러개의 entity를 여러개의 dto로 하나씩 담기위해
//            patientDTOList.add(PatientDTO.toPatientDTO(patientEntity));
//        }
        return patientRepository.findByMemberEntity_Id(Id, pageable);
    }

서비스 함수의 반환 타입을 Page 형태로 PatientEntity들이 전달되도록 수정하였다. 그리고 Pageable 인터페이스를 인자로 받는다.

주석처리된 부분은 페이징 처리 전에 List형태로 받았던 코드이다.

레포지토리의 findByMemberEntity_Id함수를 수정하여 그 반환형태 자체를 반환하도록 하였다.

 

 

3. 레포지토리 findByMemberEntity_Id함수 수정하기

Page<PatientEntity> findByMemberEntity_Id(Long id, Pageable pageable);

pageable 정보를 넘겨주면 조회결과 레코드를 10개씩 나누어 페이지로 알아서 반환해준다.

 

 

4.view에서 페이징 처리하기!

<div class="card-footer py-4">
  <nav aria-label="...">
    <ul class="pagination justify-content-end mb-0">
      <li class="page-item disabled">
        <a class="page-link" href="#" tabindex="-1">
          <i class="fas fa-angle-left"></i>
          <span class="sr-only">Previous</span>
        </a>
      </li>
      <th:block th:each="page:${#numbers.sequence(startPage,endPage)}">
        <li class="page-item" th:class="${page == nowPage} ? 'page-item active' : 'page-item'">
          <a class="page-link" th:if="${page != nowPage}" th:href="@{/main(page=${page-1})}" th:text="${page}"></a>
          <a class="page-link" th:if="${page == nowPage}" th:text="${page}" style="color:white; font-weight:bold;"></a>
        </li>
      </th:block>
      <li class="page-item">
        <a class="page-link" href="#">
          <i class="fas fa-angle-right"></i>
          <span class="sr-only">Next</span>
        </a>
      </li>
    </ul>
  </nav>
</div>
  • 첫번째 li는 이전 페이지로 이동하는 버튼을 구현한것이다
  • 두번째 li가 현재 페이지를 포함한 10개의 페이지 버튼을 보이도록 구현한것이다.
    th:each를 통해 model로 전달된 startPage와 endPage의 정보를 가지고, 각 페이지를 표시하도록 한다.
    각 페이지를 순회하면서 현재 활성화된 페이지와 같으면, page-link 스타일을 가지면서, 페이지 번호는 white색이며, 볼드체로 표시되도록 하였고, 활성화된 페이지가 아니면, page-link스타일을 가지면서 페이지 번호에 해당 페이지 링크를 걸어주었다.
  • 세번째 li가 다음 페이지로 이동하는 버튼을 구현한 것이다.

 

5. 구현결과  

페이지 버튼 성공!!

5페이지 까지는 1이 첫번째고, 10이 최대며, 그다음부터는 1씩 늘어나며 shift되는 것을 확인하였다!

잘 동작한다!!! 디자인도 잘 적용되었다!

반응형

+ Recent posts