2020/09/16 - [개발자/Java] - Java (자바) Controller, DB 파일 받아와서 만들기, C태그 사용, jsp 파일 내 자바 코드 에서 이어진다.

 

1. 객체 생성

객체 안에 DB 디렉토리랑 테이블 이름을 넣는다.

Memo.java

package com.kopo.memo;

public class Memo {
	public int idx;
	public String title;
	public String memo;
	public String created;
	public String updated;
	
	public final String DB_FILE_NAME = ".../memo2.db";
	public final String TABLE_NAME = "memo";
	
	public Memo() {
		
	}
	
	public Memo(String title, String memo) {
		this.title = title;
		this.memo = memo;
	}
	
	public Memo(int idx, String title, String memo) {
		this.idx = idx;
		this.title = title;
		this.memo = memo;
	}
	
	public String toHtmlString() {
		StringBuffer htmlString = new StringBuffer();
		htmlString.append("<tr>");
		htmlString.append("<td>" + this.idx + "</td>");
		htmlString.append("<td>" + this.title + "</td>");
		htmlString.append("<td>" + this.memo + "</td>");
		htmlString.append("<td>" + "<a href=u1?idx=" + this.idx + ">상세보기</a>" + "</td>");
		htmlString.append("</tr>");
		
		return htmlString.toString();
	}

	public int getIdx() {
		return idx;
	}

	public void setIdx(int idx) {
		this.idx = idx;
	}

	public String getTitle() {
		return title;
	}

	public void setTitle(String title) {
		this.title = title;
	}

	public String getMemo() {
		return memo;
	}

	public void setMemo(String memo) {
		this.memo = memo;
	}

	public String getCreated() {
		return created;
	}

	public void setCreated(String created) {
		this.created = created;
	}

	public String getUpdated() {
		return updated;
	}

	public void setUpdated(String updated) {
		this.updated = updated;
	}
	
}

 

People.java

package com.kopo.memo;

public class People {
	public int idx;
	public String name;
	public String phone;
	public String created;
	public String updated;
	
	public final String DB_FILE_NAME = ".../memo2.db";
	public final String TABLE_NAME = "people";

}

 

 

2. 그리고 HomeController의 일부를 CommonController로 옮겨보자

기존 HomeController

@RequestMapping(value = "/create", method = RequestMethod.GET)
public String create(Locale locale, Model model) {
	DB db = new DB<Memo>(".../memo.db", "memo");
	
	if (db.open()) {
		if (db.createTable(new Memo())) {
			model.addAttribute("message", "테이블이 생성되었습니다.");
		} else {
			model.addAttribute("message", "테이블 생성에 실패하였습니다.");
		}
		db.close();
	} else {
		model.addAttribute("message", "DB파일을 사용할 수 없습니다.");
	}
	
	return "message";
}

// insert
@RequestMapping(value = "/insert", method = RequestMethod.GET)
public String insert(Locale locale, Model model, HttpServletRequest request) {
	try {
		request.setCharacterEncoding("UTF-8");
	} catch (UnsupportedEncodingException e) {
		e.printStackTrace();
	}
	
	DB db = new DB<Memo>(".../memo.db", "memo");
	
	if (db.open()) {
		if (request.getParameter("title") != null
				&& request.getParameter("memo") != null
				&& db.insertData(new Memo(request.getParameter("title"), request.getParameter("memo")))) {				
			model.addAttribute("message", "새 데이터를 추가했습니다.");
		} else {
			model.addAttribute("message", "데이터 추가에 실패했습니다.");
		}
		db.close();
	} else {
		model.addAttribute("message", "DB파일을 사용할 수 없습니다.");
	}
	
	return "message";
}

@RequestMapping(value = "/update", method = RequestMethod.GET)
public String update(Locale locale, Model model, HttpServletRequest request) {
	try {
		request.setCharacterEncoding("UTF-8");
	} catch (UnsupportedEncodingException e) {
		e.printStackTrace();
	}

	DB db = new DB<Memo>(".../memo.db", "memo");
	
	if (db.open()) {
		if (request.getParameter("idx") != null && DB.isIntegerString(request.getParameter("idx"))
				&& request.getParameter("title") != null
				&& request.getParameter("memo") != null
				&& db.updateData(new Memo(Integer.parseInt(request.getParameter("idx"))
						, request.getParameter("title"), request.getParameter("memo")))) {				
			model.addAttribute("message", "데이터를 수정했습니다.");
		} else {
			model.addAttribute("message", "데이터 수정에 실패했습니다.");
		}
		db.close();
	} else {
		model.addAttribute("message", "DB파일을 사용할 수 없습니다.");
	}
	
	return "message";
}	

 

CommonController

DB db = new DB(new Memo());
DB db2 = new DB(new People());

// create table
@RequestMapping(value = "/create", method = RequestMethod.GET)
public String create(Locale locale, Model model) {		
	if (db.open()) {
		if (db.createTable()) {
			model.addAttribute("message", "테이블이 생성되었습니다.");
		} else {
			model.addAttribute("message", "테이블 생성에 실패하였습니다.");
		}
		db.close();
	} else {
		model.addAttribute("message", "DB파일을 사용할 수 없습니다.");
	}
	
	return "message";
}

// 이런식으로 DB를 2개 만들어 2개 이상의 테이블을 사용할 수 있다.
@RequestMapping(value = "/create2", method = RequestMethod.GET)
public String create2(Locale locale, Model model) {		
	if (db2.open()) {
		if (db2.createTable()) {
			model.addAttribute("message", "테이블이 생성되었습니다.");
		} else {
			model.addAttribute("message", "테이블 생성에 실패하였습니다.");
		}
		db2.close();
	} else {
		model.addAttribute("message", "DB파일을 사용할 수 없습니다.");
	}
	
	return "message";
}

// insert
@RequestMapping(value = "/insert", method = RequestMethod.GET)
public String insert(Locale locale, Model model, HttpServletRequest request) {
	try {
		request.setCharacterEncoding("UTF-8");
	} catch (UnsupportedEncodingException e) {
		e.printStackTrace();
	}
	
	if (db.open()) {
		if (db.insertData(request)) {				
			model.addAttribute("message", "새 데이터를 추가했습니다.");
		} else {
			model.addAttribute("message", "데이터 추가에 실패했습니다.");
		}
		db.close();
	} else {
		model.addAttribute("message", "DB파일을 사용할 수 없습니다.");
	}
	
	return "message";
}

//update
@RequestMapping(value = "/update", method = RequestMethod.GET)
public String update(Locale locale, Model model, HttpServletRequest request) {
	try {
		request.setCharacterEncoding("UTF-8");
	} catch (UnsupportedEncodingException e) {
		e.printStackTrace();
	}
	
	if (db.open()) {
		if (db.updateData(request)) {				
			model.addAttribute("message", "데이터를 수정했습니다.");
		} else {
			model.addAttribute("message", "데이터 수정에 실패했습니다.");
		}
		db.close();
	} else {
		model.addAttribute("message", "DB파일을 사용할 수 없습니다.");
	}
	
	return "message";
}

DB db = new DB(new Memo());
DB db2 = new DB(new People());

전역 변수로 빼놓고, DB 객체에 있는 DB 파일 디렉토리와 테이블 명을 사용할거다.

 

3. 이에 맞춰 DB 클래스 파일도 수정한다

기존 DB

// create table
public boolean createTable(T t) {
	Class<?> dataClass = t.getClass();
	Field[] dataClassFields = dataClass.getDeclaredFields();
	
	String fieldString = "";
	
	for (Field field: dataClassFields) {
		String fieldName = field.getName();
		String fieldType = field.getType().toString();
        
        if (!fieldString.isEmpty()) {
			fieldString = fieldString + ",";
		}
		fieldString = fieldString + fieldName;
		if (fieldName.matches("idx")) {
			fieldString = fieldString + " INTEGER PRIMARY KEY AUTOINCREMENT";
		} else if (fieldType.matches("(int|long|short)")) {
			fieldString = fieldString + " INTEGER";
		} else if (fieldType.matches("(float|double)")) {
			fieldString = fieldString + " REAL";
		} else if (fieldType.matches(".*String")) {
			fieldString = fieldString + " TEXT";
		}
	}
	
	String query = "CREATE TABLE " + this.tableName + " (" + fieldString + ")";
	try {
		Statement statement = this.connection.createStatement();
		statement.executeUpdate(query);
		statement.close();
		return true;
	} catch (SQLException e) {
		e.printStackTrace();
	}
	return false;
}

 

새 DB

// 파라미터 없는 생성자 추가
public DB() {
	
}

// dbfileName이랑 tableName을 HomeController가 new Memo()만 넣어서 보내면 Memo 객체 내에 입력된 정보를 사용하도록 변경.
public DB(T t) {
	this.t = t;

	Class<?> dataClass = t.getClass();
	Field[] dataClassFields = dataClass.getDeclaredFields();

	for (Field field : dataClassFields) {
		String fieldName = field.getName();
		String fieldType = field.getType().toString();
		try {
			if (fieldName.matches("DB_FILE_NAME")) {
			this.dbFileName = field.get(t).toString();
		} else if (fieldName.matches("TABLE_NAME")) {
				this.tableName = field.get(t).toString();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

// create table
public boolean createTable() {
	Class<?> dataClass = t.getClass();
	Field[] dataClassFields = dataClass.getDeclaredFields();
	
	String fieldString = "";
	String dbFileName = "";
	String tableName = "";
	
	for (Field field: dataClassFields) {
		String fieldName = field.getName();
		String fieldType = field.getType().toString();

		try {
			if (fieldName.matches("DB_FILE_NAME")) {
				dbFileName = field.get(t).toString();
				continue;
			} else if (fieldName.matches("TABLE_NAME")) {
				tableName = field.get(t).toString();
				continue;
			}
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
		
		if (!fieldString.isEmpty()) {
			fieldString = fieldString + ",";
		}			
		fieldString = fieldString + fieldName;
		if (fieldName.matches("idx")) {
			fieldString = fieldString + " INTEGER PRIMARY KEY AUTOINCREMENT";
		} else if (fieldType.matches("(int|long)")) {
			fieldString = fieldString + " INTEGER";
		} else if (fieldType.matches("(float|double)")) {
			fieldString = fieldString + " REAL";
		} else if (fieldType.matches(".*String")) {
			fieldString = fieldString + " TEXT";
		}
	}
	
	String query = "CREATE TABLE " + tableName + " (" + fieldString + ")";
	try {
		this.open(dbFileName);
		Statement statement = this.connection.createStatement();
		statement.executeUpdate(query);
		statement.close();
		this.close();
		return true;
	} catch (SQLException e) {
		e.printStackTrace();
		this.close();
	}
	return false;
}

생성자가 2개 추가되었고(객체에서 직접 DB 파일 디렉토리와 테이블 명을 읽어오기 위해서), 

각 쿼리 실행 메소드 안에

String dbFileName = "";
String tableName = "";

변수를 생성하고, DB파일과 테이블 정보를 추가하기 위해 for each문 안에 다음 로직이 추가되었다.

try {
	if (fieldName.matches("DB_FILE_NAME")) {
		dbFileName = field.get(t).toString();
		continue;
	} else if (fieldName.matches("TABLE_NAME")) {
		tableName = field.get(t).toString();
		continue;
	}
} catch (Exception e) {
	e.printStackTrace();
	return false;
}

 

insert와 update도 아래와 같이 수정해준다.

public boolean insertData(HttpServletRequest request) {
	Class<?> dataClass = t.getClass();
	Field[] dataClassFields = dataClass.getDeclaredFields();
	
	String fieldString = "";
	String valueString = "";
	
	ArrayList<Object> preparedValue = new ArrayList<Object>();
	
	for (Field field: dataClassFields) {
		String fieldName = field.getName();
		String fieldType = field.getType().toString();
		
		if (fieldName.matches("idx")) {
			continue;
		}
		
		// 입력된 데이터가 비어있고(null), fieldName이 created, update가 아니라면 다음 for문으로 넘어가라.
		// 입력된 데이터가 비어있지 않거나, 비어 있더라도 fieldName이 created 또는 updated면 아래 로직을 계속 실행하라.
		if (request.getParameter(fieldName) == null && !fieldName.matches("(created|updated)")) {
			continue;
		}
		// 기존에 HomeController에서 했던 숫자 데이터 체크를 request를 그대로 받아 이곳에서 한다.
		if (fieldType.matches("(int|long|short)") && !this.isIntegerString(request.getParameter(fieldName))) {
			return false;
		} else if (fieldType.matches("(float|double)") && !this.isFloatString(fieldName)) {
			return false;
		}
		
		// 2번째 for문 돌 때부터 ','를 추가
		if (!fieldString.isEmpty()) {
			fieldString = fieldString + ",";
		}
		if (!valueString.isEmpty()) {
			valueString = valueString + ",";
		}
		fieldString = fieldString + fieldName;
		valueString = valueString + "?";
		
		if (fieldName.matches("created")) {	// DB 컬럼명이 'created'일 때
			SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
			Date date = new Date(System.currentTimeMillis());	// java.util.Date;
			preparedValue.add(formatter.format(date));
		} else if (fieldName.matches("updated")) {	// DB 컬럼명이 'updated'일 때
			SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
			Date date = new Date(System.currentTimeMillis());	// java.util.Date;
			preparedValue.add(formatter.format(date));
		} else {
			preparedValue.add(request.getParameter(fieldName));
		}
	}
	
	String query = "INSERT INTO " + this.tableName + " (" + fieldString + ") VALUES(" + valueString + ")";
	try {
		this.open();
		PreparedStatement statement = this.connection.prepareStatement(query);
		for (int i = 0; i < preparedValue.size(); i++) {
			statement.setObject(i + 1, preparedValue.get(i));
		}
		statement.executeUpdate();
		statement.close();
		this.close();
		return true;
	} catch (SQLException e) {
		e.printStackTrace();
		this.close();
	}
	return false;
}


public boolean updateData(HttpServletRequest request) {
	Class<?> dataClass = t.getClass();
	Field[] dataClassFields = dataClass.getDeclaredFields();
	String setString = "";
	String whereString = "";
	
	ArrayList<Object> preparedValue = new ArrayList<Object>();
	
	for (Field field : dataClassFields) {
		String fieldName = field.getName();
		String fieldType = field.getType().toString();
		
		// 입력된 데이터가 비어있고(null), fieldName이 created, update가 아니라면 다음 for문으로 넘어가라.
					// 입력된 데이터가 비어있지 않거나, 비어 있더라도 fieldName이 updated면 아래 로직을 계속 실행하라.
		if (request.getParameter(fieldName) == null && !fieldName.matches("(updated)")) {
			continue;
		}
		// 기존에 HomeController에서 했던 숫자 데이터 체크를 request를 그대로 받아 이곳에서 한다.
		if (fieldType.matches("(int|long|short)") && !this.isIntegerString(request.getParameter(fieldName))) {
			return false;
		} else if (fieldType.matches("(float|double)") && !this.isFloatString(fieldName)) {
			return false;
		}
		
		if (!setString.isEmpty()) {
			setString = setString + ",";
		}
	
		try {
			if (fieldName.matches("idx")) {
				whereString = "idx=" + request.getParameter("idx");
				continue;
			} else if (fieldName.matches("updated")) {	// DB 컬럼명이 'updated'일 때
				SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
				Date date = new Date(System.currentTimeMillis());	// java.util.Date;
				preparedValue.add(formatter.format(date));
			} else {
				preparedValue.add(request.getParameter(fieldName));
			}
			setString = setString + fieldName + "=?";
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	String query = "UPDATE " + this.tableName + " SET " + setString + " WHERE " + whereString;
	try {
		this.open();
		PreparedStatement statement = this.connection.prepareStatement(query);
		for (int i = 0; i < preparedValue.size(); i++) {
			statement.setObject(i + 1, preparedValue.get(i));
		}
		statement.executeUpdate();
		statement.close();
		this.close();
		return true;
	} catch (SQLException e) {
		e.printStackTrace();
		this.close();
	}
	return false;
}

 

위 수정한 로직 중에서 

// 입력된 데이터가 비어있고(null), fieldName이 created, update가 아니라면 다음 for문으로 넘어가라.
// 입력된 데이터가 비어있지 않거나, 비어 있더라도 fieldName이 created 또는 updated면 아래 로직을 계속 실행하라.
if (request.getParameter(fieldName) == null && !fieldName.matches("(created|updated)")) {
	continue;
}

// update에서는 create는 아래 조건에서 빼도록 한다.
if (request.getParameter(fieldName) == null && !fieldName.matches("(updated)")) {
	continue;
}

// 기존에 HomeController에서 했던 숫자 데이터 체크를 request를 그대로 받아 이곳에서 한다.
if (fieldType.matches("(int|long|short)") && !this.isIntegerString(request.getParameter(fieldName))) {
	return false;
} else if (fieldType.matches("(float|double)") && !this.isFloatString(fieldName)) {
	return false;
}

created, updated 컬럼 데이터를 처리하기 위한 로직과, 기존에 HomeController에서 했던 숫자 컬럼에 들어온 데이터 값이 숫자형식이 맞는지 체크하는 로직을 이곳으로 옮긴 것에 주목한다.

만약 저기에 memo를 null값 허용으로 처리하게 된다면, title은 필수로 들어와야하지만 memo는 null값이어도 쿼리를 정상 수행하겠다는의미다.

 

insert 테스트 URL

http://localhost:8099/memo/insert?title=안녕하세요&memo=
http://localhost:8099/memo/insert?title=또만났군요&memo=새롭게 바뀌었습니다.
http://localhost:8099/memo/insert?title=&memo=null값 허용도 됩니다.

update 테스트 URL

http://localhost:8099/memo/update?title=수정이&memo=잘됩니다&idx=3

 

 

 

전체 코드

더보기

HomeController.java

package com.kopo.memo;

import java.io.UnsupportedEncodingException;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Locale;

import javax.servlet.http.HttpServletRequest;
import javax.swing.text.html.HTMLDocument.HTMLReader.IsindexAction;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

/**
 * Handles requests for the application home page.
 */
@Controller
public class HomeController {
	
	private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
	
	@RequestMapping(value = "/", method = RequestMethod.GET)
	public String home(Locale locale, Model model) {
		DB db = new DB<Memo>("/Users/saebyul/SqliteDB/0916memo.db", "memo");
		
		model.addAttribute("test", new Memo());	// jsp파일. 즉, HTML에서 객체를 사용하기 위해 test라는 변수에 담아 넘겨준다.
		
//		Memo memo = new Memo();
//	    memo.setIdx(10);
//	    System.out.println(memo.getIdx());
		
		if (db.open()) {
			ArrayList<Memo> list = db.selectData(new Memo());
			model.addAttribute("list", list);
			db.close();
		}
		return "home";
	}
	
//	@RequestMapping(value = "/create", method = RequestMethod.GET)
//	public String create(Locale locale, Model model) {
//		DB db = new DB<Memo>("/Users/saebyul/SqliteDB/0916memo.db", "memo");
//		
//		if (db.open()) {
//			if (db.createTable(new Memo())) {
//				model.addAttribute("message", "테이블이 생성되었습니다.");
//			} else {
//				model.addAttribute("message", "테이블 생성에 실패하였습니다.");
//			}
//			db.close();
//		} else {
//			model.addAttribute("message", "DB파일을 사용할 수 없습니다.");
//		}
//		
//		return "message";
//	}

	// insert를 위해 사용
	@RequestMapping(value = "/i1", method = RequestMethod.GET)
	public String i1(Locale locale, Model model) {
		return "i1";
	}
	// insert
//	@RequestMapping(value = "/insert", method = RequestMethod.GET)
//	public String insert(Locale locale, Model model, HttpServletRequest request) {
//		try {
//			request.setCharacterEncoding("UTF-8");
//		} catch (UnsupportedEncodingException e) {
//			e.printStackTrace();
//		}
//		
//		DB db = new DB<Memo>("/Users/saebyul/SqliteDB/0916memo.db", "memo");
//		
//		if (db.open()) {
//			if (request.getParameter("title") != null
//					&& request.getParameter("memo") != null
//					&& db.insertData(new Memo(request.getParameter("title"), request.getParameter("memo")))) {				
//				model.addAttribute("message", "새 데이터를 추가했습니다.");
//			} else {
//				model.addAttribute("message", "데이터 추가에 실패했습니다.");
//			}
//			db.close();
//		} else {
//			model.addAttribute("message", "DB파일을 사용할 수 없습니다.");
//		}
//		
//		return "message";
//	}
	
	// select
	@RequestMapping(value = "/list", method = RequestMethod.GET)
	public String select(Locale locale, Model model) {
		DB db = new DB<Memo>("/Users/saebyul/SqliteDB/0916memo.db", "memo");
		
		if (db.open()) {
			String htmlString = db.selectStringData(new Memo());
			model.addAttribute("list", htmlString);
			db.close();
		}
		
		return "list";
	}
	
	// update를 위해 사용
	@RequestMapping(value = "/u1", method = RequestMethod.GET)
	public String u1(Locale locale, Model model, HttpServletRequest request) {
		try {
			request.setCharacterEncoding("UTF-8");
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		
		DB db = new DB<Memo>("/Users/saebyul/SqliteDB/0916memo.db", "memo");
		
		if (db.open()) {
			if (request.getParameter("idx") != null && DB.isIntegerString(request.getParameter("idx"))) {
				Memo memo = (Memo) db.selectData(Integer.parseInt(request.getParameter("idx")), new Memo());
				model.addAttribute("idx", memo.idx);
				model.addAttribute("title", memo.title);
				model.addAttribute("memo", memo.memo);
			} else {
				model.addAttribute("message", "데이터 추가에 실패했습니다.");
			}
			db.close();
		} else {
			model.addAttribute("message", "DB파일을 사용할 수 없습니다.");
		}
		return "u1";
	}
	
	//update
//	@RequestMapping(value = "/update", method = RequestMethod.GET)
//	public String update(Locale locale, Model model, HttpServletRequest request) {
//		try {
//			request.setCharacterEncoding("UTF-8");
//		} catch (UnsupportedEncodingException e) {
//			e.printStackTrace();
//		}
//		
////			if (request.getParameter("idx") == null) {
////				model.addAttribute("message", "입력 데이터가 잘못 되었습니다.");
////				return "message";
////			}
//		
//		DB db = new DB<Memo>("/Users/saebyul/SqliteDB/0916memo.db", "memo");
//		
//		if (db.open()) {
//			if (request.getParameter("idx") != null && DB.isIntegerString(request.getParameter("idx"))
//					&& request.getParameter("title") != null
//					&& request.getParameter("memo") != null
//					&& db.updateData(new Memo(Integer.parseInt(request.getParameter("idx"))
//							, request.getParameter("title"), request.getParameter("memo")))) {				
//				model.addAttribute("message", "데이터를 수정했습니다.");
//			} else {
//				model.addAttribute("message", "데이터 수정에 실패했습니다.");
//			}
//			db.close();
//		} else {
//			model.addAttribute("message", "DB파일을 사용할 수 없습니다.");
//		}
//		
//		return "message";
//	}	
}

 

CommonController.java

package com.kopo.memo;

import java.io.UnsupportedEncodingException;
import java.util.Locale;

import javax.servlet.http.HttpServletRequest;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class CommonController {
	
	DB db = new DB(new Memo());
	DB db2 = new DB(new People());
	
	// create table
	@RequestMapping(value = "/create", method = RequestMethod.GET)
	public String create(Locale locale, Model model) {		
		if (db.open()) {
			if (db.createTable()) {
				model.addAttribute("message", "테이블이 생성되었습니다.");
			} else {
				model.addAttribute("message", "테이블 생성에 실패하였습니다.");
			}
			db.close();
		} else {
			model.addAttribute("message", "DB파일을 사용할 수 없습니다.");
		}
		
		return "message";
	}
	
	// 이런식으로 DB를 2개 만들어 2개 이상의 테이블을 사용할 수 있다.
	@RequestMapping(value = "/create2", method = RequestMethod.GET)
	public String create2(Locale locale, Model model) {		
		if (db2.open()) {
			if (db2.createTable()) {
				model.addAttribute("message", "테이블이 생성되었습니다.");
			} else {
				model.addAttribute("message", "테이블 생성에 실패하였습니다.");
			}
			db2.close();
		} else {
			model.addAttribute("message", "DB파일을 사용할 수 없습니다.");
		}
		
		return "message";
	}
	
	// insert
	@RequestMapping(value = "/insert", method = RequestMethod.GET)
	public String insert(Locale locale, Model model, HttpServletRequest request) {
		try {
			request.setCharacterEncoding("UTF-8");
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		
		if (db.open()) {
			if (db.insertData(request)) {				
				model.addAttribute("message", "새 데이터를 추가했습니다.");
			} else {
				model.addAttribute("message", "데이터 추가에 실패했습니다.");
			}
			db.close();
		} else {
			model.addAttribute("message", "DB파일을 사용할 수 없습니다.");
		}
		
		return "message";
	}
	
	// update
	@RequestMapping(value = "/update", method = RequestMethod.GET)
	public String update(Locale locale, Model model, HttpServletRequest request) {
		try {
			request.setCharacterEncoding("UTF-8");
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		
	//		if (request.getParameter("idx") == null) {
	//			model.addAttribute("message", "입력 데이터가 잘못 되었습니다.");
	//			return "message";
	//		}
		
		if (db.open()) {
			if (db.updateData(request)) {				
				model.addAttribute("message", "데이터를 수정했습니다.");
			} else {
				model.addAttribute("message", "데이터 수정에 실패했습니다.");
			}
			db.close();
		} else {
			model.addAttribute("message", "DB파일을 사용할 수 없습니다.");
		}
		
		return "message";
	}
}

 

DB.java

package com.kopo.memo;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;

import javax.servlet.http.HttpServletRequest;

import org.sqlite.SQLiteConfig;

public class DB<T> {
	private String dbFileName;
	private String tableName;
	private Connection connection;
	private T t;
	static {
		try {
			Class.forName("org.sqlite.JDBC");
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}
	
	// 파라미터 없는 생성자 추가
	public DB() {
		
	}
	
	// dbfileName이랑 tableName을 HomeController가 new Memo()만 넣어서 보내면 Memo 객체 내에 입력된 정보를 사용하도록 변경.
	public DB(T t) {
		this.t = t;
	
		Class<?> dataClass = t.getClass();
		Field[] dataClassFields = dataClass.getDeclaredFields();
	
		for (Field field : dataClassFields) {
			String fieldName = field.getName();
			String fieldType = field.getType().toString();
			try {
				if (fieldName.matches("DB_FILE_NAME")) {
				this.dbFileName = field.get(t).toString();
			} else if (fieldName.matches("TABLE_NAME")) {
					this.tableName = field.get(t).toString();
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	public DB(String dbFileName, String tableName) {
		this.dbFileName = dbFileName;
		this.tableName = tableName;
	}

	public boolean open() {
		SQLiteConfig config = new SQLiteConfig();
		try {
			this.connection = DriverManager.getConnection("jdbc:sqlite:/" + this.dbFileName, config.toProperties());
			return true;
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return false;
	}
	
	// 파라미터를 받아서 open하는거 생성
	public boolean open(String dbFileName) {
		SQLiteConfig config = new SQLiteConfig();
		try {
			this.connection = DriverManager.getConnection("jdbc:sqlite:/" + this.dbFileName, config.toProperties());
			return true;
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return false;
	}
	
	public void close() {
		if (this.connection != null) {
			try {
				this.connection.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}
	
//	public void createTable() {
//		String fieldString = "";
//		fieldString = fieldString + "idx INT PRIMARY KEY AUTOINCREMENT";
//		fieldString = fieldString + ", name TEXT";
//		fieldString = fieldString + ", middleScore INT";
//		fieldString = fieldString + ", finalScore INT";
//		String query = "CREATE TABLE " + this.tableName + " (" + fieldString + ")";
//		try {
//			Statement statement = this.connection.createStatement();
//			statement.executeUpdate(query);
//			statement.close();
//		} catch (SQLException e) {
//			e.printStackTrace();
//		}
//	}
	
//	public boolean createTable(T t) {
//		Class<?> dataClass = t.getClass();
//		Field[] dataClassFields = dataClass.getDeclaredFields();
//		
//		String fieldString = "";
//		
//		for (Field field: dataClassFields) {
//			if (!fieldString.isEmpty()) {
//				fieldString = fieldString + ",";
//			}
//			String fieldName = field.getName();
//			String fieldType = field.getType().toString();
//			fieldString = fieldString + fieldName;
//			if (fieldName.matches("idx")) {
//				fieldString = fieldString + " INTEGER PRIMARY KEY AUTOINCREMENT";
//			} else if (fieldType.matches("(int|long|short)")) {
//				fieldString = fieldString + " INTEGER";
//			} else if (fieldType.matches("(float|double)")) {
//				fieldString = fieldString + " REAL";
//			} else if (fieldType.matches(".*String")) {
//				fieldString = fieldString + " TEXT";
//			}
//		}
//		
//		String query = "CREATE TABLE " + this.tableName + " (" + fieldString + ")";
//		try {
//			Statement statement = this.connection.createStatement();
//			statement.executeUpdate(query);
//			statement.close();
//			return true;
//		} catch (SQLException e) {
//			e.printStackTrace();
//		}
//		return false;
//	}
	
	// 여기도 변경
//	public boolean createTable(T t) {
//		Class<?> dataClass = t.getClass();
//		Field[] dataClassFields = dataClass.getDeclaredFields();
//		
//		String fieldString = "";
//		String dbFileName = "";
//		String tableName = "";
//		
//		for (Field field: dataClassFields) {
//			String fieldName = field.getName();
//			String fieldType = field.getType().toString();
//			try {
//				if (fieldType.matches("DB_FILE_NAME")) {
//					dbFileName = field.get(t).toString();
//					continue;
//				} else if (fieldType.matches("TABLE_NAME")) {
//					tableName = field.get(t).toString();
//					continue;
//				}
//			} catch (Exception e) {
//				e.printStackTrace();
//				return false;
//			}
//			
//			if (!fieldString.isEmpty()) {
//				fieldString = fieldString + ",";
//			}			
//			fieldString = fieldString + fieldName;
//			if (fieldName.matches("idx")) {
//				fieldString = fieldString + " INTEGER PRIMARY KEY AUTOINCREMENT";
//			} else if (fieldType.matches("(int|long)")) {
//				fieldString = fieldString + " INTEGER";
//			} else if (fieldType.matches("(float|double)")) {
//				fieldString = fieldString + " REAL";
//			} else if (fieldType.matches(".*String")) {
//				fieldString = fieldString + " TEXT";
//			}
//		}
//		
//		String query = "CREATE TABLE " + tableName + " (" + fieldString + ")";
//		try {
//			this.open(dbFileName);
//			Statement statement = this.connection.createStatement();
//			statement.executeUpdate(query);
//			statement.close();
//			this.close();
//			return true;
//		} catch (SQLException e) {
//			e.printStackTrace();
//			this.close();
//		}
//		return false;
//	}
	
	public boolean createTable() {
		Class<?> dataClass = t.getClass();
		Field[] dataClassFields = dataClass.getDeclaredFields();
		
		String fieldString = "";
		String dbFileName = "";
		String tableName = "";
		
		for (Field field: dataClassFields) {
			String fieldName = field.getName();
			String fieldType = field.getType().toString();
	
			try {
				if (fieldName.matches("DB_FILE_NAME")) {
					dbFileName = field.get(t).toString();
					continue;
				} else if (fieldName.matches("TABLE_NAME")) {
					tableName = field.get(t).toString();
					continue;
				}
			} catch (Exception e) {
				e.printStackTrace();
				return false;
			}
			
			if (!fieldString.isEmpty()) {
				fieldString = fieldString + ",";
			}			
			fieldString = fieldString + fieldName;
			if (fieldName.matches("idx")) {
				fieldString = fieldString + " INTEGER PRIMARY KEY AUTOINCREMENT";
			} else if (fieldType.matches("(int|long)")) {
				fieldString = fieldString + " INTEGER";
			} else if (fieldType.matches("(float|double)")) {
				fieldString = fieldString + " REAL";
			} else if (fieldType.matches(".*String")) {
				fieldString = fieldString + " TEXT";
			}
		}
		
		String query = "CREATE TABLE " + tableName + " (" + fieldString + ")";
		try {
			this.open(dbFileName);
			Statement statement = this.connection.createStatement();
			statement.executeUpdate(query);
			statement.close();
			this.close();
			return true;
		} catch (SQLException e) {
			e.printStackTrace();
			this.close();
		}
		return false;
	}
	
//	public void insertData(Student student) {
//		String fieldString = "";
//		fieldString = fieldString + "name";
//		fieldString = fieldString + ", middleScore";
//		fieldString = fieldString + ", finalScore";
//		String valueString = "";
//		valueString = valueString + "'" + student.name + "'";
//		valueString = valueString + ", " + student.middleScore;
//		valueString = valueString + ", " + student.finalScore;
//		String query = "INSERT INTO " + this.tableName + " (" + fieldString + ") VALUES(" + valueString + ")";
//		try {
//			Statement statement = this.connection.createStatement();
//			statement.executeUpdate(query);
//			statement.close();
//		} catch (SQLException e) {
//			e.printStackTrace();
//		}
//	}
	
//	public boolean insertData(T t) {
//		Class<?> dataClass = t.getClass();
//		Field[] dataClassFields = dataClass.getDeclaredFields();
//		
//		String fieldString = "";
//		String valueString = "";
//
//		for (Field field: dataClassFields) {
//			String fieldName = field.getName();
//			String fieldType = field.getType().toString();
//			if (fieldName.matches("idx")) {
//				continue;
//			}
//			if (!fieldString.isEmpty()) {
//				fieldString = fieldString + ",";
//			}
//			if (!valueString.isEmpty()) {
//				valueString = valueString + ",";
//			}
//			fieldString = fieldString + fieldName;
//			try {
//				if (fieldType.matches(".*String")) {
//					valueString = valueString + "'" + field.get(t) + "'";
//				} else {
//					valueString = valueString + field.get(t);
//				}
//			} catch (IllegalArgumentException e) {
//				e.printStackTrace();
//			} catch (IllegalAccessException e) {
//				e.printStackTrace();
//			}
//		}
//		
//		String query = "INSERT INTO " + this.tableName + " (" + fieldString + ") VALUES(" + valueString + ")";
//		try {
//			Statement statement = this.connection.createStatement();
//			statement.executeUpdate(query);
//			statement.close();
//			return true;
//		} catch (SQLException e) {
//			e.printStackTrace();
//		}
//		return false;
//	}
	
	public boolean insertData(HttpServletRequest request) {
		Class<?> dataClass = t.getClass();
		Field[] dataClassFields = dataClass.getDeclaredFields();
		
		String fieldString = "";
		String valueString = "";
		
		ArrayList<Object> preparedValue = new ArrayList<Object>();
		
		for (Field field: dataClassFields) {
			String fieldName = field.getName();
			String fieldType = field.getType().toString();
			
			if (fieldName.matches("idx")) {
				continue;
			}
			
			// 입력된 데이터가 비어있고(null), fieldName이 created, update가 아니라면 다음 for문으로 넘어가라.
			// 입력된 데이터가 비어있지 않거나, 비어 있더라도 fieldName이 created 또는 updated면 아래 로직을 계속 실행하라.
			if (request.getParameter(fieldName) == null && !fieldName.matches("(created|updated)")) {
				continue;
			}
			// 기존에 HomeController에서 했던 숫자 데이터 체크를 request를 그대로 받아 이곳에서 한다.
			if (fieldType.matches("(int|long|short)") && !this.isIntegerString(request.getParameter(fieldName))) {
				return false;
			} else if (fieldType.matches("(float|double)") && !this.isFloatString(fieldName)) {
				return false;
			}
			
			// 2번째 for문 돌 때부터 ','를 추가
			if (!fieldString.isEmpty()) {
				fieldString = fieldString + ",";
			}
			if (!valueString.isEmpty()) {
				valueString = valueString + ",";
			}
			fieldString = fieldString + fieldName;
			valueString = valueString + "?";
			
			if (fieldName.matches("created")) {	// DB 컬럼명이 'created'일 때
				SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
				Date date = new Date(System.currentTimeMillis());	// java.util.Date;
				preparedValue.add(formatter.format(date));
			} else if (fieldName.matches("updated")) {	// DB 컬럼명이 'updated'일 때
				SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
				Date date = new Date(System.currentTimeMillis());	// java.util.Date;
				preparedValue.add(formatter.format(date));
			} else {
				preparedValue.add(request.getParameter(fieldName));
			}
		}
		
		String query = "INSERT INTO " + this.tableName + " (" + fieldString + ") VALUES(" + valueString + ")";
		try {
			this.open();
			PreparedStatement statement = this.connection.prepareStatement(query);
			for (int i = 0; i < preparedValue.size(); i++) {
				statement.setObject(i + 1, preparedValue.get(i));
			}
			statement.executeUpdate();
			statement.close();
			this.close();
			return true;
		} catch (SQLException e) {
			e.printStackTrace();
			this.close();
		}
		return false;
	}

//	public void updateData(int idx, String memo) {
//		String setString = "memo='" + memo + "'";
//		String whereString = "idx=" + idx;
//		String query = "UPDATE " + this.tableName + " SET " + setString + " WHERE " + whereString;
//		try {
//			Statement statement = this.connection.createStatement();
//			statement.executeUpdate(query);
//			statement.close();
//		} catch (SQLException e) {
//			e.printStackTrace();
//		}
//	}
	
//	public boolean updateData(T t) {
//		Class<?> dataClass = t.getClass();
//		Field[] dataClassFields = dataClass.getDeclaredFields();
//		String setString = "";
//		String whereString = "";
//		for (Field field : dataClassFields) {
//			if (!setString.isEmpty()) {
//				setString = setString + ",";
//			}
//			String fieldName = field.getName();
//			String fieldType = field.getType().toString();
//			try {
//				if (fieldName.matches("idx")) {
//					whereString = "idx=" + field.get(t);
//				} else if (fieldType.matches(".*String")) {
//					setString = setString + fieldName + "=" + "'" + field.get(t) + "'";
//				} else {
//					setString = setString + fieldName + "=" + field.get(t);
//				}
//			} catch (IllegalArgumentException e) {
//				e.printStackTrace();
//			} catch (IllegalAccessException e) {
//				e.printStackTrace();
//			} catch (Exception e) {
//				e.printStackTrace();
//			}
//		}
//		
//		String query = "UPDATE " + this.tableName + " SET " + setString + " WHERE " + whereString;
//		try {
//			Statement statement = this.connection.createStatement();
//			statement.executeUpdate(query);
//			statement.close();
//			return true;
//		} catch (SQLException e) {
//			e.printStackTrace();
//		}
//		return false;
//	}

	public boolean updateData(HttpServletRequest request) {
		Class<?> dataClass = t.getClass();
		Field[] dataClassFields = dataClass.getDeclaredFields();
		String setString = "";
		String whereString = "";
		
		ArrayList<Object> preparedValue = new ArrayList<Object>();
		
		for (Field field : dataClassFields) {
			String fieldName = field.getName();
			String fieldType = field.getType().toString();
			
			// 입력된 데이터가 비어있고(null), fieldName이 created, update가 아니라면 다음 for문으로 넘어가라.
						// 입력된 데이터가 비어있지 않거나, 비어 있더라도 fieldName이 updated면 아래 로직을 계속 실행하라.
			if (request.getParameter(fieldName) == null && !fieldName.matches("(updated)")) {
				continue;
			}
			// 기존에 HomeController에서 했던 숫자 데이터 체크를 request를 그대로 받아 이곳에서 한다.
			if (fieldType.matches("(int|long|short)") && !this.isIntegerString(request.getParameter(fieldName))) {
				return false;
			} else if (fieldType.matches("(float|double)") && !this.isFloatString(fieldName)) {
				return false;
			}
			
			if (!setString.isEmpty()) {
				setString = setString + ",";
			}
		
			try {
				if (fieldName.matches("idx")) {
					whereString = "idx=" + request.getParameter("idx");
					continue;
				} else if (fieldName.matches("updated")) {	// DB 컬럼명이 'updated'일 때
					SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
					Date date = new Date(System.currentTimeMillis());	// java.util.Date;
					preparedValue.add(formatter.format(date));
				} else {
					preparedValue.add(request.getParameter(fieldName));
				}
				setString = setString + fieldName + "=?";
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		
		String query = "UPDATE " + this.tableName + " SET " + setString + " WHERE " + whereString;
		try {
			this.open();
			PreparedStatement statement = this.connection.prepareStatement(query);
			for (int i = 0; i < preparedValue.size(); i++) {
				statement.setObject(i + 1, preparedValue.get(i));
			}
			statement.executeUpdate();
			statement.close();
			this.close();
			return true;
		} catch (SQLException e) {
			e.printStackTrace();
			this.close();
		}
		return false;
	}
	
//	public String selectData() {
//		String query = "SELECT * FROM " + this.tableName;
//		String htmlTxt = "";
//		try {
//			Statement statement = this.connection.createStatement();
//			ResultSet result = statement.executeQuery(query);
//			while(result.next()) {
//				htmlTxt = htmlTxt + "<tr>";
//				htmlTxt = htmlTxt + "<td>" + result.getInt("idx") + "</td>";
//				htmlTxt = htmlTxt + "<td>" + result.getString("name") + "</td>";
//				htmlTxt = htmlTxt + "<td>" + result.getInt("middleScore") + "</td>";
//				htmlTxt = htmlTxt + "<td>" + result.getInt("finalScore") + "</td>";
//				htmlTxt = htmlTxt + "</tr>";
//			}
//			result.close();
//			statement.close();
//		} catch (SQLException e) {
//			e.printStackTrace();
//		}
//		return htmlTxt;
//	}
	
	public String selectStringData(T t) {
		Class<?> dataClass = t.getClass();
		Field[] dataClassFields = dataClass.getDeclaredFields();

		String query = "SELECT * FROM " + this.tableName;
		ArrayList<T> resultDataSet = new ArrayList<T>(); 
		try {
			Statement statement = this.connection.createStatement();
			ResultSet result = statement.executeQuery(query);
			while(result.next()) {
				T rowData = (T)dataClass.getDeclaredConstructor().newInstance();

				for(Field field : dataClassFields) {
					String fieldName = field.getName();
					String fieldType = field.getType().toString();
						
					if (fieldType.matches("(int)")) {
						field.setInt(rowData, result.getInt(fieldName));
					} else if (fieldType.matches("(long)")) {
						field.setLong(rowData, result.getLong(fieldName));
					} else if (fieldType.matches("(float|double)")) {
						field.setDouble(rowData, result.getDouble(fieldName));
					} else if (fieldType.matches(".*String")) {
						field.set(rowData, result.getString(fieldName));
					}
				}
				resultDataSet.add(rowData);
			}
			result.close();
			statement.close();

			Method toHtmlStringMethod = dataClass.getDeclaredMethod("toHtmlString");
			StringBuffer htmlString = new StringBuffer();
			for (T row : resultDataSet) {
				htmlString.append((String) toHtmlStringMethod.invoke(row));
			}
			return htmlString.toString();
		} catch (SQLException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return "";
	}
	
	public ArrayList<T> selectData(T t) {
		Class<?> dataClass = t.getClass();
		Field[] dataClassFields = dataClass.getDeclaredFields();

		String query = "SELECT * FROM " + this.tableName;
		ArrayList<T> resultDataSet = new ArrayList<T>(); 
		try {
			Statement statement = this.connection.createStatement();
			ResultSet result = statement.executeQuery(query);
			while(result.next()) {
				T rowData = (T)dataClass.getDeclaredConstructor().newInstance();

				for(Field field : dataClassFields) {
					String fieldName = field.getName();
					String fieldType = field.getType().toString();
						
					if (fieldType.matches("(int)")) {
						field.setInt(rowData, result.getInt(fieldName));
					} else if (fieldType.matches("(long)")) {
						field.setLong(rowData, result.getLong(fieldName));
					} else if (fieldType.matches("(float|double)")) {
						field.setDouble(rowData, result.getDouble(fieldName));
					} else if (fieldType.matches(".*String")) {
						field.set(rowData, result.getString(fieldName));
					}
				}
				resultDataSet.add(rowData);
			}
			result.close();
			statement.close();
		} catch (SQLException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return resultDataSet;
	}
	
	public T selectData(int idx, T t) {
		Class<?> dataClass = t.getClass();
		Field[] dataClassFields = dataClass.getDeclaredFields();

		String query = "SELECT * FROM " + this.tableName + " WHERE idx=" + idx;
		try {
			Statement statement = this.connection.createStatement();
			ResultSet result = statement.executeQuery(query);
			T rowData = (T)dataClass.getDeclaredConstructor().newInstance();
			if(result.next()) {
				for(Field field : dataClassFields) {
					String fieldName = field.getName();
					String fieldType = field.getType().toString();
						
					if (fieldType.matches("(int)")) {
						field.setInt(rowData, result.getInt(fieldName));
					} else if (fieldType.matches("(long)")) {
						field.setLong(rowData, result.getLong(fieldName));
					} else if (fieldType.matches("(float|double)")) {
						field.setDouble(rowData, result.getDouble(fieldName));
					} else if (fieldType.matches(".*String")) {
						field.set(rowData, result.getString(fieldName));
					}
				}
			}
			result.close();
			statement.close();
			
			return rowData;
			
		} catch (SQLException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return (T)(new Object());
	}
	
	
	
	public static boolean isIntegerString(String numericString) {
		try {
			int result = Integer.parseInt(numericString);
			return true;
		} catch (Exception e) {
		}
		return false;
	}

	public static boolean isFloatString(String numericString) {
		try {
			double result = Double.parseDouble(numericString);
			return true;
		} catch (Exception e) {
		}
		return false;
	}
}

 

Memo.java

package com.kopo.memo;

public class Memo {
	public int idx;
	public String title;
	public String memo;
	public String created;
	public String updated;
	
	public final String DB_FILE_NAME = "/Users/saebyul/SqliteDB/0916memo2.db";
	public final String TABLE_NAME = "memo";
	
	public Memo() {
		
	}
	
	public Memo(String title, String memo) {
		this.title = title;
		this.memo = memo;
	}
	
	public Memo(int idx, String title, String memo) {
		this.idx = idx;
		this.title = title;
		this.memo = memo;
	}
	
	public String toHtmlString() {
		StringBuffer htmlString = new StringBuffer();
		htmlString.append("<tr>");
		htmlString.append("<td>" + this.idx + "</td>");
		htmlString.append("<td>" + this.title + "</td>");
		htmlString.append("<td>" + this.memo + "</td>");
		htmlString.append("<td>" + "<a href=u1?idx=" + this.idx + ">상세보기</a>" + "</td>");
		htmlString.append("</tr>");
		
		return htmlString.toString();
	}

	public int getIdx() {
		return idx;
	}

	public void setIdx(int idx) {
		this.idx = idx;
	}

	public String getTitle() {
		return title;
	}

	public void setTitle(String title) {
		this.title = title;
	}

	public String getMemo() {
		return memo;
	}

	public void setMemo(String memo) {
		this.memo = memo;
	}

	public String getCreated() {
		return created;
	}

	public void setCreated(String created) {
		this.created = created;
	}

	public String getUpdated() {
		return updated;
	}

	public void setUpdated(String updated) {
		this.updated = updated;
	}
}

 

People.java

package com.kopo.memo;

public class People {
	public int idx;
	public String name;
	public String phone;
	public String created;
	public String updated;
	
	public final String DB_FILE_NAME = "/Users/saebyul/SqliteDB/0916memo2.db";
	public final String TABLE_NAME = "people";

}

 

home.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>

<!DOCTYPE html>
  <head>
	<title>Home</title>
  </head>
  <body>
    <h1>
	  Hello world!  
    </h1>
    <%
    int a = 10;
    int b = 5;
    int c = a + b;
    
    
    // jsp 안에 java코드 사용(이제는 하지 말 것)
    // jstl : jsp에서 태그 <> 형태로 제공하는 기능 (jsp 기반)(한국은 아직 여기...)
    // thymeleaf (<html> 기반) (spring boot 기본 방식 가장 최신이고 표준이다.)
    
    %>
    <P> axb+c=<%=a*b+c %>. </P> <!-- 대문자로 써도 되긴 하지만 html 표준은 태그를 소문자로 사용한다. <p> 이런식으로 사용 -->
    
    <!-- test라는 변수가 HomeController에게 Memo라는 객체를 넘겨 받아 그 객체의 title, memo 안에 원하는 value를 넣는다. -->
    <c:set target="${test }" property="title" value="야호호호"></c:set>
    <c:set target="${test }" property="memo" value="코로나야끝나라아아아"></c:set>
    <p> title :  <c:out value="${test.title }"></c:out> </p>
    <p> memo :  <c:out value="${test.memo }"></c:out> </p>
    
     <table style="width: 100%;">
      <tbody>
        <c:forEach var="each" items="${list }">
          <tr>
            <td>${each.getIdx() }</td>                        <!-- getter를 쓸 수도 있고, -->
            <td>${each.title }</td>                           <!-- c:out 태그를 생략하고 사용할 수도 있다. (편리!) -->
            <td><c:out value="${each.memo }"></c:out></td>    <!-- Full로 쓰면 c:out 태그를 넣어야 한다. -->
            <td><a href="u1?idx=${each.idx }">수정하기</a>
          </tr>
        </c:forEach>
      </tbody>
    </table>
        
  </body>
</html>

  

i1.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Insert title here</title>
  </head>
  <body>
    <form action="insert">
      <input type="text" placeholder="title" name="title" />
      <textarea name="memo" style="width: 100%;"></textarea>
      <input type="submit" value="입력 완료" />
    </form>
    <br /><br />
    <a href="list" style="padding: 10px 20px; background: #eee;">리스트로</a>
  </body>
</html>

  

list.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Insert title here</title>
  </head>
  <body>
    <table style="width: 100%;">
      <thead>
        <tr>
        <th>idx</th>
        <th>title</th>
        <th>memo</th>
        <th></th>
      </thead>
      ${list }
    </table>
    <a href="i1" style="padding: 10px 20px; background: #eee;">글쓰기</a>
  </body>
</html>

 

message.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>

<head>
	<title>Message</title>
</head>
<body>

<P>${message}</P>
</body>
</html>

 

u1.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Insert title here</title>
  </head>
  <body>
    <form action="update">
      <input type="hidden" name="idx" value="${idx }" />
      <input type="text" placeholder="title" name="title" value="${title }"/>
      <textarea name="memo" style="width: 100%;">${memo }</textarea>
      <input type="submit" value="수정 완료" />
    </form>
    <br /><br />
    <a href="list" style="padding: 10px 20px; background: #eee;">리스트로</a>
  </body>
</html>

  

 

 

2020/09/14 - [개발자/Algorithm] - Kruskal's Algorithm

2020/09/21 - [개발자/Algorithm] - Prim's Algorithm

블로그에 포스팅한 원글과, 원글에 포함된 원본 소스코드, 그 소스코드의 출처는 위와 같습니다. 아래 코드는 제가 원본 소스코드를 참고하여 수정한 코드입니다.

 

 

Kruskal

// 기존 1번 코드 원본에서 불필요하게 반복되는 find 함수의 실행을 줄임.

import java.io.IOException;
import java.util.ArrayList;
import java.util.PriorityQueue;
import java.util.Scanner;


public class Kruskal2 {
    static int V, E;                    // V : 점, E : 변
    static ArrayList<Edge> mst;         // result값 Minimum spanning tree
    static PriorityQueue<Edge> pq;      // 모든 노드(점)을 힙에 넣고 시작.
    static int[] arr ;                  // 각 노드의 부모가 누구인지를 저장한다. ex) arr[0] = 1 이면 0의 부모(root node)는 1이다.

    public static void main(String[] args) throws IOException {
        Scanner sc = new Scanner(System.in);

        // Vertex : 전체 점의 개수를 입력 받는다. (샘플에서 6에 해당)
        V = sc.nextInt();

        // Edge : 전체 변의 개수를 입력 받는다. (샘플에서 9에 해당)
        E = sc.nextInt();

        mst = new ArrayList<>();
        arr = new int[V+1]; // array는 인덱스가 0부터 시작하는데 문제의 점이 1부터 6까지라 인덱스 0번을 제외하고 1~6을 쓰기 위헤 V+1개의 배열 생성.
        // Queue는 기본적으로 FIFO 구조를 갖는다. Priority queue는  '최대 우선순위 큐'와 '최소 우선순위 큐'로 나뉜다.
        pq = new PriorityQueue<>();

        // vertex의 개수 + 1개 만큼 반복. (i = 1부터 시작하면 안 된다. 0번을 안 쓰더라도 배열은 반드시 0번부터 채워야한다. 비워둘 수 없다.)
        for (int i = 0; i <= V; i++) {
            // 각 vertex는 시작할 때는 자기 자신을 root로 초기화.
            arr[i] = i;
        }

        // 각 노드의 연결 관계와 비용을 입력 받는다. (샘플에서 1 2 5...  1 3 4 에 해당)
        for (int i = 0; i < E; i++) {
            int v1 = sc.nextInt();      // 1번 노드와...  1번 노드와
            int v2 = sc.nextInt();      // 2번 노드의...  3번 노드의
            int value = sc.nextInt();   // 비용은 5다...  비용은 4다

            // 우선순위 큐에 Edge라는 객체를 하나씩 추가한다. Edge는 변으로, 자기 자신의 좌우의 노드와 비용에 대한 정보를 갖는다.
            pq.add(new Edge(v1, v2, value));
        }

        // V개의 노드를 연결하는 최소 Edge의 수는 '노드-1'이므로 mst.size()가 'V-1'이 될 때까지 반복한다.
        // 즉, mst.size()가 'V-2'개일 때 반복하다, 마지막 하나를 더 찾아 'V-1'개가 되면 반복문 조건이 false가 된다.
        while(mst.size() < (V-1)) {
            // PQ에 하나씩 넣었고, 이제 하나씩 뺀다. PQ로 넣어서 FIFO이 아닌 오름차순으로 나온다.(value 기준)
            Edge edge = pq.poll();
            // Edge의 양 끝 각 노드(start, end라 명명함)의 최종 부모(root)를 구한다.
            int parentEdgeStart = find(edge.start);
            int parentEdgeEnd = find(edge.end);

            // Edge(변)의 앞뒤 각각 노드의 최종 부모가 서로 같지 않다면(= 서로 다른 그룹이라면 혹은 아직 그룹이 아니라면) 실행. (Edge의 좌우 노드의 최종 부모가 같다면 이미 같은 그룹이니 해당 엣지는 로직을 돌지 않고 버린다.)
            if(parentEdgeStart != parentEdgeEnd)
            {
                // 같은 그룹이 아니라면 MST 로직을 만족(True)하니까 mst ArrayList에 edge 객체를 추가한다.
                mst.add(edge);
                // MST에 추가한 후 이 Edge가 속한 그룹(Union)이 새 그룹이든, 기존 그룹에 결합된 그룹이든간에 Edge 객체의 좌우 노드의 최종 부모(root)를 갱신해준다.
                union(parentEdgeStart, parentEdgeEnd);
            }
        }
        System.out.println(mst);
    }

    static int find(int n) {            // 입력 받은 노드의 최종 부모를 구한다.
        if (n == arr[n]) {              // 자기 자신이 root면 자기 자신을 반환. root가 바뀌어 자기 자신이 root가 아니면 자신의 부모 노드가 root인지를 재귀를 통해 확인. 이 경우 아래 else문을 탄다.
            return n;
        } else {
            int p = find(arr[n]);       // 각 노드의 부모를 찾는 함수를 재귀로 호출한다. 자기 자신이 root가 되어 if문을 탈 때까지 재귀를 반복한다. 재귀를 돌 때 arr[n]을 넣어 자기의 부모를 넣는다.
                                        // 그러면 그 부모는 또 자기의 부모를 찾는다. 그리고 더이상 부모가 없고 자기 자신이 root면 return을 한다. // ex) arr[0] = 1, arr[1] = 5, arr[5] = 5 자기 자신인 최종 root가 나오면 3번 호출되어 if문을 타고 return된다.
            arr[n]=p;                   // path compression : 코드 최적화. 불필요한 재귀 호출을 줄이기 위해 재귀를 들어가 return 되어 나올 때 각 노드의 최종 부모(root)를 가리키도록 저장한다.
                                        // 즉, arr[0] = 3, arr[1] = 3, arr[5] = 3. 그러면 다음번에는 1+1번만 호출되어 return된다.
            return p;                   // 최종 부모(root)를 return.
        }
    }

    // Union 로직 함수. 노드1과 노드2의 최종 부모가 다르다면 같은 그룹이 아니므로 하나의 그룹으로 결합한다. 이 때 root는 뒤쪽 노드가 된다.
    static void union(int p1, int p2) {
        if (p1 != p2) {
            arr[p1]= p2;
        }
    }

    // Edge 객체를 클래스로 정의. 2개의 노드와 1개의 비용을 변수로 갖는다.
    static class Edge implements Comparable<Edge>{
        int start, end, value;

        Edge(int s, int e, int v) {
            this.start = s;
            this.end = e;
            this.value = v;
        }
        @Override
        public int compareTo(Edge o) {
            return this.value - o.value;
        }
        @Override
        public String toString() {
            return "Edge [start=" + start + ", end=" + end + ", value=" + value + "]\n";
        }
    }
}

/*
6
9
1 2 5
1 3 4
2 3 2
2 4 7
3 4 6
4 6 8
3 5 11
4 5 3
5 6 8
*/
더보기
// 2번 코드에서 Scanner를 BufferedReader, InputStreamReader로 바꿈.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.PriorityQueue;


public class Kruskal3 {
    static int V, E;                    // V : 점, E : 변
    static ArrayList<Edge> mst;         // result값 Minimum spanning tree
    static PriorityQueue<Edge> pq;      // 모든 노드(점)을 힙에 넣고 시작.
    static int[] arr ;                  // 각 노드의 부모가 누구인지를 저장한다. ex) arr[0] = 1 이면 0의 부모(root node)는 1이다.

    public static void main(String[] args) throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        // throws IOException을 해주거나,
        // String var = null; 을 미리 인스턴스화 시킨 후
        // try {
        //      var = bufferedReader.readLine();
        // } catch (IOException | NumberFormatException e) {
        //      e.printStackTrace();
        // }
        // 을 해줘야한다.

        // Vertex : 전체 점의 개수를 입력 받는다. (샘플에서 6에 해당)
        V = Integer.parseInt(bufferedReader.readLine());

        // Edge : 전체 변의 개수를 입력 받는다. (샘플에서 9에 해당)
        E = Integer.parseInt(bufferedReader.readLine());

        mst = new ArrayList<>();
        arr = new int[V+1]; // array는 인덱스가 0부터 시작하는데 문제의 점이 1부터 6까지라 인덱스 0번을 제외하고 1~6을 쓰기 위헤 V+1개의 배열 생성.
        // Queue는 기본적으로 FIFO 구조를 갖는다. Priority queue는  '최대 우선순위 큐'와 '최소 우선순위 큐'로 나뉜다.
        pq = new PriorityQueue<>();

        // vertex의 개수 + 1개 만큼 반복. (i = 1부터 시작하면 안 된다. 0번을 안 쓰더라도 배열은 반드시 0번부터 채워야한다. 비워둘 수 없다.)
        for (int i = 0; i <= V; i++) {
            // 각 vertex는 시작할 때는 자기 자신을 root로 초기화.
            arr[i] = i;
        }

        // Edge의 정보는 양쪽 Vertex와 비용, 3개의 정보만 고정적으로 존재하기 때문에 3으로 고정 선언.
        String[] inputTmp = new String[3];
        int v1;
        int v2;
        int value;

        // 각 노드의 연결 관계와 비용을 입력 받는다. (샘플에서 1 2 5...  1 3 4 에 해당)
        for (int i = 0; i < E; i++) {
            inputTmp = bufferedReader.readLine().split(" ");
            v1 = Integer.parseInt(inputTmp[0]);     // 1번 노드와...  1번 노드와
            v2 = Integer.parseInt(inputTmp[1]);     // 2번 노드의...  3번 노드의
            value = Integer.parseInt(inputTmp[2]);  // 비용은 5다...  비용은 4다

            // 우선순위 큐에 Edge라는 객체를 하나씩 추가한다. Edge는 변으로, 자기 자신의 좌우의 노드와 비용에 대한 정보를 갖는다.
            pq.add(new Edge(v1, v2, value));
        }

        // V개의 노드를 연결하는 최소 Edge의 수는 '노드-1'이므로 mst.size()가 'V-1'이 될 때까지 반복한다.
        // 즉, mst.size()가 'V-2'개일 때 반복하다, 마지막 하나를 더 찾아 'V-1'개가 되면 반복문 조건이 false가 된다.
        while(mst.size() < (V-1)) {
            // PQ에 하나씩 넣었고, 이제 하나씩 뺀다. PQ로 넣어서 FIFO이 아닌 오름차순으로 나온다.(value 기준)
            Edge edge = pq.poll();
            // Edge의 양 끝 각 노드(start, end라 명명함)의 최종 부모(root)를 구한다.
            int parentEdgeStart = find(edge.start);
            int parentEdgeEnd = find(edge.end);

            // Edge(변)의 앞뒤 각각 노드의 최종 부모가 서로 같지 않다면(= 서로 다른 그룹이라면 혹은 아직 그룹이 아니라면) 실행. (Edge의 좌우 노드의 최종 부모가 같다면 이미 같은 그룹이니 해당 엣지는 로직을 돌지 않고 버린다.)
            if(parentEdgeStart != parentEdgeEnd)
            {
                // 같은 그룹이 아니라면 MST 로직을 만족(True)하니까 mst ArrayList에 edge 객체를 추가한다.
                mst.add(edge);
                // MST에 추가한 후 이 Edge가 속한 그룹(Union)이 새 그룹이든, 기존 그룹에 결합된 그룹이든간에 Edge 객체의 좌우 노드의 최종 부모(root)를 갱신해준다.
                union(parentEdgeStart, parentEdgeEnd);
            }
        }
        bufferedReader.close();
        System.out.println(mst);
    }

    static int find(int n) {            // 입력 받은 노드의 최종 부모를 구한다.
        if (n == arr[n]) {              // 자기 자신이 root면 자기 자신을 반환. root가 바뀌어 자기 자신이 root가 아니면 자신의 부모 노드가 root인지를 재귀를 통해 확인. 이 경우 아래 else문을 탄다.
            return n;
        } else {
            int p = find(arr[n]);       // 각 노드의 부모를 찾는 함수를 재귀로 호출한다. 자기 자신이 root가 되어 if문을 탈 때까지 재귀를 반복한다. 재귀를 돌 때 arr[n]을 넣어 자기의 부모를 넣는다.
            // 그러면 그 부모는 또 자기의 부모를 찾는다. 그리고 더이상 부모가 없고 자기 자신이 root면 return을 한다. // ex) arr[0] = 1, arr[1] = 5, arr[5] = 5 자기 자신인 최종 root가 나오면 3번 호출되어 if문을 타고 return된다.
            arr[n]=p;                   // path compression : 코드 최적화. 불필요한 재귀 호출을 줄이기 위해 재귀를 들어가 return 되어 나올 때 각 노드의 최종 부모(root)를 가리키도록 저장한다.
            // 즉, arr[0] = 3, arr[1] = 3, arr[5] = 3. 그러면 다음번에는 1+1번만 호출되어 return된다.
            return p;                   // 최종 부모(root)를 return.
        }
    }

    // Union 로직 함수. 노드1과 노드2의 최종 부모가 다르다면 같은 그룹이 아니므로 하나의 그룹으로 결합한다. 이 때 root는 뒤쪽 노드가 된다.
    static void union(int p1, int p2) {
        if (p1 != p2) {
            arr[p1]= p2;
        }
    }

    // Edge 객체를 클래스로 정의. 2개의 노드와 1개의 비용을 변수로 갖는다.
    static class Edge implements Comparable<Edge>{
        int start, end, value;

        Edge(int s, int e, int v) {
            this.start = s;
            this.end = e;
            this.value = v;
        }
        @Override
        public int compareTo(Edge o) {
            return this.value - o.value;
        }
        @Override
        public String toString() {
            return "Edge [start=" + start + ", end=" + end + ", value=" + value + "]\n";
        }
    }
}

/*
6
9
1 2 5
1 3 4
2 3 2
2 4 7
3 4 6
4 6 8
3 5 11
4 5 3
5 6 8
*/

 

 

Prim

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.PriorityQueue;
import java.util.Scanner;

public class Prim3 {
    static int V, E;                        // V : 점, E : 변
    static ArrayList<Edge> mst;             // result값 Minimum spanning tree
    static LinkedList<Edge>[] graph;        // Kruskal에서는 각 노드를 PriorityQueue<Edge>에 넣었다면, 여기서 graph는 해당 노드가 start인 모든 엣지를 넣는다. 즉, 3번 노드와 연결된 엣지가 5개면 3번 graph[3] start가 3인 엣지를 5개 가지고 있는 배열이 된다.
    static boolean[] visit;                 // 노드의 방문 여부 (Kruskal에서는 int[] arr에 각 노드의 부모가 누구인지를 저장했다.)

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        V = sc.nextInt();
        E = sc.nextInt();

        // 각 노드를 graph로 정의한다. 배열을 V+1개로 만들면 인덱스가 0 ~ V까지 배정되고, 0을 버리고 1 ~ V번 인덱스를 사용한다.
        graph = new LinkedList[V+1];
        // 각 노드의 방문 여부를 boolean 타입의 일차운 단순 배열로 만든다.
        visit =  new boolean[V+1];

        // 1번 노드부터 V번 노드까지 LinkedList<Edge>[] 타입의 graph 변수에 넣는다. (1 ~ 6번 노드가 샘플로 주어진다.)
        for (int i = 1; i <= V; i++) {
            graph[i]= new LinkedList<>();
        }

        mst = new ArrayList<>();

        // 각 노드의 연결 관계와 비용을 입력 받는다. (샘플에서 1 2 5...  1 3 4 에 해당. 엣지가 총 9개 주어졌으니 9번 돈다.)
        for (int i = 0; i < E; i++) {
            int v1 = sc.nextInt();      // 1번 노드와...  1번 노드와
            int v2 = sc.nextInt();      // 2번 노드의...  3번 노드의
            int value = sc.nextInt();   // 비용은 5다...  비용은 4다

            // 노드1에서 노드2로 가는 방향으로 저장.
            graph[v1].add(new Edge(v1,v2,value));
            // 노드2에서 노드1로 가는 방향으로 저장.
            graph[v2].add(new Edge(v2,v1,value));

            // 왜 양방향으로 저장할까?
            // 노드를 기준으로 뻗어 나가는 엣지를 정의하고 그 중 비용이 작은 것을 택하는 것이기 때문에 각 노드에서 뻗어나가는 모든 엣지를 합하면 결국 양방향이 되고 전체 엣지의 2배가 된다.
        }

        // Kruskal에서 while(mst.size() < (V-1)) { }에 해당. 메인 메소드에 다 넣지 않고 빼놓은 것 뿐이다.
        makeMst();
        System.out.println(mst);

    }
    static void makeMst() {
        // 1. 시작 노드는 아무거나 지정.
        // 2. 선택한 노드에 연결된 엣지들을 PrioityQueue에 넣는다. (비용을 기준으로 오름차순 정렬됨.)
        // 3. 비용이 최소인 Edge 객체를 poll()한다. 이 객체는 [start, end, value]를 갖고 있고, end를 visit 검사를 통해 방문 여부를 확인한다.
        // 4. 2~3 반복. n-1개의 간선이 선택할때까지 or 모든 정점이 연결될때까지

        // 노드를 기준으로 연결된 엣지를 담을 PriorityQueue를 하나 인스턴스화 한다. 그러면 해당 노드에 연결된 엣지들을 비용을 기준으로 오름차순 정렬한다.
        PriorityQueue<Edge> pq = new PriorityQueue<>();

        // 프림 알고리즘을 돌리기 위해 현재 방문중인 노드를 nowV라는 int타입 변수에 넣는다.
        // 1로 임의로 정한 것은 시작점을 1로 하겠다는 의미다. 3에서 시작하고 싶다면 3을 넣으면 된다.
        int nowV = 1;

        while(mst.size() < (V-1)) {
            // 현재 노드는 방문 했으니 true로 바꾼다.
            visit[nowV] = true;

            // LinkedList<Edge>[] graph에 모든 노드를 담고 시작했다. nowV에 해당하는 노드의 엣지를 하나씩 꺼낸다.
            // 즉, 1번 노드에 엣지가 3개면 각 엣지로 for문을 3번 돌거고, 5번 노드에 엣지가 1개면 for문을 1번 돌고 그런 식이다.
            // 그리고 이 엣지는 nowV라는 노드가 볼 때, start(자기 자신), end(연결된 노드), value(비용)을 가진 객체다.
            // 따라서 이 for문은 주어진 노드에 연결된 엣지를 개수만큼 꺼내 위에서 선언한 pq에 넣어 비용을 기준으로 엣지를 오름차순 정렬시킨다.
            // 이때 해당 노드의 모든 엣지를 무조건 가져올 수도 있찌만 이미 방문한 노드는 다시 계산할 필요가 없으니 if(!visit[edge.end]) { } 를 통해 방문된 노드와 연결된 엣지는 무시한다.
            for (Edge edge : graph[nowV]) {
                // Edge가 start -> end니까. start가 지금 들어온 nowV번 노드고, end가 start에서 뻗어 나간 엣지의 도착 방향의 노드다.
                // 이미 방문된 노드의 엣지는 무시하기 위한 로직.
                // *** 여기서 중요한 것은 방문한 노드가 end인 엣지를 추가하지 않았지만 그게 더 효율적일 수도 있다. 즉, 눈으로 볼 때는 그게 쉽게 보이지만
                // 컴퓨터로 볼 때는 그렇지 않다. 그러면 어떻게 처리를 할까? 방문한 노드 방향의 엣지를 담지 않는 대신 해당 엣지는 이미 이전에 반대편 노드가 start로 들어왔을 때
                // pq에 담겨있고, 그 pq를 매번 돌 때마다 초기화 하지 않기 때문에 유지가 된다. 즉,
                if(!visit[edge.end]) {
                    pq.add(edge);
                }
            }

            // nowV 노드의 엣지를 비용으로 정렬시킨 PriorityQueue가 모두 사라질 때까지 반복한다.
            while(!pq.isEmpty()) {
                // nowV 노드의 엣지중 비용이 작은 것부터 꺼낸다.
                Edge edge = pq.poll();
                // 해당 엣지의 end(nowV와 연결된 노드)가 방문하지 않았다면 if문으로 들어간다.
                if(!visit[edge.end]) {
                    // 이제 유효한 엣지를 찾았으니 방문중인 노드 nowV는 다음 노드로 이동한다.
                    nowV = edge.end;
                    // 그리고 그 엣지를 mst에 넣는다.
                    mst.add(edge);
                    // mst를 찾았으면 해당 nowV의 다른 엣지(지금 찾은 엣지보다 비용이 더 큰 엣지)는 반복을 중단한다.
                    break;
                }
            }
        }
    }
    static class Edge implements Comparable<Edge>{
        int start, end, value;

        Edge(int s, int e, int v) {
            this.start = s;
            this.end = e;
            this.value = v;
        }
        @Override
        public int compareTo(Edge o) {
            // TODO Auto-generated method stub
            return this.value - o.value;
        }
        @Override
        public String toString() {
            return "Edge [start=" + start + ", end=" + end + ", value=" + value + "]\n";
        }
    }
}

/*
6
9
1 2 5
1 3 4
2 3 2
2 4 7
3 4 6
4 6 8
3 5 11
4 5 3
5 6 8
*/

 

 

 

 

'개발자 > Algorithm' 카테고리의 다른 글

Prim's Algorithm  (0) 2020.09.21
Kruskal's Algorithm  (0) 2020.09.14
05. 장기판 포 말잡기  (0) 2020.08.30
04. 토끼 잡기  (0) 2020.08.28
02. 수열 A와 수열 B가 같은지 확인하기  (0) 2020.08.24

Wub.zip
0.45MB

 

아 진짜... 3달간 노트북 2개가 똑같은 증상으로 망가졌다....

심지어 구글에 수두룩하게 나오는 업데이트 후 망가진 나와 동일 증상들.......

포맷 말고 해결조차 안 되던.....

윈도우 업데이트는 하다 망가지는거 이거 진짜 20년 전이나 지금이나 진리인 듯....

차라리 2~3년에 한 번씩 클린설치를 하고 말지...

Paralells에서는 따로 이렇게 설정할 필요가 없었는데... VMware는 일일히 설정을 해줘야 한다;;

게다가 동시지원모드랑 다르게 Unity(유니티) 모드는 성능이 영 꽝이다 ㅠㅠ 그냥 전체화면 모드로만 써야겠다...

아무튼... 손이 많이 가는 VMware지만 Hyper-V보단 덜 가니까;;;;;;; 잡설 그만 하고... 공유폴더 설정하기...

 

해당 가상 머신 이미지 설정에서

Name : 가상 머신에서 보여질 폴더 이름Host path : 호스트 PC에서 공유에 사용할 디렉토리 경로
(주의 : 문서 같은 곳을 잡을 경우 MS 계정 로그인이 되어있다면 원드라이브 동기화 때문에 용량을 차지하거나 호스트와 게스트 모두 로그인 되어 있을 경우 동기화가 자주 깨지게 된다. 공유 드라이브가 아닌 곳을 위치로 잡는다.)

 

이제 가상머신을 부팅하고 들어가면 네트워크 > vmware-host > Shared Folders > 위에서 만든 폴더 이름을 통해 공유가 가능하다. paralles에 비하면 너무도 불편하지만 이게 최선이다 ㅠㅠ 즐겨찾기에 이 디렉토리를 고정하고 사용하자.

www.raymond.cc/blog/10-commercial-disk-imaging-software-features-and-backuprestore-speed-comparison/2/

 

Comparing 20 Drive Imaging Software Backup/Restore Speed and Image Size • Raymond.CC

When Windows or your hard drive fails, it makes sense having a backup image of the whole system to hand. Because of the sizes involved, it's important to have an imaging application that creates a small backup and be fast to do it. Here we test 20 applicat

www.raymond.cc

Acronis True Image는 전체적으로 가장 뛰어난 성능을 보인다. 단, WD Edition은 유료에 비해 성능이 떨어지는 경우가 발생.

Acronis, EaseUs, Macrium은 모두 상위권이다.

무료끼리 비교해보면EaseUs Todo Backup은 백업이 빠르고, Macrium Reflect는 복원이 빠르다.

Paragon은 다소 떨어지는 모습을 보인다.

 

 

 

Tag.

고스트, ghost, Ghost, easyus, easy us, Easyus, Easy Us, EasyUs

크러스컬 : 엣지를 오름차순으로 정렬하고, 비용이 최소인 엣지부터 최종 부모가 같은 노드에 속한지를 검사하며 유효한 노드를 찾아나간다. > 엣지를 이용해서 노드의 최상위 부모를 찾음 > MST 도출

프림 : 임의의 노드를 하나 잡고 그 노드를 중심으로 엣지의 최솟값을 찾는다. > 노드 기준으로 연결된 엣지의 비용과 노드의 방문 여부를 검사 > MST 도출

 

Kruskal's Algorithm2020/09/14 - [개발자/Algorithm] - Kruskal's Algorithm

 

 

Prim.java

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Scanner;

public class Prim {
    static int V, E;                        // V : 점, E : 변
    static ArrayList<Edge> mst;             // result값 Minimum spanning tree
    static LinkedList<Edge>[] graph;        // Kruskal에서는 각 노드를 PriorityQueue<Edge>에 넣었다면, 여기서 graph는 해당 노드가 start인 모든 엣지를 넣는다. 즉, 3번 노드와 연결된 엣지가 5개면 3번 graph[3] start가 3인 엣지를 5개 가지고 있는 배열이 된다.
    static boolean[] visit;                 // 노드의 방문 여부 (Kruskal에서는 int[] arr에 각 노드의 부모가 누구인지를 저장했다.)

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        V = sc.nextInt();
        E = sc.nextInt();

        // 각 노드를 graph로 정의한다. 배열을 V+1개로 만들면 인덱스가 0 ~ V까지 배정되고, 0을 버리고 1 ~ V번 인덱스를 사용한다.
        graph = new LinkedList[V+1];
        // 각 노드의 방문 여부를 boolean 타입의 일차운 단순 배열로 만든다.
        visit =  new boolean[V+1];

        // 1번 노드부터 V번 노드까지 LinkedList<Edge>[] 타입의 graph 변수에 넣는다. (1 ~ 6번 노드가 샘플로 주어진다.)
        for (int i = 1; i <= V; i++) {
            graph[i]= new LinkedList<>();
        }

        mst = new ArrayList<>();

        // 각 노드의 연결 관계와 비용을 입력 받는다. (샘플에서 1 2 5...  1 3 4 에 해당. 엣지가 총 9개 주어졌으니 9번 돈다.)
        for (int i = 0; i < E; i++) {
            int v1 = sc.nextInt();      // 1번 노드와...  1번 노드와
            int v2 = sc.nextInt();      // 2번 노드의...  3번 노드의
            int value = sc.nextInt();   // 비용은 5다...  비용은 4다

            // 노드1에서 노드2로 가는 방향으로 저장.
            graph[v1].add(new Edge(v1,v2,value));
            // 노드2에서 노드1로 가는 방향으로 저장.
            graph[v2].add(new Edge(v2,v1,value));

            // 왜 양방향으로 저장할까?
            // 노드를 기준으로 뻗어 나가는 엣지를 정의하고 그 중 비용이 작은 것을 택하는 것이기 때문에 각 노드에서 뻗어나가는 모든 엣지를 합하면 결국 양방향이 되고 전체 엣지의 2배가 된다.
        }

        // Kruskal에서 while(mst.size() < (V-1)) { }에 해당. 메인 메소드에 다 넣지 않고 빼놓은 것 뿐이다.
        makeMst();
        System.out.println(mst);

    }
    static void makeMst() {
        // 1. 시작 노드는 아무거나 지정.
        // 2. 선택한 노드에 연결된 엣지들을 PrioityQueue에 넣는다. (비용을 기준으로 오름차순 정렬됨.)
        // 3. 비용이 최소인 Edge 객체를 poll()한다. 이 객체는 [start, end, value]를 갖고 있고, end를 visit 검사를 통해 방문 여부를 확인한다.
        // 4. 2~3 반복. n-1개의 간선이 선택할때까지 or 모든 정점이 연결될때까지

        // 노드를 기준으로 연결된 엣지를 담을 PriorityQueue를 하나 인스턴스화 한다. 그러면 해당 노드에 연결된 엣지들을 비용을 기준으로 오름차순 정렬한다.
        PriorityQueue<Edge> pq = new PriorityQueue<>();

        // nowV에 현재 방문중인 노드를 넣어주고, 더이상 방문중인 노드가 없다면 while문을 돌려주기 위해 temp로 사용할 변수로써 큐를 인스턴스화 한다.
        // visit은 각 노드의 방문 여부를 true, false로 저장하는 boolean 타입의 배열로, temp처럼 사용되는게 아닌 전역변수이기 때문에 queue와는 쓰임새가 다르다.
        Queue<Integer> queue = new LinkedList<>();
        // 1번 노드에서 시작하겠다.
        queue.add(1);

        while(!queue.isEmpty()) {
            // 현재 노드
            int nowV = queue.poll();
            // 현재 노드는 방문 했으니 true로 바꾼다.
            visit[nowV] = true;

            // LinkedList<Edge>[] graph에 모든 노드를 담고 시작했다. nowV에 해당하는 노드의 엣지를 하나씩 꺼낸다.
            // 즉, 1번 노드에 엣지가 3개면 각 엣지로 for문을 3번 돌거고, 5번 노드에 엣지가 1개면 for문을 1번 돌고 그런 식이다.
            // 그리고 이 엣지는 nowV라는 노드가 볼 때, start(자기 자신), end(연결된 노드), value(비용)을 가진 객체다.
            // 따라서 이 for문은 주어진 노드에 연결된 엣지를 개수만큼 꺼내 위에서 선언한 pq에 넣어 비용을 기준으로 엣지를 오름차순 정렬시킨다.
            // 이때 해당 노드의 모든 엣지를 무조건 가져올 수도 있찌만 이미 방문한 노드는 다시 계산할 필요가 없으니 if(!visit[edge.end]) { } 를 통해 방문된 노드와 연결된 엣지는 무시한다.
            for (Edge edge : graph[nowV]) {
                // Edge가 start -> end니까. start가 지금 들어온 nowV번 노드고, end가 start에서 뻗어 나간 엣지의 도착 방향의 노드다.
                // 이미 방문된 노드의 엣지는 무시하기 위한 로직.
                // *** 여기서 중요한 것은 방문한 노드가 end인 엣지를 추가하지 않았지만 그게 더 효율적일 수도 있다. 즉, 눈으로 볼 때는 그게 쉽게 보이지만
                // 컴퓨터로 볼 때는 그렇지 않다. 그러면 어떻게 처리를 할까? 방문한 노드 방향의 엣지를 담지 않는 대신 해당 엣지는 이미 이전에 반대편 노드가 start로 들어왔을 때
                // pq에 담겨있고, 그 pq를 매번 돌 때마다 초기화 하지 않기 때문에 유지가 된다. 즉,
                if(!visit[edge.end]) {
                    pq.add(edge);
                }
            }

            // nowV 노드의 엣지를 비용으로 정렬시킨 PriorityQueue가 모두 사라질 때까지 반복한다.
            while(!pq.isEmpty()) {
                // nowV 노드의 엣지중 비용이 작은 것부터 꺼낸다.
                Edge edge = pq.poll();
                // 해당 엣지의 end(nowV와 연결된 노드)가 방문하지 않았다면 if문으로 들어간다.
                if(!visit[edge.end]) {
                    // 연결된 노드를 방문한 노드를 담는 queue에 담는다.
                    // 그냥 nowV = edge.end에 바로 넣어도 될 것 같지만, 더이상 추가할 mst가 없다면 큐가 비어있을거고, while(!queue.isEmpty())를 종료하기 위해서 큐를 사용한다.
                    queue.add(edge.end);
                    // 그리고 해당 노드는 방문 여부를 true로 바꾼다.
                    visit[edge.end] = true;
                    // 그리고 그 엣지를 mst에 넣는다.
                    mst.add(edge);
                    // mst를 찾았으면 해당 nowV의 다른 엣지(지금 찾은 엣지보다 비용이 더 큰 엣지)는 반복을 중단한다.
                    break;
                }
            }
        }
    }
    static class Edge implements Comparable<Edge>{
        int start, end, value;

        Edge(int s, int e, int v) {
            this.start = s;
            this.end = e;
            this.value = v;
        }
        @Override
        public int compareTo(Edge o) {
            // TODO Auto-generated method stub
            return this.value - o.value;
        }
        @Override
        public String toString() {
            return "Edge [start=" + start + ", end=" + end + ", value=" + value + "]\n";
        }
    }
}
// source : https://velog.io/@dudrkdl777/Graph-최소신장트리-MSTPrim알고리즘
/*
6
9
1 2 5
1 3 4
2 3 2
2 4 7
3 4 6
4 6 8
3 5 11
4 5 3
5 6 8
*/

graph는 각 노드마다 연결된 엣지와 비용을 자기 자신을 start로 해서 LinkedList<Edge>에 담는다.

nowV가 2에 있을 때, pq에 edge.end가 방문한 노드인 경우 추가하지 않도록 하는 최적화 알고리즘 때문에 Edge [2, 4, 7]만 추가하게 된다. 사람 눈으로 볼 때는 당연히 3과 4를 잇는게 최선이라는 것을 바로 알 수 있지만 매번 돌 때마다 graph가 담겨있는 pq를 초기화 하면 이것을 계산할 수 없기 때문에 Edge [2, 4, 7]을 mst로 추가하게 된다.

그렇다면 어떻게 Edge [3, 4, 6]을 찾을까? 바로 pq를 초기화 하지 않고 담아두기 때문이다. 이 때 pq = { Edge [1, 2, 5], Edge [3, 4, 6], Edge [3, 5, 11] } 인 상태에서 nowV 2에서 pq에 Edge [2, 4, 7]을 추가하게 되므로,
다시 pq = { Edge [1, 2, 5], Edge [3, 4, 6], Edge [2, 4, 7], Edge [3, 5, 11] } 이 된다. 따라서 Edge [1, 2, 5]는 현재 nowV가 2에 오면서 visit = true로 바꾸었기 때문에 탈락이고, 그 다음 작은 비용인 Edge [3, 4, 6]이 조건을 만족하므로 mst에 추가되게 된다.

nowV = 2일 때, pq를 추가하고 난 뒤의 모습

 

Prim2.java

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Scanner;

public class Prim2 {
    static int V, E;                        // V : 점, E : 변
    static ArrayList<Edge> mst;             // result값 Minimum spanning tree
    static LinkedList<Edge>[] graph;        // Kruskal에서는 각 노드를 PriorityQueue<Edge>에 넣었다면, 여기서 graph는 해당 노드가 start인 모든 엣지를 넣는다. 즉, 3번 노드와 연결된 엣지가 5개면 3번 graph[3] start가 3인 엣지를 5개 가지고 있는 배열이 된다.
    static boolean[] visit;                 // 노드의 방문 여부 (Kruskal에서는 int[] arr에 각 노드의 부모가 누구인지를 저장했다.)

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        V = sc.nextInt();
        E = sc.nextInt();

        // 각 노드를 graph로 정의한다. 배열을 V+1개로 만들면 인덱스가 0 ~ V까지 배정되고, 0을 버리고 1 ~ V번 인덱스를 사용한다.
        graph = new LinkedList[V+1];
        // 각 노드의 방문 여부를 boolean 타입의 일차운 단순 배열로 만든다.
        visit =  new boolean[V+1];

        // 1번 노드부터 V번 노드까지 LinkedList<Edge>[] 타입의 graph 변수에 넣는다. (1 ~ 6번 노드가 샘플로 주어진다.)
        for (int i = 1; i <= V; i++) {
            graph[i]= new LinkedList<>();
        }

        mst = new ArrayList<>();

        // 각 노드의 연결 관계와 비용을 입력 받는다. (샘플에서 1 2 5...  1 3 4 에 해당. 엣지가 총 9개 주어졌으니 9번 돈다.)
        for (int i = 0; i < E; i++) {
            int v1 = sc.nextInt();      // 1번 노드와...  1번 노드와
            int v2 = sc.nextInt();      // 2번 노드의...  3번 노드의
            int value = sc.nextInt();   // 비용은 5다...  비용은 4다

            // 노드1에서 노드2로 가는 방향으로 저장.
            graph[v1].add(new Edge(v1,v2,value));
            // 노드2에서 노드1로 가는 방향으로 저장.
            graph[v2].add(new Edge(v2,v1,value));

            // 왜 양방향으로 저장할까?
            // 노드를 기준으로 뻗어 나가는 엣지를 정의하고 그 중 비용이 작은 것을 택하는 것이기 때문에 각 노드에서 뻗어나가는 모든 엣지를 합하면 결국 양방향이 되고 전체 엣지의 2배가 된다.
        }

        // Kruskal에서 while(mst.size() < (V-1)) { }에 해당. 메인 메소드에 다 넣지 않고 빼놓은 것 뿐이다.
        makeMst();
        System.out.println(mst);

    }
    static void makeMst() {
        // 1. 시작 노드는 아무거나 지정.
        // 2. 선택한 노드에 연결된 엣지들을 PrioityQueue에 넣는다. (비용을 기준으로 오름차순 정렬됨.)
        // 3. 비용이 최소인 Edge 객체를 poll()한다. 이 객체는 [start, end, value]를 갖고 있고, end를 visit 검사를 통해 방문 여부를 확인한다.
        // 4. 2~3 반복. n-1개의 간선이 선택할때까지 or 모든 정점이 연결될때까지

        // 노드를 기준으로 연결된 엣지를 담을 PriorityQueue를 하나 인스턴스화 한다. 그러면 해당 노드에 연결된 엣지들을 비용을 기준으로 오름차순 정렬한다.
        PriorityQueue<Edge> pq = new PriorityQueue<>();

        // nowV에 현재 방문중인 노드를 넣어주고, 더이상 방문중인 노드가 없다면 while문을 돌려주기 위해 temp로 사용할 변수로써 큐를 인스턴스화 한다.
        // visit은 각 노드의 방문 여부를 true, false로 저장하는 boolean 타입의 배열로, temp처럼 사용되는게 아닌 전역변수이기 때문에 queue와는 쓰임새가 다르다.
        Queue<Integer> queue = new LinkedList<>();
        // 1번 노드에서 시작하겠다.
        queue.add(1);

        while(!queue.isEmpty()) {
            // 현재 노드
            int nowV = queue.poll();
            // 현재 노드는 방문 했으니 true로 바꾼다.
            visit[nowV] = true;

            // LinkedList<Edge>[] graph에 모든 노드를 담고 시작했다. nowV에 해당하는 노드의 엣지를 하나씩 꺼낸다.
            // 즉, 1번 노드에 엣지가 3개면 각 엣지로 for문을 3번 돌거고, 5번 노드에 엣지가 1개면 for문을 1번 돌고 그런 식이다.
            // 그리고 이 엣지는 nowV라는 노드가 볼 때, start(자기 자신), end(연결된 노드), value(비용)을 가진 객체다.
            // 따라서 이 for문은 주어진 노드에 연결된 엣지를 개수만큼 꺼내 위에서 선언한 pq에 넣어 비용을 기준으로 엣지를 오름차순 정렬시킨다.
            // 이때 해당 노드의 모든 엣지를 무조건 가져올 수도 있찌만 이미 방문한 노드는 다시 계산할 필요가 없으니 if(!visit[edge.end]) { } 를 통해 방문된 노드와 연결된 엣지는 무시한다.
            for (Edge edge : graph[nowV]) {
                // Edge가 start -> end니까. start가 지금 들어온 nowV번 노드고, end가 start에서 뻗어 나간 엣지의 도착 방향의 노드다.
                // 이미 방문된 노드의 엣지는 무시하기 위한 로직.
                // *** 여기서 중요한 것은 방문한 노드가 end인 엣지를 추가하지 않았지만 그게 더 효율적일 수도 있다. 즉, 눈으로 볼 때는 그게 쉽게 보이지만
                // 컴퓨터로 볼 때는 그렇지 않다. 그러면 어떻게 처리를 할까? 방문한 노드 방향의 엣지를 담지 않는 대신 해당 엣지는 이미 이전에 반대편 노드가 start로 들어왔을 때
                // pq에 담겨있고, 그 pq를 매번 돌 때마다 초기화 하지 않기 때문에 유지가 된다. 즉,
                if(!visit[edge.end]) {
                    pq.add(edge);
                }
            }

            // nowV 노드의 엣지를 비용으로 정렬시킨 PriorityQueue가 모두 사라질 때까지 반복한다.
            while(!pq.isEmpty()) {
                // nowV 노드의 엣지중 비용이 작은 것부터 꺼낸다.
                Edge edge = pq.poll();
                // 해당 엣지의 end(nowV와 연결된 노드)가 방문하지 않았다면 if문으로 들어간다.
                if(!visit[edge.end]) {
                    // 연결된 노드를 방문한 노드를 담는 queue에 담는다.
                    // 그냥 nowV = edge.end에 바로 넣어도 될 것 같지만, 더이상 추가할 mst가 없다면 큐가 비어있을거고, while(!queue.isEmpty())를 종료하기 위해서 큐를 사용한다.
                    queue.add(edge.end);
                    // 그리고 그 엣지를 mst에 넣는다.
                    mst.add(edge);
                    // mst를 찾았으면 해당 nowV의 다른 엣지(지금 찾은 엣지보다 비용이 더 큰 엣지)는 반복을 중단한다.
                    break;
                }
            }
        }
    }
    static class Edge implements Comparable<Edge>{
        int start, end, value;

        Edge(int s, int e, int v) {
            this.start = s;
            this.end = e;
            this.value = v;
        }
        @Override
        public int compareTo(Edge o) {
            // TODO Auto-generated method stub
            return this.value - o.value;
        }
        @Override
        public String toString() {
            return "Edge [start=" + start + ", end=" + end + ", value=" + value + "]\n";
        }
    }
}

코드 중복으로는 지울만한게 별로 없다. 방문 중인 노드 nowV에서 최소의 엣지를 찾은 후 visit 함수를 호출하는 것 하나만 제거했다.(while문 시작하자마자 visit[nowV] = true; 가 있어서 중복되기 때문에)

 

Prim3.java

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.PriorityQueue;
import java.util.Scanner;

public class Prim3 {
    static int V, E;                        // V : 점, E : 변
    static ArrayList<Edge> mst;             // result값 Minimum spanning tree
    static LinkedList<Edge>[] graph;        // Kruskal에서는 각 노드를 PriorityQueue<Edge>에 넣었다면, 여기서 graph는 해당 노드가 start인 모든 엣지를 넣는다. 즉, 3번 노드와 연결된 엣지가 5개면 3번 graph[3] start가 3인 엣지를 5개 가지고 있는 배열이 된다.
    static boolean[] visit;                 // 노드의 방문 여부 (Kruskal에서는 int[] arr에 각 노드의 부모가 누구인지를 저장했다.)

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        V = sc.nextInt();
        E = sc.nextInt();

        // 각 노드를 graph로 정의한다. 배열을 V+1개로 만들면 인덱스가 0 ~ V까지 배정되고, 0을 버리고 1 ~ V번 인덱스를 사용한다.
        graph = new LinkedList[V+1];
        // 각 노드의 방문 여부를 boolean 타입의 일차운 단순 배열로 만든다.
        visit =  new boolean[V+1];

        // 1번 노드부터 V번 노드까지 LinkedList<Edge>[] 타입의 graph 변수에 넣는다. (1 ~ 6번 노드가 샘플로 주어진다.)
        for (int i = 1; i <= V; i++) {
            graph[i]= new LinkedList<>();
        }

        mst = new ArrayList<>();

        // 각 노드의 연결 관계와 비용을 입력 받는다. (샘플에서 1 2 5...  1 3 4 에 해당. 엣지가 총 9개 주어졌으니 9번 돈다.)
        for (int i = 0; i < E; i++) {
            int v1 = sc.nextInt();      // 1번 노드와...  1번 노드와
            int v2 = sc.nextInt();      // 2번 노드의...  3번 노드의
            int value = sc.nextInt();   // 비용은 5다...  비용은 4다

            // 노드1에서 노드2로 가는 방향으로 저장.
            graph[v1].add(new Edge(v1,v2,value));
            // 노드2에서 노드1로 가는 방향으로 저장.
            graph[v2].add(new Edge(v2,v1,value));

            // 왜 양방향으로 저장할까?
            // 노드를 기준으로 뻗어 나가는 엣지를 정의하고 그 중 비용이 작은 것을 택하는 것이기 때문에 각 노드에서 뻗어나가는 모든 엣지를 합하면 결국 양방향이 되고 전체 엣지의 2배가 된다.
        }

        // Kruskal에서 while(mst.size() < (V-1)) { }에 해당. 메인 메소드에 다 넣지 않고 빼놓은 것 뿐이다.
        makeMst();
        System.out.println(mst);

    }
    static void makeMst() {
        // 1. 시작 노드는 아무거나 지정.
        // 2. 선택한 노드에 연결된 엣지들을 PrioityQueue에 넣는다. (비용을 기준으로 오름차순 정렬됨.)
        // 3. 비용이 최소인 Edge 객체를 poll()한다. 이 객체는 [start, end, value]를 갖고 있고, end를 visit 검사를 통해 방문 여부를 확인한다.
        // 4. 2~3 반복. n-1개의 간선이 선택할때까지 or 모든 정점이 연결될때까지

        // 노드를 기준으로 연결된 엣지를 담을 PriorityQueue를 하나 인스턴스화 한다. 그러면 해당 노드에 연결된 엣지들을 비용을 기준으로 오름차순 정렬한다.
        PriorityQueue<Edge> pq = new PriorityQueue<>();

        // 프림 알고리즘을 돌리기 위해 현재 방문중인 노드를 nowV라는 int타입 변수에 넣는다.
        // 1로 임의로 정한 것은 시작점을 1로 하겠다는 의미다. 3에서 시작하고 싶다면 3을 넣으면 된다.
        int nowV = 1;

        while(mst.size() < (V-1)) {
            // 현재 노드는 방문 했으니 true로 바꾼다.
            visit[nowV] = true;

            // LinkedList<Edge>[] graph에 모든 노드를 담고 시작했다. nowV에 해당하는 노드의 엣지를 하나씩 꺼낸다.
            // 즉, 1번 노드에 엣지가 3개면 각 엣지로 for문을 3번 돌거고, 5번 노드에 엣지가 1개면 for문을 1번 돌고 그런 식이다.
            // 그리고 이 엣지는 nowV라는 노드가 볼 때, start(자기 자신), end(연결된 노드), value(비용)을 가진 객체다.
            // 따라서 이 for문은 주어진 노드에 연결된 엣지를 개수만큼 꺼내 위에서 선언한 pq에 넣어 비용을 기준으로 엣지를 오름차순 정렬시킨다.
            // 이때 해당 노드의 모든 엣지를 무조건 가져올 수도 있찌만 이미 방문한 노드는 다시 계산할 필요가 없으니 if(!visit[edge.end]) { } 를 통해 방문된 노드와 연결된 엣지는 무시한다.
            for (Edge edge : graph[nowV]) {
                // Edge가 start -> end니까. start가 지금 들어온 nowV번 노드고, end가 start에서 뻗어 나간 엣지의 도착 방향의 노드다.
                // 이미 방문된 노드의 엣지는 무시하기 위한 로직.
                // *** 여기서 중요한 것은 방문한 노드가 end인 엣지를 추가하지 않았지만 그게 더 효율적일 수도 있다. 즉, 눈으로 볼 때는 그게 쉽게 보이지만
                // 컴퓨터로 볼 때는 그렇지 않다. 그러면 어떻게 처리를 할까? 방문한 노드 방향의 엣지를 담지 않는 대신 해당 엣지는 이미 이전에 반대편 노드가 start로 들어왔을 때
                // pq에 담겨있고, 그 pq를 매번 돌 때마다 초기화 하지 않기 때문에 유지가 된다. 즉,
                if(!visit[edge.end]) {
                    pq.add(edge);
                }
            }

            // nowV 노드의 엣지를 비용으로 정렬시킨 PriorityQueue가 모두 사라질 때까지 반복한다.
            while(!pq.isEmpty()) {
                // nowV 노드의 엣지중 비용이 작은 것부터 꺼낸다.
                Edge edge = pq.poll();
                // 해당 엣지의 end(nowV와 연결된 노드)가 방문하지 않았다면 if문으로 들어간다.
                if(!visit[edge.end]) {
                    // 이제 유효한 엣지를 찾았으니 방문중인 노드 nowV는 다음 노드로 이동한다.
                    nowV = edge.end;
                    // 그리고 그 엣지를 mst에 넣는다.
                    mst.add(edge);
                    // mst를 찾았으면 해당 nowV의 다른 엣지(지금 찾은 엣지보다 비용이 더 큰 엣지)는 반복을 중단한다.
                    break;
                }
            }
        }
    }
    static class Edge implements Comparable<Edge>{
        int start, end, value;

        Edge(int s, int e, int v) {
            this.start = s;
            this.end = e;
            this.value = v;
        }
        @Override
        public int compareTo(Edge o) {
            // TODO Auto-generated method stub
            return this.value - o.value;
        }
        @Override
        public String toString() {
            return "Edge [start=" + start + ", end=" + end + ", value=" + value + "]\n";
        }
    }
}

마지막 노드를 방문한 다음 바로 종료하면 좋겠는데, 마지막 노드 방문 후 현재 방문중인 노드(nowV)를 임시(temp)로 담는 queue가 완전히 비어있을 때까지 돌리는 로직 때문에 마지막 노드에서 혹시 유효한 엣지가 있는지를 검사하며 아직 사용하지 못하고 큐에 남아있는 모든 엣지를 모두 검사한 다음 로직을 종료한다. 즉... 해당 로직은 모든 엣지를 다 검사하고 끝낸다. 굳이......

1 ) while문을 큐가 빌 때 까지 도는 대신 mst의 크기가 (V-1)보다 작을때까지 돌도록 해서, 불필요한 반복을 줄인다.
2 ) 따라서 큐에 넣고 빼는 대신, 바로 nowV에 바로 현재 방문중인 노드를 넣어준다.
사실... 여기서 pq에 담긴 객체 중 start와 end가 모두 visit = true인 것을 바로 제거하면 불필요한 로직을 더 줄일 수 있을 것 같긴 한데, 오히려 이걸 검사하는게 더 많은 로직을 실행해서 비효율적일 것 같아서 그 부분은 건드리지 않았다.

 

Tag.

크러스컬 알고리즘, 크루스칼 알고리즘, kruskal algorithm, kruskal's algorithm, kruskal's, 프림 알고리즘, prim's algorithm, prim's, prim algorithm, Prim Algorithm, Prim's Algorithm

'개발자 > Algorithm' 카테고리의 다른 글

Kruskal & Prim 비교  (0) 2020.09.22
Kruskal's Algorithm  (0) 2020.09.14
05. 장기판 포 말잡기  (0) 2020.08.30
04. 토끼 잡기  (0) 2020.08.28
02. 수열 A와 수열 B가 같은지 확인하기  (0) 2020.08.24

자바 : 알고리즘을 공부하다보니 트리셋 Object를 정렬하는 것을 사용했다. 공통 DB.java 클래스 파일을 이용해 빠르게 프로젝트를 만들어보는 것을 해보고, C태그를 배웠다.

알고리즘 : 역시 자바로 하는거긴 한데... 크러스컬과 프림, 다익스트라를 배웠다. 크러스컬을 수정하면서 느낀건데... 알고리즘은 내가 직접 짜려고 머리 굴리는 것 보다 그냥 잘 짜여진 코드를 인터넷에서 찾아서 그걸 배우는게 좋은 것 같다.

내가 아무리 혼자 끙끙대며 수학 공부해서 만들어봤자 데카르트가 이미 만든 좌표계를 배워서 쓰는게 더 빠르고 정확하고, 내가 아무리 혼자 끙끙대며 전자기학이랑 수학 공부해서 만든다고 해봤자 못 만들 맥스웰 4대 방정식을 만들겠다고 쇼하는 것 보다 맥스웰 4대 방정식 유도를 보고 외워서 배우는 게 낫듯이... 알고리즘도 그냥 수학공식이다~~~ 물리공식이다~~~ 하고 그냥 풀지 말고 보고 외워야 하는 과목인 것 같다. 풀어보겠다 끙끙대면 시간만 많이 쓰고 결과물은 영 시원찮으니...

자바스크립트 & 노드 : 엑셀파일로 만들어 다운로드 하기를 했다.

SQL(오라클) : 수요예측모델 정확도 산출을 위한 6주 아카이브 MAPE를 쿼리로 구현... 거의 다 해간다.

'개발자 > TIL' 카테고리의 다른 글

TIL 20.09.28 ~ 20.10.04  (0) 2020.10.04
TIL 20.09.21 ~ 20.09.25  (0) 2020.09.25
TIL 20.09.08 ~ 20.09.13  (0) 2020.09.13
TIL 20.09.04 ~ 20.09.07  (0) 2020.09.08
TIL 20.09.03  (0) 2020.09.04

재설치를 해보고 다른 버전도 설치해봐도 VMware Tools 설치가 비활성화된다. 게다가 수동으로 설치하려 해도 애초에 windows.iso라는 VMware Tools 설치 이미지 자체가 설치되지 않는다. (다른 버전 포함 재설치만 10번 가까이 해봤다;;)

 

홈페이지에 다운로드도 찾기 힘들어서 정리해서 남긴다.

 

1. VMware-Tools Docs에 접속

https://docs.vmware.com/kr/VMware-Tools/index.html

버전에 속지 말자.

VMware-Tools 버전은 VMware 버전의 숫자를 따라가지 않는다. 따로 논다;;

여기서는 최신 버전만 확인한다... 릴리즈 노트에선 절대로 다운로드 링크를 찾을 수 없다...

 

여기 들어가서 받자...

https://my.vmware.com/web/vmware/downloads/details?downloadGroup=VMTOOLS1115&productId=742

 

VMware Maintenance

Content Not Available Dear user, the web content you have requested is not available.

maintenance.vmware.com

다운로드 받고 압축을 풀면 'windows.iso.sha'라는 파일이 보인다. 이걸 가상 머신 부팅 전에 CD에 넣고 부팅하면 내컴퓨터에서 CD가 들어있는 것을 볼 수 있다. 들어가서 'setup64.exe'를 실행하면 된다.

+ Recent posts