자바8은 간결한 코드, 멀티코어 프로세서의 쉬울 활용 이라는 두가지 요구사항을 기반으로 한다.


1. 스트림 처리 


스트림이란 한번에 한개씩 만들어지는 연속적인 데이터 항목들의 모임이다. 

java.util.stream 패키지에 스트림 api 추가되었다.

반복되는 패턴으로 주어진 조건에 따라 


- 데이터를 필터링(무게가 100 이상인 사과만)

- 데이터를 추출(사과 무게 필드 추출)

- 데이터를 그룹화(초록색 사과와 빨간색 사과로 그룹화)


컬렉션을 필터링하는 가장 빠른방법은

컬렉션을 스트림으로 바꾸고 병렬로 처리 후 리스트로 다시 복원한다.


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
43
44
45
46
47
48
49
50
51
52
import java.util.*;
import java.util.stream.*;
 
public class Main {
 
    public static class Apple {
        private int weight = 0;
        private String color = "";
 
        public Apple(int weight, String color) {
            this.weight = weight;
            this.color = color;
        }
 
        public int getWeight() {
            return weight;
        }
 
        public void setWeight(int weight) {
            this.weight = weight;
        }
 
        public String getColor() {
            return color;
        }
 
        public void setColor(String color) {
            this.color = color;
        }
 
        @SuppressWarnings("boxing")
        @Override
        public String toString() {
            return String.format("Apple{color='%s', weight=%d}", color, weight);
        }
    }
    
    private static final List<Apple> inventory = Arrays.asList(
            new Apple(80"GREEN")
          , new Apple(155"GREEN")
          , new Apple(120"RED"));
    
    public static void main(String[] args) {
 
        List<Apple> heavyApples = inventory.stream()
                .filter((Apple a) -> a.getWeight() > 100)
                .collect(Collectors.toList());
        
        System.out.println(heavyApples);
        // [Apple{color='green', weight=155}, Apple{color='red', weight=120}]
    }
}
cs



2. 디폴드 메서드 지원


예를 들어 Collections.sort(list)는 정렬 하는 메소드이다. 

자바8 이전에는 List를 구현하는 모든 클래스가 sort를 구현해야 하는데

자바8 이후에는 디폴트 sort메서드가 나와 구현하지 않아도 된다.



3. 동작 파라미터화 코드 전달하기


계속 변경 되는 요구사항에 대응하기 위해 엔지니어링 비용이 가장 최소화 될수 있게 유지보수가 쉬워야한다.


동작 파라미터화를 이용하면 자주 바뀌는 요구사항에 효과적으로 대응할 수 있다.

동작 파라미터는 아직은 어떻게 실행할 것인지 결정하지 않은 코드 블록이다.


변화하는 요구사항에 대응하기 위해 다음과 같은 문제가 주어진다고 가정하자.


3.1 녹색사과 필터링하기



1
2
3
4
5
6
7
8
9
10
// 초록색 사과만 추출 
public static List<Apple> filterGreenApples(List<Apple> inventory) {
    List<Apple> result = new ArrayList<>();
    for (Apple apple : inventory) {
        if (Color.GREEN.name().equals(apple.getColor())) {
            result.add(apple);
        }
    }
    return result;
}
cs


위 메소드는 녹색사과만 필터링 할수 있다.

만약 빨간 사과도 필터링 한다고 하면 

색깔만 다른 동일한 메소드 내용의 다른 메소드명( fliterRedApples )으로 생성할수도 있지만

argument enum Color를 추가하여 값을 받아와 처리한다.



3.2 Color 인자 추가하기


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// enum Color
enum Color{
     RED
    ,GREEN
}
 
// Color를 파라미터화
public static List<Apple> filterApplesByColor(List<Apple> inventory, Color color) {
    List<Apple> result = new ArrayList<>();
    for (Apple apple : inventory) {
        if (color.name().equals(apple.getColor())) {
            result.add(apple);
        }
    }
    return result;
}
cs


Color라는 인자를 추가하여 메소드의 인자가 1개 늘었다.

하지만 색깔뿐만 아니라 필터로 과일의 무게, 적합한지 flag값도 인자로 받아본다고 가정해보자.



3.3 모든 속성을 메서드 파라미터로 추가하기

 

1
2
3
4
5
6
7
8
9
10
11
12
13
// 모든 속성을 메서드 파라미터로 추가
public static List<Apple> filterApplesAllAttr(List<Apple> inventory, Color color
        , int weight, boolean flag) {
    List<Apple> result = new ArrayList<>();
    for (Apple apple : inventory) {
        if (color.name().equals(apple.getColor()) && flag == true) {
            if(apple.getWeight() > weight) {
                result.add(apple);
            }
        }
    }
    return result;
}
cs


인자가 총 4개가 되었다. 모든속성을 받아와 

조건에 따라 적합한 데이터를 추출하여 뽑아오는것이 가능해졌지만

가독성도 떨어지고 앞으로 추가될 여러 인자(5개~10개)에 유연하게 대처할 수 없다.


참 또는 거짓을 반환하는 함수 predicate(프레디케이트를) 사용하여 

선택조건을 결정하는 인터페이스를 정의해보자.



3.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
28
29
// 사과 predicate 인터페이스
public interface ApplePredicate{
    boolean test(Apple apple);
}
 
// 초록색 사과 prediate
public static class AppleGreenColorPredicate implements ApplePredicate {
    public boolean test(Apple apple) {
        return (Main.Color.GREEN.name()).equals(apple.getColor());
    }
}
 
// 무거운 사과 prediate
public static class AppleHeavyWeightPredicate implements ApplePredicate {
    public boolean test(Apple apple) {
        return apple.getWeight() > 150;
    }
}
 
// predicate를 사용하여 추상적 조건으로 필터링하기
public static List<Apple> filterApples(List<Apple> inventory, ApplePredicate p){
    List<Apple> result = new ArrayList<>();
    for(Apple apple: inventory) {
        if(p.test(apple)) {
            result.add(apple);
        }
    }
    return result;
}
cs


인터페이스 ApplePredicate를 구현하는 클래스는 

모두 들어갈 수 있게 되어 좀 더 유연하게 파라미터를 넣을 수 있다. 

메인에서 다음과 같이 선언 후 넣으면 각 조건에 맞게 추상적으로 데이터를 추출할 수 있다.



1
2
filterApples(inventory, new AppleHeavyWeightPredicate())
filterApples(inventory, new AppleGreenColorPredicate())
cs




3.5 익명 클래스를 사용하여 클래스 선언과 인스턴스화를 동시해 해서 가져오기 


1
2
3
4
5
6
// 클래스 선언과 인스턴스화 동시 수행하는 익명 클래스 사용하기 
static List<Apple> redApples = filterApples(inventory, new ApplePredicate() {
    public boolean test(Apple apple) {
        return Color.RED.name().equals(apple.getColor());
    }
});
cs


predicate와 익명클래스를 같이 사용하여 필요한 데이터를 추출해 올 수 도 있다.


위 코드는 자바8의 람다 표현식을 이용하여 간단하게 표현할 수도 있다.


1
2
// 람다 표현식 사용
static List<Apple> result = filterApples(inventory, (Apple apple) -> Color.RED.name().equals(apple.getColor()));
cs


정리하면


동작 파라미터화를 이용해 변화하는 요구사항에 잘 대응할 수 있는 코드를 구현할 수 있으며 엔지니어링 비용 또한 줄일 수 있다.

predicate 인터페이스를 생성하여 여러 클래스에서 predicate를 구현하여 같은 메소드명의 다른 내용인 메소드(오버라이드)를 구현 할 수 있다.  


마지막으로 전체 소스코드를 첨부한다.


전체소스코드.zip


윈도우 10 에서 anaconda3의 jupyter notebook을 사용하는데


콘솔화면으로 실행되는 것이 바탕화면에 걸리적거려

 

백그라운드에서 실행되게 설정하는 방법을 검색했다.





anaconda navigator를 사용해 launch행동으로 jupyter notebook을 백그라운드로 실행되게 만들수 있었다.






파이썬 디버깅이 쉬운 쥬피터 노트북을 구글 GCP 서버를 이용 설치해보겠다.



1. GCP 인스턴스를 생성해준다.




gcp는 지역마다 프리티어로 720시간 무료로 제공하는 region이 있다. 

인스턴스 생성전에 무료 지역이 있는지 확인하자.


본인은 우분투 18.04 버전에 지역은 서울로 인스턴스는 가장작은 micro로 설정했다.




2.  생성된 인스턴스에 접속해본다.








3. 파이썬, 쥬피터 노트북 설치한다.


1
2
3
4
5
6
7
# python3 pip 설치
 
$ sudo apt-get update
 
$ sudo apt update
$ sudo apt install python3-pip
$ pip3 --version
cs





1
2
3
4
5
# 쥬피터 노트북 설치
 
$ sudo apt-get install jupyter-notebook
$ jupyter --version
$ jupyter notebook
cs




쥬피터 노트북이 설치됐다면 ctrl+c를 여러번 눌러 종료시킨다.




4. 쥬피터 노트북 외부접속 가능 환경설정 만들기(1)



1
2
3
4
5
6
7
8
9
10
11
12
# ipython3 설치 및 외부접속 설정하기
 
$ sudo apt-get install ipython3
 
$ ipython3
 
from notebook.auth import passwd
passwd()
Enter password: 1234
Verify password: 1234
sha1:ef93cf62d4ab:bccb5bc464007fd425027d9426
exit()
cs





password 입력 후 나오는 sha1로 시작되는 암호화된 문자열을 복사해준다. 


만약 ipyhon3 명령어가 안된다면 update 한번 더 받아준다.


1
$ sudo apt-get update
cs



쥬피터 노트북에 로그인시 필요한 패스워드 설정을 다하고 빠져나온다.




4. 쥬피터 노트북 외부접속 가능 환경설정 만들기(2)



1
2
3
4
5
# 쥬피터 노트북 외부접속 환경설정하기
 
$ jupyter notebook --generate-config
 
$ vi ~/.jupyter/jupyter_notebook_config.py
cs


config 파일을 만들고 만든 config 파일을 열어본다.




config 파일 안에 수정할 주석 위치를 찾아가 주석해제하여 넣어도 되지만

본인은 맨 앞에 환경설정을 추가 하였다.


1
2
3
4
5
6
= get_config()
c.JupyterApp.config_file_name = 'juyter_notebook_config.py'
c.NotebookApp.allow_origin = '*'
c.NotebookApp.ip = 'GCP내부IP'
c.NotebookApp.open_browser = False
c.NotebookApp.password = '패스워드 sha1로 시작되는 암호화 문자열 입력'
cs



esc :wq 를 입력하여 저장하고 나온다.


이후 jupyter notebook으로 잘 구동되는지 확인한다.





http://외부ip:8888 로 접속한다.







설정한 비밀번호로 입력시 쥬피터 환경으로 접속되는 것을 확인할 수 있다.


이제 백그라운드에서 실행될수 있게 설정한다.


1
2
3
# 백그라운드에서 실행되게 하기
 
$ nohup jupyter notebook &
cs




백그라운드로 jupyter notebook 을 실행시키면 gcp를 종료해도 접속 가능하다.


jupyter notebook 종료는 프로세스 번호를 조회해서 

kill -9 [pid number] 로 종료가능하다.


1
2
3
# 백그라운드에서 종료하기
 
$ kill -9 17076
cs


1. 해쉬 테이블이란?


- 키(Key)에 데이터(Value)를 저장하는 데이터 구조이다.

- Key를 통해 바로 데이터를 받아올 수 있으므로, 속도가 매우 빨라진다.

- 파이썬에서는 해쉬를 별도 구현할 이유가 없다. - 딕셔너리 타입을 사용하면 된다.


- 해쉬는 임의값을 고정길이로 변환하는 것이다.

- 해쉬 테이블은 해쉬값 또는 해쉬주소와 슬롯을 갖고 있는 데이터 구조이다.

- 해싱 함수는 key값을 넣으면 해쉬주소가 나오는 함수다.



2. 해쉬테이블 체이닝 기법( Chaining )


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
# 체이닝 기법
 
hash_table = list([0 for i in range(10)])
 
def get_key(data):
    return hash(data)
 
def hash_func(key):
    return key % 8
 
def csave_data(data, value):
    index_key = get_key(data)
    hash_address = hash_func(index_key)
    
    if hash_table[hash_address] != 0:
        for i in range(len(hash_table[hash_address])):
            if hash_table[hash_address][i][0== index_key:
                hash_table[hash_address][i][1= value
                return
        hash_table[hash_address].append([index_key, value])
    else:
        hash_table[hash_address] = [[index_key, value]]
 
def cread_data(data):
    index_key = get_key(data)
    hash_address = hash_func(index_key)
    
    if hash_table[hash_address] != 0:
        for i in range(len(hash_table)):
            if hash_table[hash_address][i][0== index_key:
                return hash_table[hash_address][i][1]
    else:
        None
cs


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
csave_data('ab','123')
csave_data('ad','456')
hash_table
 
'''
output
[
    [
        [-8164117310928844936, '123'], 
        [6320205215618637368, '456']
    ],
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0
]
'''
cs


체이닝 기법은 해쉬 테이블 저장공간 외의 공간을 활용하는 기법이다.


충돌이 일어나면 링크드 리스트라는 자료 구조를 사용해서

링크드 리스트로 데이터를 추가로 뒤에 연결시켜서 저장하는 기법이다.



3. 해쉬테이블 리니어 기법( Linear )


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
# 리니어 기법
 
hash_table = list([0 for i in range(10)])
 
def get_key(data):
    return hash(data)
 
def hash_func(key):
    return key % 8
 
def lsave_data(data, value):
    index_key = get_key(data)
    hash_address = hash_func(index_key)
    
    if hash_table[hash_address] != 0:
        for i in range(hash_address, len(hash_table)):
            if hash_table[i] == 0:
                hash_table[i] = [index_key, value]
                return
            elif hash_table[i][0== index_key:
                hash_table[i][1= value
                return
            
    else:
        hash_table[hash_address] = [index_key, value]
 
cs


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
lsave_data('ab','123')
lsave_data('ad','456')
hash_table
 
'''
output
[
     [-8164117310928844936, '123'],
     [6320205215618637368, '456'],
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0
]
'''
cs


리니어 기법은 충돌이 일어나면, 해당 hash address의 다음 address부터 맨 처음 나오는 빈공간에 저장하는 기법이다.

저장공간 활용도를 높이기 위한 기법이다.

'전체 > 자료구조' 카테고리의 다른 글

파이썬 시간복잡도 이해하기  (0) 2020.06.08
파이썬으로 큐 구현하기  (0) 2020.06.08
파이썬으로 스택 구현하기  (0) 2020.06.08


1. 링크드 리스트란?





- 배열의 단점을 극복하기 위해 만들어짐

- 배열은 미리 메모리 사이즈를 할당하고 사용해야 함

- 메모리할당을 미리예약하지않고 필요할때마다 데이터를 추가추가할수 있는 구조

- 데이터 + next 데이터를 가르킬 주소를 하나의 데이터라고 보면 된다.

- 그런테 이것을 데이터라고 부를수 없어 데이터와 주소를 하나로 부르는 것을 노드라고 한다.

- 다음 데이터를 가르킬 주소라고 하기도 하지만 포인터라고 얘기한다.


링크드 리스트는 노드와 노드의 연결이다.

노드는 데이터와 다음 노드의 연결정보를 갖는 공간이다.



2. 링크드 리스트 구현


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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
class Node:
    def __init__(self, data):
        self.data = data
        self.next = None
 
class NodeMgmt:
    def __init__(self, data):
        self.head = Node(data)
    
    def add(self, data):
        if self.head == '':
            self.head = Node(data)
        else:
            node = self.head
            while node.next:
                node = node.next
            node.next = Node(data)
    
    def desc(self):
        node = self.head
        while node:
            print(node.data)
            node = node.next
    
    def delete(self, data):
        # 헤드값 없을때
        if self.head == '':
            print("해당값을 가진 노드가 없습니다.")
            return
 
        # 지우는값이 헤드값일때
        if self.head.data == data:
            tmp = self.head
            self.head = self.head.next
            del tmp
        else:
            # 지우는 값이 중간값일때
            node = self.head
            while node.next:
                if node.next.data == data:
                    temp = node.next
                    node.next = node.next.next
                    del temp
                    return
                else:
                    node = node.next
 
linkedlist1 = NodeMgmt(0# 0
linkedlist1.add(1# 0 1
linkedlist1.add(2# 0 1 2
linkedlist1.add(3# 0 1 2 3
 
linkedlist1.desc() # 0 1 2 3
 
linkedlist1.delete(1# 0 2 3
 
linkedlist1.desc() # 0 2 3
cs


링크드 리스트는 데이터가 계속 체이닝 되어서 찾아가는것인데

노드가 10000개 있고 찾으려는 데이터값이 9000번대에 있다면 

맨 앞에서부터 링크드 되어있는 리스트의 값을 찾아가야 한다.  


하지만 뒤에서부터 찾는다면 1000번만 찾으면 값을 찾을 수 있다.

prev, tail을 갖는 것이 더블 링크드 리스트이다.


더블링크드 리스트는 추후에 정리할 예정이다.


알고리즘 시간복잡도가 필요한 이유는

하나의 문제를 푸는데 알고리즘이 다양할 수 있다.

복잡도를 통해 효율적인 알고리즘을 구현할 수 있다.


- 시간복잡도 : 알고리즘 실행속도를 말한다.

- 공간 복잡도 : 알고리즘이 사용하는 메모리 사이즈를 말한다.


- 시간복잡도는 반복문이 여러개 생길경우 많이 발생한다.


시간복잡도는 빅오 표기법을 사용한다.


O(1) < O( 𝑙𝑜𝑔𝑛 ) < O(n) < O(n 𝑙𝑜𝑔𝑛 ) < O( 𝑛2 ) < O( 2𝑛 ) < O(n!)



1. O(n)의 시간복잡도를 가지는 1부터 n까지의 합을 구하는 알고리즘


1
2
3
4
5
6
7
8
9
10
11
12
# 1부터 n까지의 합을 구하는 알고리즘1
 
def sum_all(n):
    total = 0
    for num in range(1, n + 1):
        total += num
    return total
 
print(sum_all(100)) # 5050 
 
# 시간복잡도  O(n)
# 입력 n에 따라 덧셈을 n 번 해야 함
cs



2. O(1)의 시간복잡도를 가지는 1부터 n까지의 합을 구하는 알고리즘


1
2
3
4
5
6
7
8
9
# 1부터 n까지의 합을 구하는 알고리즘 2
 
def sum_all(n):
    return int(n * (n + 1/ 2)
 
print(sum_all(100)) # 5050
 
# 시간복잡도  O(1)
# 반복문이 없으므로 입력이 n이어도 O(1)
cs



같은 결과를 내는 메소드라도 시간복잡도가 적은 O(1)이 효율적인 알고리즘이라고 할 수 있다.

 

'전체 > 자료구조' 카테고리의 다른 글

파이썬으로 해쉬테이블 구현하기  (0) 2020.06.08
파이썬으로 큐 구현하기  (0) 2020.06.08
파이썬으로 스택 구현하기  (0) 2020.06.08


큐는 줄을 서는 행위와 유사하다.

가장 먼저 넣은 데이터를 가장 먼저 꺼내는 구조이다.


매표소에서 티켓을 구매할때 가장 먼저 줄을 선 사람이  먼저 티켓을 살수 있는 것이다.

FIFO (First In First Out) 정책을 사용한다.




1. 내장함수 큐를 사용하여 구현하기



1
2
3
4
5
6
7
8
9
10
11
12
# 내장 함수 queue를 사용하여 구현
import queue
 
= queue.Queue()
 
q.put("a")
q.put("b")
q.put("c")
 
q.qsize()    # 3
q.get()      # 'a'
q.qsize()    # 2
cs



맨 처음에 들어간 데이터가 먼저 나온다.





2. LifoQueue()로 큐 만들기



1
2
3
4
5
6
7
8
9
10
11
# 내장 함수 queue를 사용하여 
# Last in First Out Queue 구현하기
 
import queue
 
q= queue.LifoQueue()
q.put("a")
q.put("b")
 
q.qsize()   #  2
q.get()     # 'b'
cs


마지막에 넣은 데이터가 가장 먼저 나온다.




3. PriorityQueue()로 큐 만들기



1
2
3
4
5
6
7
8
9
# 우선순위가 높은 큐가 먼저 나온다.
import queue
 
= queue.PriorityQueue()
q.put((10"a"))
q.put((5"b"))
q.put((15"c"))
q.qsize()    # 3
q.get()      # (5, 'b')
cs



우선순위가 높은 큐가 먼저 나온다.




4. enqueue, dequeue 구현하기



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 내장함수 사용하지 않고 
# enqueue, dequeue 구현하기
 
q_list = list()
 
def enqueue(data):
    q_list.append(data)
 
def dequeue():
    data = q_list[0]
    del q_list[0]
    return data
 
for i in range(10):
    enqueue(i)
 
print(q_list)     # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
dequeue()         # 0
print(q_list)     # [1, 2, 3, 4, 5, 6, 7, 8, 9]
enqueue(11)       # 11
print(q_list)     # [1, 2, 3, 4, 5, 6, 7, 8, 9, 11]
cs


'전체 > 자료구조' 카테고리의 다른 글

파이썬으로 해쉬테이블 구현하기  (0) 2020.06.08
파이썬 시간복잡도 이해하기  (0) 2020.06.08
파이썬으로 스택 구현하기  (0) 2020.06.08


스택은 배열을 활용해 

배열의 맨 뒤에 있는 데이터를 빼고 맨뒤에 데이터를 추가하면 되는 간단한 개념이다. 

LIFO 마지막에 넣은 데이터를 가장 먼저 추출하는 데이터 관리 정책을 사용한다.


1.  내장 pop 함수 사용하기


1
2
3
4
5
6
7
8
# 스택 push pop 구현하기1
stack = list()
stack.append(1)
stack.append(2)
 
print(stack) # [1, 2]
stack.pop()  # 2
print(stack) # [1]
cs



2.  push와 pop 메소드 구현하기


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 스택 push pop 구현하기2
 
stack_list = list()
 
def push(data):
    stack_list.append(data)
 
def pop():
    data = stack_list[-1]
    del stack_list[-1]
    return data 
 
for index in range(10):
    push(index)
 
print(stack_list) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
pop()             # 9
print(stack_list) # [0, 1, 2, 3, 4, 5, 6, 7, 8]
cs


'전체 > 자료구조' 카테고리의 다른 글

파이썬으로 해쉬테이블 구현하기  (0) 2020.06.08
파이썬 시간복잡도 이해하기  (0) 2020.06.08
파이썬으로 큐 구현하기  (0) 2020.06.08

+ Recent posts