JPA-basic-3-one&two-way-relationship

목표

  • 객체와 테이블 연관관계의 차이를 이해
  • 객체의 참조와 테이블의 외래 키를 매핑
  • 용어 이해
    • 방향(Direction) : 단방향, 양방향
    • 다중성(Multiply) : 다대일, 일대다, 일대일, 다대다 이해
    • 연관관계의 주인(Owner) : 객체 양방향 연관관계는 관라주인이 필요

목차

  • 연관관계가 필요한 이유
  • 단방향 연관관계
  • 양방향 연관관계와 연관관계의 주인
  • 실전 예제 - 2. 연관관계 매핑 시작

연관관계가 필요한 이유

‘객체지향 설계의 목표는 자율적인 객체들의 협력 공동체를 만드는 것이다.’ -조영호 (객체지향의 사실과 오해)

예제 시나리오

  • 회원과 팀이 있다.
  • 회원은 하나의 팀에만 소속될 수 있다.
  • 회원과 팀은 다대일 관계다.

객체를 테이블에 맞추어 모델링

  • 객체 연관관계
    • Member
      • id
      • teamId
      • userName
    • Team
      • id
      • name
  • 테이블 연관관계
    • member
      • member_id(PK)
      • team_id(FK)
      • username
    • team
      • team_id(PK)
      • name

객체를 테이블에 맞추어 모델링 (참조 대신에 외래 키를 그대로 사용)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Entity
public class Member {
@Id @GeneratedValue
private Long id;

@Column(name = "username")
private String name;

@Column(name = "team_id")
private Long teamId;
....
}

@Entity
public class Team {
@Id @GeneratedValue
private Long id;
private String name;
...
}

객체를 테이블에 맞추어 모델링(외래 키 식별자를 직접 다룸)

1
2
3
4
5
6
7
8
9
10
// 팀 저장
Team team = new Team();
team.setName("TeamA");
em.persist(team);

// 회원 저장
Member member = new Member();
member.setName("member1");
member.setTeamId(team.getId());
em.persist(member);

객체를 테이블에 맞추어 모델링(식별자로 다시 조회, 객체지향적인 방법은 아니다.)

1
2
3
4
5
//조회
Member findMember = em.find(Member.class, member.getId());

//연관관계가 없음
Team findTeam = em.find(Team.class, team.getId());

객체를 테이블에 맞추어 데이터 중심으로 모델링하면, 협력관계를 만들 수 없다.

  • 테이블은 외래 키로 조인을사용해서 연관된 테이블을 찾는다.
  • 객체는 참조를 사용해서 연관된 객체를 찾는다.
  • 테이블과 객체 사이에는 이런 큰 간격이 있다.

단방향 연관관계

객체 지향 모델링(객체 연관관계 사용)

  • 객체 연관관계
    • Member
      • id
      • Team team
      • username
    • Team
      • id
      • name
  • 테이블 연관관계
    • member
      • member_id(PK)
      • team_id(FK)
      • username
    • team
      • team_id(PK)
      • name

객체 지향 모델링(객체의 참조와 테이블의 외래키를 매핑)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Entity
public class Member{
@Id @GeneratedValue
private Long id;

@Column(name = "USERNAME")
private String name;
private int age;
// @Column(name = "TEAM_ID")
// private Long teamId;

@ManyToOne
@JoinColumn(name = "team_id")
private Team team;
}

객체 지향 모델링 (ORM 매핑)

  • 객체 연관관계
    • Member
      • id
      • Team team(TEAM_ID(FK))
      • username
    • Team
      • id
      • name
  • 테이블 연관관계
    • member
      • member_id(PK)
      • team_id(FK) (Team team)
      • username
    • team
      • team_id(PK)
      • name

객체 지향 모델링 (연관관계 저장)

1
2
3
4
5
6
7
8
9
10
//팀 저장
Team team = new Team();
team.setName("TeamA");
em.persist(team);

//회원 저장
Member member = new Member();
member.setName("member1");
member.setTeam(team); // 단방향 연관관계 설정, 참조 저장
em.persist(member);

객체 지향 모델링 (참조로 연관관계 조회 - 객체 그래프 탐색)

1
2
3
4
5
//조회
Member findMember = em.find(Member.class, member.getId());

//참조를 사용해서 연관관계 조회
Team findTeam = findMember.getTeam();

객체 지향 모델링(연관관계 수정)

1
2
3
4
5
6
7
// 새로운 팀B
Team teamB = new Team();
teamB.setName("TeamB");
em.persist(teamB);

// 회원1에 새로운 팀B 설정
member.setTeam(teamB);

양방향 연관관계와 연관관계의 주인

양방향 매핑

  • 양방향 객체 연관관계
    • Member
      • id
      • Team team (team_id(FK))
      • username
    • Team
      • id
      • name
      • List members
  • 테이블 연관관계
    • member
      • member_id(PK)
      • team_id(FK) (Team team)
      • username
    • team
      • team_id(PK)
      • name

양방향 매핑 (Member 엔티티는 단방향과 동일)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Entity
public class Member {

@Id @GeneratedValue
private Long id;

@Column(name = "username")
private String name;
private int age;

@ManyToOne
@JoinColumn(name = "team_id")
private Team team;
...

양방향 매핑 (Team 엔티티는 컬렉션 추가)

1
2
3
4
5
6
7
8
9
10
11
@Entity
public class Team {
@Id @GeneratedValue
private Long id;

private String name;

@OneToMany(mappedBy = "team")
List<Member> members = new ArrayList<Member>();
...
}

양방향 매핑 (반대 방향으로 객체 그래프 탐색)

1
2
3
4
// 조회
Team findTeam = em.find(Team.class, team.getId());

int memberSize = findTeam.getMembers().size(); //역방향 조회

연관관계의 주인과 mappedBy

  • mappedBy = JPA의 멘탈 붕괴급 난이도
  • mappedBy는 처음에는 이해하기 어렵다
  • 객체와 테이블간에 연관관계를 맺는 차이를 이해해야 한다.

객체와 테이블이 관계를 맺는 차이

  • 객체 연관관계 = 2개
    • 회원 → 팀 연관관계 1개(단방향)
    • 팀 → 회원 연관관계 1개(단방향)
  • 테이블 연관관계 = 1개
    • 회원 ←→ 팀의 연관관계 1개(양방향)

객체와 테이블이 관계를 맺는 차이

  • 양방향 객체 연관관계
    • Member
      • id
      • Team team
      • username
    • Team
      • id
      • name
      • List members
  • 테이블 연관관계
    • member
      • member_id(PK)
      • team_id(FK)
      • username
    • team
      • team_id(PK)
      • name

객체의 양방향 관계

  • 객체의 양방향 관계는 사실 양방향 관계가 아니라 서로 다른 단뱡향 관계 2개다.
  • 객체를 양방향으로 참조하려면 단방향 연관관계를 2개 만들어야 한다.
  • A→B (a.getB())
  • B→A (b.getA())

테이블의 양방향 연관관계

  • 테이블은 외래 키 하나로 두 테이블의 연관관계를 관리
  • member.team_id 외래 키 하나로 양방향 연관관계를 가짐 (양쪽으로 조인할 수 있다.)
1
2
3
4
5
6
7
select *
from member m
join team t on m.team_id = t.team_id

select *
from team t
join member m on t.team_id = m.team_id

둘 중 하나로 외래 키를 관리해야 한다.

연관관계의 주인(Owner)

양방향 매핑 규칙

  • 객체의 두 관계중 하나를 연관관계의 주인으로 지정
  • 연관관계의 주인만이 외래 키를 관리(등록, 수정)
  • 주인이 아닌쪽은 읽기만 가능
  • 주인은 mappedBy 속성 사용x
  • 주인이 아니면 mappedBy 속성으로 주인 지정

누구를 주인으로 ?

  • 외래키가 있는 곳을 주인으로 정해라
  • 여기서는 Member.team 이 연관관계의 주인

양방향 매핑 시 가장 많이 하는 실수

(연관관계의 주인에 값을 입력하지 않음)

1
2
3
4
5
6
7
8
9
10
11
Team team = new Team();
team.setName("TeamA");
em.persist(team);

Member member = new Member();
member.setName("member1");

//역방향(주인이 아닌 방향)만 연관관계 설정
team.getMembers().add(member);

em.persisit(member);

양방향 매핑시 연관관계의 주인에 값을 입력해야 한다.

(순수한 객체 관계를 고려하면 항상 양쪽 다 값을 입력해야 한다.)

1
2
3
4
5
6
7
8
9
10
11
12
Team team = new Team();
team.setName("TeamA");
em.persist(team);

Member member = new Memeber();
member.setName("member1");

team.getMembers().add(member);
//연관관계의 주인에 값 설정
member.setTeam(team); //**

em.persist(member);

양방향 연관관계 주의

  • 순수 객체 상태를 고려해서 항상 양쪽에 값을 설정하자
  • 연관관계 편의 메소드를 생성하자
  • 양방향 매핑시에 무한 루프를 조심하자

양방향 매핑 정리

  • 단방향 매핑만으로도 이미 연관관계 매핑은 완료
  • 양방향 매핑은 반대 방향으로 조회(객체 그래프 탐색) 기능이 추가된 것 뿐
  • JPQL에서 역방향으로 탐색할 일이 많음
  • 단방향 매핑을 잘 하고 양방향은 필요할 때 추가해도 됨(테이블에 영향을 주지 않음)

연관관계의 주인을 정하는 기중

  • 비즈니스 로직을 기준으로 연관관계의 주인을 선택하면 안됨
  • 연관관계의 주인은 외래 키의 위치를 기준으로 정해야함.

JPA_basic -2- Objec&Table mapping

@Entity

  • @Entity가 붙은 클래스는 JPA가 관리, 엔티티라 한다.
  • JPA를 사용해서 테이블과 매핑할 클래스는 @JPA필수
  • 주의
    • 기본 생성자 필수(파라미터가 없는 public 혹은 protected 생성)
    • final 클래스, enum, interface, inner 클래스 사용 x
    • 저장할 필드에 final 사용 x

@Entity 속성 관리

  • 속성 : name
    • JPA에서 사용할 엔티티 이름을 지정한다.
    • 기본값 : 클래스 이름을 그대로 사용(예 : Member)
    • 같은 클래스 이름이 없으면 가급적 기본값을 사용한다.

@Table

  • @Table은 엔티티와 매핑할 테이블 지정
    • name : 매핑할 테이블 이름, 기본값 : 엔티티 이름을 그대로 사용
    • catalog : 데이터베이스 catalog 매핑
    • schema : 데이터베이스 schema 매핑
    • uniqueConstraints : DDL 생성 시에 유니크 제약 조건 생성

데이터베이스 스키마 자동 생성

  • DDL을 애플리케이션 실행 시점에 자동 생성
  • 테이블 중심 -> 객체 중심
  • 데이터 베이스 방언을 활용해서 데이터 베이스에 맞는 적절한 DDL 생성
  • 이렇게 생성된 DDL은 개발 장비에서만 사용
  • 생성된 DDL은 운영서버에서는 사용하지 않거나, 적절히 다듬은 후 사용

데이터베이스 스키마 자동 생성 - 속성

hibernate.hbm2ddl.auto

  • create : 기존테이블 삭제 후 다시 생성 (DROP + CREATE)
  • create-drop : create와 같으나 종료 시점에 테이블 DROP
  • update: 변경분만 반영(운영 DB에는 사용 x)
  • validate : 엔티티와 테이블 정상 매핑 되었는지만 확인
  • none : 사용하지 않음

데이터베이스 스키마 자동 생성 -주의

  • 운영 장비에는 절대 create, create-drop, update를 사용하면 안된다.
  • 개발 초기 단계는 create 또는 update
  • 테스트 서버는 update 또는 validate
  • 스테이징과 운영 서버는 validate 또는 none

필드와 컬럼 매핑

  • 회원은 일반 회원과 관리자로 구분해야한다.
  • 회원 가입일과 수정일이 있어야 한다.
  • 회원을 설명할 수 있는 필드가 있어야 한다. 이 필드는 길이 제한이 없다.

매핑 어노테이션 정리

  • @Column : 컬럼 매핑
  • @Temporal : 날짜 타입 매핑
  • @Enumerated : enum 타입 매핑
  • @Lob : BLOB, CLOB 매핑
  • @Transient : 특정 필드를 컬럼에 매핑하지 않음(매핑 무시)

@Column

  • name : 필드와 매핑할 테이블의 컬럼 이름 (기본값 : 객체의 필드이름)
  • insertable, updatable : 등록, 변경 가능 여부(기본 : TRUE)
  • nullable(DDL) : null 값의 허용여부를 설정한다. false로 설정하면 DDL 생성 시 not null 제약조건이 붙는다.
  • unique(DDL) : @TableuniqueConstraints와 같지만 한 컬럼에 간단히 유니크 제약조건을 걸 때 사용한다.
  • columnDefinition(DDL) : 데이터베이스 컬럼 정보를 직접 줄 수 있다.(필드의 자바 타입과 방언 정보를 사용)
    • ex: varchar(100) default 'EMPTY'
  • length(DDL) : 문자 길이 제약조건, String 타입에만 사용한다. (기본 255)
  • precision, scale(DDL) : BigDecimal 타입에서 사용한다.(BigInteger도 사용할 수 있다.) precision은 소수점을 포함한 전체 자릿수를 scale은 소수의 자리수다. 참고로 double, float 타입에는 적용되지 않는다. 아주 큰 숫나자 정밀한 소수를 다루어야 할 때만 사용한다.

@Enumerated

  • 자바 enum 타입을 매핑할 때 사용

주의! ORDINAL 사용x

  • value
    • EnumType.ORDINAL : enum 순서를 데이터베이스에 저장
    • EnumType.STRING : enum 이름을 데이터베이스에 저장
    • 기본값 : EnumType.ORDINAL

@Temporal (hibernate 최신 버전에는 안써도 됨.)

  • 날짜 타입(java.util.Date, java.util.Calendar)을 매핑할 때 사용
  • 참고 : LocalDate, LocalDateTime을 사용할 때는 생략 가능(최신 하이버네이트 지원)
  • value
    • TemporalType.DATE : 날짜, 데이터베이스 data 타입과 매핑(예 2013-10-11)
    • TemporalType.TIME : 시간, 데이터베이스 time 타입과 매핑(예 11:11:11)
    • TemporalType.TIMESTAMP : 날짜와 시간, 데이터베이스 timestamp 타입과 매핑 (예 2013-10-11 11:11:11)

@Lob

데이터베이스 BLOB, CLOB 타입과 매핑

  • @Lob에는 지정할 수 있는 속성이 없다.
  • 매핑하는 필드 타입이 문자면 CLOB 매핑, 나머지는 BLOB 매핑
    • CLOB : String, char[], java.sql.CLOB
    • BLOB : byte[], java.sql.BLOB

기본 키 매핑 어노테이션

  • @Id
  • @GeneratedValue
1
2
@Id @GeneratedValue(strategy = GenerationType.Auto)
private Long id;

기본 키 매핑 방법

  • 직접 할당 : @Id만 사용
  • 자동 생성(@GeneratedValue)
    • IDENTITY : 데이터베이스에 위임, MYSQL
    • SEQUENCE : 데이터베이스 시퀀스 오브젝트 사용, ORACLE
      • @SequenceGenerator 필요
    • TABLE : 키 생성용 테이블 사용, 모든 DB에서 사용
      • @TableGenerator 필요
    • AUTO : 방언에 따라 자동 지정, 기본값

직접 할당

  • @Id

IDENTITY 전략 - 특징

  • 기본 키 생성을 데이터베이스에 위임
  • 주로 MYSQL, PostreSQL, SQL Server, DB2에서 사용(예: MySQL의 AUTO_INCREMENT)
  • JPA는 보통 트랜잭션 커밋 시점에 INSERT SQL 실행
  • AUTO_INCREMENT는 데이터베이스에 INSERT SQL을 실행한 이후에 ID 값을 알 수 있음
  • IDENTITY 전략은 em.persist() 시점에 즉시 INSERT SQL 실행하고 DB에서 식별자를 조회
  • 모아놨다가 한꺼번에 실행하는 전략은 사용 불가

IDENTITY 전략 - 매핑

1
2
3
4
5
@Entity
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

SEQUENCE 전략 - 특징

  • 데이터베이스 시퀀스는 유일한 값을 순서대로 생성하는 특별한 데이터베이스 오브젝트(예: 오라클 시퀀스)
  • 오라클, PostgreSQL, DB2, H2 데이터베이스에서 사용

SEQUENCE 전략 - 매핑

1
2
3
4
5
6
7
8
9
10
11
@Entity
@SequenceGenerator(
name = "MEMBER_SEQ_GENERATOR",
sequenceName = "MEMBER_SEQ",
initialValue = 1, allocationSize = 1)
public class Member {

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE,
generator = "MEMBER_SEQ_GENERATOR")
private Long id;

SEQUENCE - @SequenceGenerator

  • 주의 : allocationSize 기본값 = 50
  • name : 식별자 생성기 이름 (필수)
  • sequenceName : 데이터베이스에 등록되어 있는 시퀀스 이름(기본 : hibernate_sequence)
  • initialValue : DDL 생성 시에만 사용됨, 시퀀스 DDL을 생성할 때 처음 1 시작하는 수를 지정한다.(기본 : 1)
  • allocationSize : 시퀀스 한 번 호출에 증가한느 수 (성능 최적화에 사용됨 데이터 시퀀스 값이 하나씩 증가하도록 설정되어 있으면 이 값을 반드시 1로 설정해야 한다.)(기본 값: 50)

TABLE 전략

  • 키 생성 전용 테이블을 하나 만들어서 데이터베이스 시퀀스를 흉내내는 전략
  • 장점 : 모든 데이터베이스에 적용 가능
  • 단점 : 성능

TABLE 전략 - 매핑

1
2
3
4
5
create table MY_SEQUENCES (
sequence_name varchar(255) not null,
next_val bigint,
primary key (sequence_name)
)
1
2
3
4
5
6
7
8
9
10
@Entity
@TableGenerator(
name = "MEMBER_SEQ_GENERATOR",
table = "MY_SEQUENCES",
pkColumnValue = "MEMBER_SEQ", allocationSize = 1)
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.TABLE,
generator = "MEMBER_SEQ_GENERATOR")
private Long id;

TableGenerator - 속성

  • name : 식별자 생성기 이름 (필수)
  • table : 키생성 테이블명 (기본 hibernate_sequences)
  • pkColumnName : 시퀀스 컬럼명 (기본 sequence_name)
  • valueColumnNa : 시퀀스 값 컴럼명 (기본 next_val)
  • pkColumnValue : 키로 사용할 값 이름 (엔티티 이름)
  • initialValue : 초기 값, 마지막으로 생성된 값이 기준이다.(기본 0)
  • allocationSize : 시퀀스 한 번 호출에 증가하는 수(성능 최적화에 사용됨) (기본 50)
  • catalog, schema : 데이터베이스 catalog, schema 이름
  • uniqueConstraint : 유니크 제약 조건을 지정할 수 있다.

권장하는 식별자 전략

  • 기본 키 제약 조건 : null 아님, 유일, 변하면 안된다.
  • 미래까지 이 조건을 만족하는 자연키는 찾기 어렵다. 대리키(대체키)를 사용하자.
  • 예를 들어 주민등록번호도 기본 키로 적절하지 않다.
  • 권장 : Long형 + 대체키 + 키 생성전략 사용.

JPA_basic -1- Entity persistence

JPA에서 가장 중요한 2가지

  1. 객체와 관계형 데이터베이스 매핑하기
    (Object Relational Mapping)

  2. 영속성 컨텍스트

영속성 컨텍스트

  • JPA를 이해하는데 가장 중요한 용어
  • “엔티티를 영구 저장하는 환경”
  • EntityManager.persist(entity);

엔티티 매니저? 영속성 컨텍스트?

  • 영속성 컨텍스트는 논리적인 개념
  • 눈에 보이지 않는다.
  • 엔티티 매니저를 통해서 영속성 컨텍스트에 접근

J2SE 환경

엔티티 매니저와 영속성 컨텍스트가 1:1

J2EE, 스프링 프레임워크와 같은 컨테이너 환경

엔티티 매니저와 영속성 컨텍스트가 N:1

엔티티의 생명주기

  • 비영속 (new/transient) : 영속성 컨텍스트와 전혀 관계가 없는 새로운 상태
  • 영속 (managed) : 영속성 컨텍스트에 관리되는 상태
  • 준영속 (detached) : 영속성 컨텍스트에 저장되었다가 분리된 상태
  • 삭제 (removed) : 삭제된 상태

비영속

: 아무것도 아닌 상태

영속

: persist 에 집어넣은 상태

준영속, 삭제

영속성 컨텍스트의 이점

  • 1차 캐시 : 한번 조회한건 쿼리로 날아가지 않는다.(동일한 트랜잭션 안에서)
  • 동일성 보장
  • 트랜잭션을 지원하는 쓰기 지연 : 버퍼링 - write을 모았다가 한번에 write
  • 변경 감지(Dirty Checking) : update 할때, JPA가 자동으로 인식해서 flush 해주는 것.
  • 지연 로딩(Lazy Loading)

엔티티 조회, 1차캐시

1차 캐시 : 한번 조회한건 쿼리로 날아가지 않는다.(동일한 트랜잭션 안에서)

플러시

영속성 컨텍스트의 변경내용을 데이터베이스에 반영.

플러시 발생

  • 변경 감지
  • 수정된 엔티티 쓰기 지연 SQL 저장소에 등록
  • 쓰기 지연 SQL 저장소의 쿼리슷 데이터베이스에 전송 (등록, 수정, 삭제 쿼리)

플러시 모드 옵션

1
em.setFlushMode(flushModeType.COMMIT);
  • FlushModeType.AUTO : 커밋이나 쿼리를 실행할 때 플러시 (기본값, 거의 이것만 씀)
  • FlushModeType.COMMIT : 커밋할 때만 플러시 (활용성 거의 0)

플러시는!

  • 영속성 컨텍스트를 비우지 않는다.
  • 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화
  • 트랜잭션이라는 작업 단위가 중요 -> 커밋 직전에만 동기화 하면 됨.

준영속 상태

  • 영속 -> 준영속
  • 영속 상태의 엔티티가 영속성 컨텍스트에서 분리(detached).
  • 영속성 컨텍스트가 제공하는 기능을 사용 못함.

준영속 상태로 만드는 방법

1
2
// 특정 엔티티만 준영속 상태로 전환
em.detach(entity);
1
2
// 영속성 컨텍스트를 완전히 초기화
em.clear();
1
2
// 영속성 컨텍스트를 종료
em.close();

출처 : 김영한 - 자바 ORM 표준 JPA 프로그래밍 - 기본편

algorithm-7-h-rod-cutting

전화번호 목록

문제 설명

길이가 N인 막대기가 있다. 막대기를 길이가 자연수가 되도록 여러 조각으로 자를 수 있다.

막대기는 자르는 길이에 따라 값이 정해져 있다. 막대를 자르는 값어치의 합이 최대가 되게끔 막대기를 자르자.

예를 들어, 길이가 4인 막대기를 자를 때, 길이 별 받을 수 있는 값이 아래와 같다고 하자.
Length | 1 | 2 | 3 | 4
———— | ————- | ————- | ————- | ————-
Cost | 1 | 5 | 8 | 9

이 경우에 길이 2에 cost 5인 막대기 두 개가 되게끔 자르면 전체 값어치가 10으로 최대로 값을 받을 수 있을 것이다.

첫 줄에 막대기 길이 N(1<= N <= 3000)이 주어지고,

두번째 줄에 1000이하의 자연수 N 개가 공백으로 구분되어 주어진다. i번째 자연수는 길이 i 막대기의 값을 의미한다.

첫 줄에 값어치 합이 최대가 되도록 막대기를 자를 때, 값어치 합을 출력한다.

예제 입력 :

4

1 5 8 9

예제 출력 : 10

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package com.programmers.algorithm;

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

public class RodCutting {
private static int N;
private static BufferedReader br;
private static StringTokenizer st;

private static int [] partPrice;
private static int [] maxPricePerLength;

public static void main(String[] args) throws NumberFormatException, IOException {

br = new BufferedReader(new InputStreamReader(System.in));

N = Integer.parseInt(br.readLine());

partPrice = new int [N+1];
maxPricePerLength = new int [N+1];

st = new StringTokenizer(br.readLine(), " ");
for (int i = 1; i <= N; i++) {
partPrice[i] = Integer.parseInt(st.nextToken());
}

br.close();

for (int i = 1; i <= N; i++) {
int max = 0;
for (int j = 1; j <= i; j++) {
max = Math.max(max, partPrice[j] + maxPricePerLength[i-j]);
}
maxPricePerLength[i] = max;
}

System.out.println(maxPricePerLength[N]);
}
}

algorithm-6-h-index

H-Index 출처: 프로그래머스

문제 설명

H-Index는 과학자의 생산성과 영향력을 나타내는 지표입니다. 어느 과학자의 H-Index를 나타내는 값인 h를 구하려고 합니다. 위키백과1에 따르면, H-Index는 다음과 같이 구합니다.

어떤 과학자가 발표한 논문 n편 중, h번 이상 인용된 논문이 h편 이상이고 나머지 논문이 h번 이하 인용되었다면 h의 최댓값이 이 과학자의 H-Index입니다.

어떤 과학자가 발표한 논문의 인용 횟수를 담은 배열 citations가 매개변수로 주어질 때, 이 과학자의 H-Index를 return 하도록 solution 함수를 작성해주세요.

제한 사항

  • 과학자가 발표한 논문의 수는 1편 이상 1,000편 이하입니다.
  • 논문별 인용 횟수는 0회 이상 10,000회 이하입니다.

입출력 예

citations return
[3, 0, 6, 1, 5] 3

입출력 예 설명

이 과학자가 발표한 논문의 수는 5편이고, 그중 3편의 논문은 3회 이상 인용되었습니다. 그리고 나머지 2편의 논문은 3회 이하 인용되었기 때문에 이 과학자의 H-Index는 3입니다.

문제인식

  1. h-index 설명에 의하면, 인용된 수를 내림차순으로 정렬한 후, 피인용수가 논문수와 같아지거나 피용수가 논문 수보다 더 작아지는 지점을 h-index라고 한다.

해결방향

  1. 인용수가 증가하다가 감소하는 ‘최소값’을 찾으면 된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.util.Arrays;
import java.util.Comparator;
import java.util.Collections;

class Solution {
public int solution(int[] citations) {
int answer = 0;
Integer[] num = new Integer[citations.length];
for(int i = 0; i < citations.length; i++){
num[i] = citations[i];
}

Arrays.sort(num, Collections.reverseOrder());
while(answer < citations.length){
if(num[answer] <= answer){
break;
}
answer++;
}

return answer;
}
}

algorithm-5-disk-controller

디스크컨트롤러 출처: 프로그래머스

문제 설명

하드디스크는 한 번에 하나의 작업만 수행할 수 있습니다. 디스크 컨트롤러를 구현하는 방법은 여러 가지가 있습니다. 가장 일반적인 방법은 요청이 들어온 순서대로 처리하는 것입니다.

예를들어

1
2
3
- 0ms 시점에 3ms가 소요되는 A작업 요청
- 1ms 시점에 9ms가 소요되는 B작업 요청
- 2ms 시점에 6ms가 소요되는 C작업 요청

와 같은 요청이 들어왔습니다. 이를 그림으로 표현하면 아래와 같습니다.
디스크 컨트롤러

한 번에 하나의 요청만을 수행할 수 있기 때문에 각각의 작업을 요청받은 순서대로 처리하면 다음과 같이 처리 됩니다.

디스크 컨트롤러2

1
2
3
- A: 3ms 시점에 작업 완료 (요청에서 종료까지 : 3ms)
- B: 1ms부터 대기하다가, 3ms 시점에 작업을 시작해서 12ms 시점에 작업 완료(요청에서 종료까지 : 11ms)
- C: 2ms부터 대기하다가, 12ms 시점에 작업을 시작해서 18ms 시점에 작업 완료(요청에서 종료까지 : 16ms)

이 때 각 작업의 요청부터 종료까지 걸린 시간의 평균은 10ms(= (3 + 11 + 16) / 3)가 됩니다.

하지만 A → C → B 순서대로 처리하면

디스크 컨트롤러3

1
2
3
- A: 3ms 시점에 작업 완료(요청에서 종료까지 : 3ms)
- C: 2ms부터 대기하다가, 3ms 시점에 작업을 시작해서 9ms 시점에 작업 완료(요청에서 종료까지 : 7ms)
- B: 1ms부터 대기하다가, 9ms 시점에 작업을 시작해서 18ms 시점에 작업 완료(요청에서 종료까지 : 17ms)

이렇게 A → C → B의 순서로 처리하면 각 작업의 요청부터 종료까지 걸린 시간의 평균은 9ms(= (3 + 7 + 17) / 3)가 됩니다.

각 작업에 대해 [작업이 요청되는 시점, 작업의 소요시간]을 담은 2차원 배열 jobs가 매개변수로 주어질 때, 작업의 요청부터 종료까지 걸린 시간의 평균을 가장 줄이는 방법으로 처리하면 평균이 얼마가 되는지 return 하도록 solution 함수를 작성해주세요. (단, 소수점 이하의 수는 버립니다)

제한 사항

  • jobs의 길이는 1 이상 500 이하입니다.
  • jobs의 각 행은 하나의 작업에 대한 [작업이 요청되는 시점, 작업의 소요시간] 입니다.
  • 각 작업에 대해 작업이 요청되는 시간은 0 이상 1,000 이하입니다.
  • 각 작업에 대해 작업의 소요시간은 1 이상 1,000 이하입니다.
  • 하드디스크가 작업을 수행하고 있지 않을 때에는 먼저 요청이 들어온 작업부터 처리합니다.

입출력 예

jobs Second Header
[[0, 3], [1, 9], [2, 6]] 9

입출력 예 설명

문제에 주어진 예와 같습니다.

  • 0ms 시점에 3ms 걸리는 작업 요청이 들어옵니다.
  • 1ms 시점에 9ms 걸리는 작업 요청이 들어옵니다.
  • 2ms 시점에 6ms 걸리는 작업 요청이 들어옵니다.

문제인식

  1. ‘하드디스크가 작업을 수행하고 있지 않을 때에는 먼저 요청이 들어온 작업부터 처리합니다.’
    => 시작은 작업 요청 시점이 0인 작업부터, 바로 우선순위 큐로 정렬하면, 이 규칙에 어긋난다.
  2. 평균 처리시간을 줄이기 위해선, 작업 요청 시점 순으로 처리하는 것보다, 작업 처리시간의 적은 것부터 처리하는것이 더 효율적이다.

해결방향

  1. 문제인식 1번 부분을 충족하기 위해, 우선 주어진 배열을 ‘작업요청시점’의 오름차순으로 정렬한다.
  2. 문제인식 2번 부분을 충족하기 위한, 우선순위 큐의 인스턴스를 생성하고, ‘작업처리시간’의 오름차순의 정렬기준을 준다.
  3. 문제를 해결하기 위해, 반복문을 선언하는데, 종료 기준을 주어진 배열의 맨끝 인덱스까지, 혹은 큐가 비어있지 않을 때까지로 정한다.
  4. 3번의 반복문 안에 큐에 디스크 작업을 넣는 반복문을 선언하는데, 종료 기준을 ‘작업요청 시점’이하 그리고, 인덱스 번호가 배열의 길이미만 일때 까지로 정한다.
  5. 3번 반복문이 도는 동안, 4번 반복문을 통해 큐에 jobs 데이터가 삽입된다.
  6. 만약 큐가 비어있다면, ‘작업 시작점’에 가장 최근 ,jobs 데이터의 ‘작업요청 시점’을 넣는다.
  7. 큐가 비어있지 않다면, 큐의 데이터를 꺼낸다. 작업 처리시간은 이전작업 완료 시점 - 작업이 요청되는 시점 + 작업의 처리시간 로 이 공식대로 총 작업 처리시간을 구한다.
  8. 모든 디스크 작업의 처리시간 합계를 구하고 평균을 구한 뒤, 그 정답을 리턴한다.

주의사항

  • 대기시간 역시 작업 처리시간에 포함해야 한다. 즉, 7번의 작업 처리시간 공식은 대기사간 + 작업처리시간이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.programmers.algorithm;

import java.util.Arrays;
import java.util.PriorityQueue;
import java.util.Queue;

public class DiskController {
public int solution(int[][] jobs) {
int answer = 0;
int idx = 0;
int timePoint = 0;
int length = jobs.length;

Queue<int[]> queue = new PriorityQueue<>(((o1, o2) -> o1[1] - o2[1]));
Arrays.sort(jobs, (o1, o2) -> o1[0] - o2[0]);

while(idx < length || ! queue.isEmpty()){
while(idx < length && jobs[idx][0] <= timePoint) queue.offer(jobs[idx++]);

if(queue.isEmpty()) timePoint = jobs[idx][0];
else {
int[] job = queue.poll();
answer += timePoint - job[0] + job[1];
timePoint += job[1];
}
}

return answer/length;
}

}

class DiskControllerTest {
public static void main(String[] args) {
int[][] jobs = {{0, 3}, {1,9}, {2, 6}};
DiskController diskController = new DiskController();
System.out.println(diskController.solution(jobs));
}
}

algorithm-4-spicy

더 맵게 출처: 프로그래머스

문제 설명

매운 것을 좋아하는 Leo는 모든 음식의 스코빌 지수를 K 이상으로 만들고 싶습니다. 모든 음식의 스코빌 지수를 K 이상으로 만들기 위해 Leo는 스코빌 지수가 가장 낮은 두 개의 음식을 아래와 같이 특별한 방법으로 섞어 새로운 음식을 만듭니다.

1
섞은 음식의 스코빌 지수 = 가장 맵지 않은 음식의 스코빌 지수 + (두 번째로 맵지 않은 음식의 스코빌 지수 * 2)

Leo는 모든 음식의 스코빌 지수가 K 이상이 될 때까지 반복하여 섞습니다.
Leo가 가진 음식의 스코빌 지수를 담은 배열 scoville과 원하는 스코빌 지수 K가 주어질 때, 모든 음식의 스코빌 지수를 K 이상으로 만들기 위해 섞어야 하는 최소 횟수를 return 하도록 solution 함수를 작성해주세요.

제한 사항

  • scoville의 길이는 2 이상 1,000,000 이하입니다.
  • K는 0 이상 1,000,000,000 이하입니다.
  • scoville의 원소는 각각 0 이상 1,000,000 이하입니다.
  • 모든 음식의 스코빌 지수를 K 이상으로 만들 수 없는 경우에는 -1을 return 합니다.

입출력 예

scoville K return
[1, 2, 3, 9, 10, 12] 7 2

입출력 예 설명

  1. 스코빌 지수가 1인 음식과 2인 음식을 섞으면 음식의 스코빌 지수가 아래와 같이 됩니다. 새로운 음식의 스코빌 지수 = 1 + (2 * 2) = 5 가진 음식의 스코빌 지수 = [5, 3, 9, 10, 12]

  2. 스코빌 지수가 3인 음식과 5인 음식을 섞으면 음식의 스코빌 지수가 아래와 같이 됩니다. 새로운 음식의 스코빌 지수 = 3 + (5 * 2) = 13 가진 음식의 스코빌 지수 = [13, 9, 10, 12]

모든 음식의 스코빌 지수가 7 이상이 되었고 이때 섞은 횟수는 2회입니다.

문제인식

  1. 스코빌 공식에 의하면, 가장 맵지 않은 음식과 그 다음으로 맵지 않은 음식을 순차적으로 꺼낼 수 있는 자료구조가 필요하다.
  2. 모든 음식이 스코빌 지수를 k 이상으로 만들어야한다. 뒤집어 생각해보면, 하나라도 스코빌 지수가 k 미만이면, 문제조건에 맞지 않는다.

해결방향

  1. 우선순위 큐를 이용하여, 모든 음식의 스코빌 지수를 오름차순으로 정렬한다.
  2. 스코빌 지수 공식에 따라, 두 개씩 뽑아야 하므로, 반복문을 선언할때, 반복조건을 큐의 데이터 갯수를 2개 까지로 제한한다.
  3. 문제인식 2번에 따라, 하나라도, k 미만의 음식이 있으면, 실격이므로, 우선순위의 큐의 최소값(head에 해당하는 데이터)이 k 미만 역시 반복조건에 AND로 추가한다.
  4. 반복문의 반복 횟수마다 섞은 횟수를 한개 씩 늘려준다.
  5. 모든 음식이 K 지수 이상이라면, head 데이터 역시 K 지수 이상으로, 반복문이 끝날 것이고, 그 반복문의 횟수를 리턴하면, 그것이 정답이 될 것이다.
  6. 만약 모든 음식을 섞었다면, 큐에 남은 데이터는 최종적으로 하나가 남을 것이다. 그 하나의 데이터가 K 지수보다 낮으면, 모든 음식이 K 지수 미만이므로 이 경우에는 -1을 리턴한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.programmers.algorithm;

import java.util.PriorityQueue;

public class Spicy {
public int solution(int[] scoville, int K) {
int answer = 0;
PriorityQueue<Integer> scovilleQ = new PriorityQueue<>();
for (int sco : scoville){
scovilleQ.add(sco);
}

int mixedScoville;
int firstMin;
int secMin;
while (scovilleQ.size() > 1 && scovilleQ.peek() < K){
firstMin = scovilleQ.poll();
secMin = scovilleQ.poll();
mixedScoville = firstMin + (secMin * 2);
scovilleQ.add(mixedScoville);
answer++;
}
if(scovilleQ.size() <= 1 && scovilleQ.peek() < K) answer = -1;
return answer;
}
}

class SpicyTest {
public static void main(String[] args) {
//case01
int[] scoville = {1, 2, 3, 9, 10, 12};
int k = 99;

Spicy spicy = new Spicy();
System.out.println(spicy.solution(scoville, k));
}
}

algorithm-3-carpet

카펫 출처: 프로그래머스

문제 설명

Leo는 카펫을 사러 갔다가 아래 그림과 같이 중앙에는 노란색으로 칠해져 있고 테두리 1줄은 갈색으로 칠해져 있는 격자 모양 카펫을 봤습니다.

카펫

Leo는 집으로 돌아와서 아까 본 카펫의 노란색과 갈색으로 색칠된 격자의 개수는 기억했지만, 전체 카펫의 크기는 기억하지 못했습니다.

Leo가 본 카펫에서 갈색 격자의 수 brown, 노란색 격자의 수 yellow가 매개변수로 주어질 때 카펫의 가로, 세로 크기를 순서대로 배열에 담아 return 하도록 solution 함수를 작성해주세요.

제한 사항

  • 갈색 격자의 수 brown은 8 이상 5,000 이하인 자연수입니다.
  • 노란색 격자의 수 yellow는 1 이상 2,000,000 이하인 자연수입니다.
  • 카펫의 가로 길이는 세로 길이와 같거나, 세로 길이보다 깁니다.

입출력 예

brown yellow return
10 2 [4, 3]
8 1 [3, 3]
24 24 [8, 6]

문제인식

  1. ‘중앙에는 노란색’, ‘테두리 1줄은 갈색’ => 갈색이 2줄 이상 연속으로 배치되는 경우는 없다. (안쪽에 노란색을 채우고 겉에 갈색으로 감싼다.)
  2. 카펫의 가로 길이는 세로 길이와 같거나, 세로 길이보다 길다. => 정사각형 or 넓은 직사각형.

해결방향

  1. 갈색 테두리의 갯수 = (노란 카펫 높이 + 노란 카펫 폭 + 2) * 2
  2. 노란 카펫의 타일 갯수 만큼 반복문을 돌린다.
  3. 노란 카펫의 가로, 세로 길이를 구하고, 1번 공식을 대입한다.
  4. 해당 공식이 주어진 갈색 카펫의 갯수와 일치하면, 반복문을 끝내고 정답을 리턴한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Solution {
public int[] solution(int brown, int yellow) {
int[] answer = new int[2];
int yCol = 0;
int yRow = 0;
int o1 = 0;
int o2 = 0;
int result = 0;

for(int i = 1; i <= yellow; i++){
if(yellow % i == 0){
o1 = i;
o2 = yellow / i;
yCol = o1 > o2 ? o1 : o2;
yRow = o1 > o2 ? o2 : o1;
result = (yCol + yRow + 2) * 2;
if(brown == result){
answer[0] = yCol + 2;
answer[1] = yRow + 2;
break;
};
}
}

return answer;
}
}

algorithm-theory-Dynamic-programming

Dynamic Programming

정의

  • 동적 계획법
    • 입력 크기가 작은 부분 문제들을 해결한 후, 해당 부분 문제의 해를 활용해서, 보다 큰 크기의 부분 문제를 해결, 최종적으로 전체 문제를 해결하는 알고리즘
    • 상향식 접근법으로, 가장 최하위 해답을 구한 후, 이를 저장하고, 해당 결과값을 이용해서 상위 문제를 풀어가는 방식
    • Memoization 기법을 사용함
      • Memoization : 프로그램 실행 시 이전에 계산한 값을 저장하여, 다시 계산하지 않도록 하여 전체 실행 속도를 빠르게 하는 기술
    • 문제를 잘게 쪼겔 때, 부분 문제는 중복되어, 재활용됨.
      • 예: 파보나치 수열

피보나치 수열

동적 계획법

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Fibonachi {
// dynamic programing
public int fibonachi(int num){
if(num == 0) return 0;
else if(num == 1) return 1;
return fibonachi(num - 1) + fibonachi(num - 2);
}

public static void main(String[] args) {
// fibonacci(0):0
// fibonacci(1):1
// fibonacci(2):1
// fibonacci(3):2
// fibonacci(4):3
// fibonacci(5):5
// fibonacci(6):8
// fibonacci(7):13
// fibonacci(8):21
// fibonacci(9):34
Fibonachi fibo = new Fibonachi();
System.out.println(fibo.fibonachi(9));
}
}

algorithm-2-marathon

완주하지 못한 선수

문제 설명

수많은 마라톤 선수들이 마라톤에 참여하였습니다. 단 한 명의 선수를 제외하고는 모든 선수가 마라톤을 완주하였습니다.

마라톤에 참여한 선수들의 이름이 담긴 배열 participant와 완주한 선수들의 이름이 담긴 배열 completion이 주어질 때, 완주하지 못한 선수의 이름을 return 하도록 solution 함수를 작성해주세요.

제한사항

  • 마라톤 경기에 참여한 선수의 수는 1명 이상 100,000명 이하입니다.
  • completion의 길이는 participant의 길이보다 1 작습니다.
  • 참가자의 이름은 1개 이상 20개 이하의 알파벳 소문자로 이루어져 있습니다.
  • 참가자 중에는 동명이인이 있을 수 있습니다.

해결방향

  1. 문제 인식

    • ‘참가자의 이름은 1개 이상 20개 이하의 알파벳 소문자로 이루어져 있습니다.’ : 대소문자 구문x

    • 참가자 중에는 동명이인이 있을 수 있다. : 단순히 ‘이름’을 기준으로 진행하면, 중복 삭제가 될 수 있다.

    • ‘completion의 길이는 participant의 길이보다 1 작습니다.’ : 완주하지 못한 선수는 무조건 1명이다.

  2. 문제 해결

    1. 같은 이름의 인덱스도 구분해야하기 때문에, HashMap으로 key-value에 해당하는 재너릭 변수명을 각각 ‘이름’, ‘중복횟수’로 지정하고 인스턴스를 초기화 한다.

    2. ‘참가자 배열’을 향상된 for문을 돌려 해쉬맵에 “이름”, 1로 put 하되, 같은 이름의 인덱스로 put을 하게 될때마다, 기존 ‘중복횟수’에서 1을 더한다.

    3. ‘경쟁자 배열’ 향상된 for문을 돌려 동일하게 해쉬맵에 적용하되, 이미 적용된 해쉬맵이 검색될 때마다. 기존 중복횟수에서 -1을 한다.

    4. 해쉬맵 인스턴스가 두개의 for문을 거치면, 결과적으로 0 혹은 1의 값을 가진 키셋이 형성된다.
      해당 해쉬맵에 Iterator와 Entry를 이용해, 해쉬 테이블을 전체적으로 검색하고 그중에서 중복횟수 ‘값’이 1을 가진 키가 검색된다면, 그 키 값을 리턴하고, 프로그램을 끝낸다.

  3. 주의사항

    • 다른 사람의 풀이를 보면, 간혹 해쉬앱을 바로 for문에 적용하여, 검색하는 풀이를 볼 수 있는데, 코드의 길이와 가독성을 보면 언듯 더 편리해보이지만, EntrySet의 getValue()가 HashMap의 get() 보다 사용하는 함수의 갯수가 더 적고, 실행 속도도 빠르기 때문에, 해쉬에서 값이나 키를 출력할 때는 EntrySet을 사용하는 것이 결과적으로는 더 효율적이다.

    • 출처 : https://stackoverflow.com/questions/3870064/performance-considerations-for-keyset-and-entryset-of-map

출처 https://programmers.co.kr/learn/courses/30/lessons/42576?language=java