2018년 12월 11일 화요일

[javascript] Object.create 함수로 크로스 브라우저 호환성 보완 - 05

/*
Object.create 함수로 크로스 브라우저 호환성 보완
*/
(function () {
  if(!Object.create) {
    Object.create = (function () {
      function F() {}
      return function (o) {
        F.prototype = o;
        return new F();
      }
    })();
  }
});

[javascript][prototype] ECMA의 class와 extends를 통한 상속 - 04

/* 
class와 extends를 통한 상속
자바스크립트의 new기능의 모호함을 해소하기 위해
ECMA6에서는 class와 extends를 정의한다.
*/

class Person {
  constructor() {
    this.name = "anonymous";
  }
}

class User extends Person {
  constructor() {
    super();
    this.name = "User";
  }
}

var user1 = new User();
console.log(user1 instanceof Person);
console.log(user1 instanceof User);
console.log(user1.constructor); // [Class: user]

[javascript][prototype] Object.create과 new 키워드로 생성자의 연결이 깨지지 않도록 - 03

/* Object.create와 new 키워드 조합 
Object.create과 new 키워드를 조합하여 생성자의 연결이 깨지지 않도록 해보자.
*/

function Person() {
  this.name = "unknown";
}

function User() {
  this.name = "user";
}

// 두 번째 매개변수는 키는 속성명으로 
// 새로 만든 객체에 추가될 속성 설명자(descriptor)를 지정합니다.
// writable option defaults to false.
// https://stackoverflow.com/questions/7757337/defining-read-only-properties-in-javascript
User.prototype = Object.create(Person.prototype, {
  constructor: { // Person.prototype.constructor 는 이제 읽기전용이다. 
    value: User  // 왜냐하면 기본이 writable = false 이다.
  }
})

var myuser = new User();
console.log(myuser instanceof User);
console.log(myuser instanceof Person);
console.log(myuser.constructor);
이 녀석은 이전

[javascript][prototype] 자바스크립트 프로토타입 맛보기 -02

function Car() {
  this.wheel = 4;
  this.beep = "BEEP";
}

Car.prototype.go = function () {
alert(this.beep);
  };

function Truck() {
  this.wheel = 6;
  this.beep = "TRUCK!!";
}

Truck.prototype = new Car();

function SUV() {
  this.beep = "SUV!!";
}
SUV.prototype = new Car();

var truck = new Truck(),
suv = new SUV();

console.log(truck.wheel);  // 6
console.log(suv.wheel);  // 4
console.log(truck.beep);  // TRUCK!!
console.log(suv.beep);  // SUV!!
Car라는 객체를 프로토타입에 넣어보니
Truck과 Suv 는 go라는 함수를 공통으로 사용할 수 있으며, 특히 SUV의 경우 wheel을 정의하지 않았지만
Car에 정의되어 있는 wheel = 4로 바퀴갯수 프로퍼티를 가질 수 있게 되었다.

[js] Object.create을 사용한 자바스크립트 상속 - 01

// 실제로는 Object.create 이라는 함수가 있다. 더글라스 크락포드가 주장했던 함수형태

Object.my_create = function(o) {
  function F() {};
  F.prototype = o;  // O는 처음부터 prototype역할을 하는 녀석일 것이다.
  return new F(); 
}

function Person(name) {
  this.name = name;
}

Person.prototype = {
  yell: function() {
    alert("My name is " + this.name);
  }
};

var u = Object.my_create(Person.prototype); // prototype을 넘기는 것을 주의하자.

u.name = "U";
u.yell();  // alert
/*
이렇게 Object.create 함수를 통해 객체를 생성하면 
개발자가 new 키워드를 사용하지 않고 함수 호출로 객체가 생성된다.

new 키워드를 사용할 때와는 달리 전체적으로 소스에 생성자의 개념이 사라지고 (new가 없으니까)
객체의 인스턴스와 인스턴스 간의 상속을 강조하는 것이 Object.create의 특징 (???)
*/

[js] 자바스크립트 상속기존의 자바스크립트 상속과 그 이면 - 00

function Person() {
  this.name = "anomymous";
  this.sayHello = function() {
    console.log(this.name + "!!");
  };
}

function Nam() {
  this.name = "Nam";
}
Nam.prototype = new Person();

var nam = new Nam();
nam.sayHello();
console.log(nam instanceof Nam);  // true
console.log(nam instanceof Person);  // true

console.log(nam.constructor);  // ?? Nam이 아니라 Person!!
/*
생성자 속성은 객체가 가지고 있는 것이 아니라. 프로토타입이 가지고 있다.
객체는 내부 링크로 프로토타엡에 있는 생성자 속성을 참조한다.

지금 Nam프로토타입은 
그런데 new Person()으로 원래 객체(여기선 Nam)의 생성자를 가지고 있는 프로토타입을 덮어씌우면
Nam의 생성자는 연결고리를 잃는다. (원래 Nam의 생성자는 function Nam() {...} 을 가리키고 있어야 한다.)

따라서 nam.constructor를 실행하면 [Function: Person] 이 나온다.
뭐 잘 작동하기는 하지만 연결이 끊어진다는 것은 좋은 것이 아니다.
*/


괜찮은 링크.
https://medium.com/@bluesh55/javascript-prototype-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-f8e67c286b67

2018년 11월 6일 화요일

[java][thread][unsafe] 자바가 숨겨놓은 악마의 열매 sun.misc.Unsafe - 1

sun.misc.Unsafe

이름부터 강력함을 뽐내는 이 클래스는 아무나 쓸 수 없는 녀석이다.
사실 이 강력함은 우리를 다치게 할 수 있다. 그 어떤 강력한 것도 항상 제물이 필요하다.

열정을 더 불태우기 위해 내 몸을 불 속에 몸을 던지는 행위와 같이
이 세상 모든 뜨거운 것에는 장작이 필요하다.

Unsafe가 그러하다. 자바를 더욱 핫하게 만들어줄 이 녀석은 사실 우리는 쓰지 못하도록 되어 있다.
이 강력한 힘은 사용자가 쓰지는 못하도록 만든 것이다.
더 나아가, 필자의 Eclipse에서는 아예 존재하지 않는 것처럼 보인다.

너무 강력하고 위험하기 때문에, 아예 import조차 못하도록 막은 것이다. (해당 설정을 바꾸면 가능하다)

자바에서도 C와 같은 강력한 기능을 사용하려면 JNI를 이용하여 C언어 개발을 해야 할 것이다.
하지만 C를 모르고 알고 싶지도 않은 사람이 있을 것이다. 하지만 java.misc.Unsafe를 이용하면 Java API를 이용하여
low-level 프로그래밍을 할 수 있게 해준다.

일단 Unsafe를 내가 찾게 된 연유를 보자. 나는 AtomicInteger라는 녀석의 구현을 보고 싶었다.
어느때와 같이 AtomicInteger에서 숫자를 변경하는 메소드를 보았으나. 내 눈앞에 보이는건 Unsafe클래스였다.
안으로 들어가도 바이트코드만 보이는 것을 보고. 뭔가 심각한 녀석임을 눈치챘다.

녀석은 생성자가 private으로 만들어져 있다. 그리고 싱글턴 인스턴스가 private으로 존재한다. 그러므로 getUnsafe라는 녀석이 있는 것으로 봐서
녀석을 실행시키면 된다고 생각했다. 하지만 예외만 던져질 뿐이었다. 왜 그러한지 한 번 둘러보자.
public static Unsafe getUnsafe() {
  Class cc = sun.reflect.Reflection.getCallerClass(2);
  if (cc.getClassLoader() != null)
    throw new SecurityException("Unsafe");
  return theUnsafe;
}
여기서 볼 수 있듯이 일단 theUnsafe라는 녀석이 싱글턴 인스턴스다.
그리고 눈에 보이는 이상한 것들이 있는데 바로 sun.reflection.Reflection 이다.
이녀석은 현재 스레드의 메소드 스택을 확인하여 주어진 숫자 만큼 call-stack-frame 밑으로 내려가서 해당 메소드의 클래스를 리턴한다.
(이 구현을 변경될 수도 있다) 지금 숫자에는 2가 들어있으니까 콜스택의 3번째 값을 가져온다.

index 0. Reflection (getCallerClass)
index 1. Usnafe (getUnsafe)
index 2. Unsafe를 실행하는 나의 어플 내에 있는 메소드

이게 index 2 값을 가져와서 getClassLoader를 실행시킨다. 소스 상에서는 null이어야 한다.
null 레퍼런스는 HotSpot virtual machine 위에 bootstrap class loader를 가리킨다. (이 내용은 {@link Class#getClassLoader()}에 문서화 되어 있다.

아니면 예외를 던진다. (좀 더 자세한 내용은 참고 깃허브 참고)


하지만 트릭은 언제나 존재한다.

바로 이렇게 콜스택으로 체크 하는 방식이 아주 대충 디자인 된 것이라고 한다.
바로 리플렉션의 사용을 막지 않았기 때문에 바로 가져오면 된다.
public void getUnsafe() throws Exception {
  Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
  theUnsafe.setAccessible(true);
  Unsafe unsafe = (Unsafe) theUnsafe.get(null);
  System.out.println(unsafe);
}
이렇게 하면 되지만 안드로이드에서는 Unsafe클래스의 이름이 "theUnsafe"가 아니라. THE_ONE이라는 이름으로 불린다고 하니 이런 코드는 플랫폼에 의존적이다.
다르게 가져와보자.
public Unsafe getUnsafeInstance() throws Exception {
  Constructor unsafeConstructor = Unsafe.class.getDeclaredConstructor();
  Unsafe unsafe = unsafeConstructor.newInstance();
  System.out.println(unsafe);
  return unsafe;
}

좋다. 이렇게 가져와 봤다.
이게 얼마나 위험하기에 이렇게 힘겹게(?) 가져와야 하는 것일까?

해당 내용은 다음에 만들 이야기

[java][thread][unsafe] 자바가 숨겨놓은 악마의 열매 sun.misc.Unsafe - 2

에서 확인해보자.

2018년 11월 5일 월요일

[java][thread] volatile 대체 어디서 쓸만한 걸까?

참고 사이트 (내 깃허브): https://github.com/ssisksl77/java-demo/tree/master/src/main/java/concurrent/volatile_test

volatile은 멀티스레드에서 값들을 캐시하지않고 변경된 내용을 바로바로 가져올 수 있게 해준다.
오! 그렇다면 아무 곳에나 volatile만 넣어준다면 마법처럼 동기화가 되면서 공유상태를 사용할 수 있는 것일까?
아래 소스코드와 결과를 보면 그렇제 않은 것 같다.
package volatile_test;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class VolatileTest1 {
 private volatile static int counter = 0;
 
 public static void main(String[] args) {
  Runnable r1 = () -> { System.out.println(counter); counter++;};
  
  ExecutorService es = Executors.newCachedThreadPool();
  
  for(int i = 0; i < 10; i++) {
   es.execute(r1);
  }
  
  es.shutdown();
//              OUTPUT
//  0
//  0
//  0
//  3
//  4
//  5
//  6
//  7
//  8
//  9

 }
}
0이 3번이나 나왔다. 왜그럴까? 특이한 것은 결과적으로 +1이 잘 되었지만 조회가 잘 안되었다는 것인데 다시 한 번 해보자.
0
0
0
0
0
0
6
6
8
8
전혀 다른 값이 나왔다. 그 말은 값이 달라졌다는 것이다. volatile을 사용한다고 동기화가 되는 것이 아니다. 읽기-수정-쓰기 가 동기화가 되야 하는 것이다. 그렇다면 volatile은 언제 써야 할까? 아래와 같은 경우를 보자.
package volatile_test;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class VolatileTest2 {
 // 아래 내용을 보자. 과연 잘 돌아가는 경우가 얼마나 있을까?
 private static boolean flag = true;
 
 public static void main(String[] args) {
  Runnable r1 = () -> { 
   while(flag) {
     // 여기에 막히게 된다. 
   }
   System.out.println("bye");
  };
  
  Runnable r2 = () -> { System.out.println("flag false start"); flag = false;};
  ExecutorService es = Executors.newCachedThreadPool();
  
  for(int i = 0; i < 10; i++) {
   es.execute(r1);
  }
  es.execute(r2);
  
  es.shutdown();

 }
}
여기서는 전혀 돌아가지는 않는다. 이것은 volatile의 부재일 수도 있지만, 컴파일러 마다 최적화를 하면서 결과값이 다르게 나올 수도 있다고 한다. 여튼 위 내용은 현재 flag를 false로 변경했지만 먹히지 않는다. 이때 volatile을 넣어보자.
package volatile_test;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class VolatileTest3 {
 // 이럴때 volatile로 바꾸는 것만으로 효과가 있다.
 private volatile static boolean flag = true;
 
 public static void main(String[] args) {
  Runnable r1 = () -> { 
   while(flag) {
    // System.out.println("hi");
   }
   System.out.println("bye");
  };
  
  Runnable r2 = () -> { System.out.println("flag false start"); flag = false;};
  ExecutorService es = Executors.newCachedThreadPool();
  
  for(int i = 0; i < 10; i++) {
   es.execute(r1);
  }
  es.execute(r2);
  
  es.shutdown();

 }
}

2018년 10월 29일 월요일

[python] 월별로 난수 생성하기

파이썬에는 아쉬운게 timedelta에 month를 더하는 것이 없다. 다른 유틸을 받아서 사용하면 된다고 하는데 이렇게 해도 잘 되는 것 같다.
import random
import string
import csv
import datetime
import calendar

random_numbers = set()
for _ in range(120):
res = ''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(16))
random_numbers.add(res)

def add_months(sourcedate, months) -> datetime.date:
month = sourcedate.month - 1 + months
year = sourcedate.year + month // 12
month = month % 12 + 1
return datetime.date(year,month,1)


somedate = datetime.date(2018, 4, 1)
with open('random_number.txt', 'w', newline='\n') as f:
w = csv.writer(f, delimiter=' ', quoting=csv.QUOTE_NONE)
for idx, num in enumerate(random_numbers):
w.writerow([add_months(somedate, idx).strftime('%Y-%m'), num])


# print(len(random_numbers))

[codility][python] max counter

codility max counter

https://app.codility.com/demo/results/trainingUBUCBJ-RVE/

def solution(N, A):
  res = [0] * N
  max_counter = 0

  for a in A:
    if a <= N:  # N카운터 존재
      res[a-1] +=1  # 카운터 올림
      if max_counter < res[a-1]: # max_counter 보다 크면 현재 카운터를 tmp_max로 변경
        max_counter = res[a-1] 
    else: # max counter 실행
      for i in enumerate(res):  # 전부 max_counter로 덮어씌운다.
        res[i] = max_counter
  return res
빅오노테이션이 O(MxN) 인 관계로 이중포문에서 하나를 없애야 한다.
# N 카운터가 있음. 0으로 세팅
# max counter : 모든 카운터들이 아무 카운터의 최대 값으로 세팅됨.

# A[K] = X 이면, K오퍼레이션은 X를 증가시킨다.
# -> 값이 X면 X를 증가시킴 (X범위 1<=X<=N)
# A[K] = N + 1 이면 K는 max counter  (??? K가 N보다 크면 max??)
# 아아아 max counter라는 걸 실행함!!!

def solution(N, A):
  res = [0] * N
  max_counter = 0
  tmp_max = 0
  for a in A:
    if a <= N:  # N카운터 존재
      if max_counter > res[a-1]:  # 1 max_counter가 더 크면 max_counter가 갱신된 것
        res[a-1] = max_counter  # 2 max_counter 값으로 변경해준다.
      res[a-1] +=1  # 3 그리고 그 다음에 카운터 시킨다
      if tmp_max < res[a-1]:  # 4 tmp_max보다 크면 현재 카운터를 tmp_max로 변경
        tmp_max = res[a-1]

    else:  # max_counter 에 현재 tmp_max를 넣는다. (max_counter는 그때그때 실행할 것이다.)
      max_counter = tmp_max

  # 아직 max_counter가 변경 된 이후에 counter가 변경된 이력이 없으면 max_counter로 덮어씌워준다.
  # 물론 max_counter보다 작은 경우.
  for idx, _ in enumerate(res):  
    if (res[idx] < max_counter):
      res[idx] = max_counter

  return res

a = [3,4,4,6,1,4,4]
N = 5

print(solution(5, a))

[hackerrank][python]Journey to the Moon

hackerrank: Journey to the Moon


1st attempt

# Determine how many pairs of astronauts from different countries they can choose from.
# UN 연합은 달에 사람들을 보내려고 한다.
# 그들은 이 사람들이 서로 다른 나라였으면 한다.
# 당신은 두 쌍의 우주조종사 id 리스트를 받을 것이다.
# 각 쌍은 같은 나라 우주조종사 id의 두 쌍으로 이루어져 있다.
# - 얼마나 많은 조종사 쌍을 만들 수 있는지 말해라.

# n : number of astronaut
# p : the number of pairs
from collections import defaultdict

# 일단 연결된 것들을 만든다.
def dfs(graph, start):
  visited, stack = set(), [start]
  while stack:
    v = stack.pop()
    visited.add(v)
    # 해당 정점에 연결된 것들을 stack에 넣는다.
    # 방문한 것은 제외
    stack.extend(graph[v] - visited)
  # 연결된 것들을 리턴.
  return visited

def journeyToMoon(n, ast_pairs):
  graph = defaultdict(set)
  asts = set()
  # 그래프 연결...
  for a1, a2 in ast_pairs:
    graph[a1].add(a2)
    graph[a2].add(a1)
    asts.add(a1)
    asts.add(a2)
  
  # 고립된 애들이 있을 수 있나? 일단 받아놔... (있는듯...)
  iso_asts = set([i for i in range(n)]) - asts
  
  groups = []  # divied by contry
  while asts:
    ast = asts.pop()
    connected_asts = dfs(graph, ast)
    groups.append(connected_asts)
    # 한번 방문한 팀은 다시 방문하지 않는다.
    asts = asts - connected_asts
  
  for i in iso_asts:
    groups.append({i})
  
  idx1, idx2 = 0, 1
  group_sum = 0
  while idx1 < len(groups)-1:
    idx2 = idx1 + 1
    while idx2 < len(groups):
      group_sum += len(groups[idx1]) * len(groups[idx2])
      idx2 += 1
    idx1 += 1
 
  return group_sum
    
  
n, p = list(map(int, input().strip().split(' ')))
ast_pairs = []
for _ in range(p):
   astronaut = [int(a) for a in input().strip().split(' ')]
   ast_pairs.append(astronaut)
   
result = journeyToMoon(n, ast_pairs)
print(result)
def dfs(graph, start):
  visited, stack = set(), [start]
  while stack:
    v = stack.pop()
    visited.add(v)
    stack.extend(graph[v] - visited)
  return visited

def journeyToMoon(n, ast_pairs):
  graph = defaultdict(set)
  asts = set()
  for a1, a2 in ast_pairs:
    graph[a1].add(a2)
    graph[a2].add(a1)
    asts.add(a1)
    asts.add(a2)
  
  iso_asts = set([i for i in range(n)]) - asts
  
  groups = []  # divied by contry
  while asts:
    ast = asts.pop()
    connected_asts = dfs(graph, ast)
    groups.append(connected_asts)
    # 한번 방문한 팀은 다시 방문하지 않는다.
    asts = asts - connected_asts
  
  for i in iso_asts:
    groups.append({i})
  
  # 뒤에 추가된 것들을 따로 분리해야 한다. 그래야 추가된 녀석들이 만드는
  # 패턴을 알 수 있다.
  # A*B + A*C + B*C -- A*B + A*C + B*C -- A*B + (A+B)*C 
  # A*B + A*C + A*D + B*C + B*D + C*D 
  # - A*B + (A+B)*C (A+B+C)*D
  # A*B + A*C + A*D + A*E + B*C + B*D + B*E + C*D + C*E + D*E
  # - A*B + (A+B)*C + (A+B+C)*D + (A+B+C+D)*E 
  idx1, idx2 = 0, 1
  group_sum = 0
  tmp_sum = 0
  while idx1 < len(groups):
    group_sum += tmp_sum*len(groups[idx1])
    tmp_sum += len(groups[idx1])

    # group_sum += tmp_sum*(len(groups[idx1 + 1]))
    idx1 += 1
  
  return group_sum
    
  
n, p = list(map(int, input().strip().split(' ')))
ast_pairs = []
for _ in range(p):
   astronaut = [int(a) for a in input().strip().split(' ')]
   ast_pairs.append(astronaut)
   
result = journeyToMoon(n, ast_pairs)
print(result)

[javascript] 접근자 defineProperties를 이용한 생성자 설정.

function Person(name) {
this.name = name;
}
Person.prototype = {
  yell: function() {
    console.log("my name is " + this.name);
  }
}
/* 수정불가 */
var user = Object.create(Person.prototype, {
  name: {
    value: 'new name'
  }
})

/* 수정가능 */
user = Object.create(Person.prototype, {
  name : {
    value: 'new name2',
    configurable: true,
    enumerable: true,
    writable: true
  }
})

/* 접근자 활용의 예 */

Object.defineProperties(user, {
  firstName: {
    value: "Younghwan",
    writable: true
  },
  lastName: {
    value: "Yang",
    writable: true
  },
  fullName: {
    get: function() {
      return this.firstName + " " + this.lastName;
  },
  set: function(value) {
    var res = value.split(' ');
    if (res.length > 1) {
      this.firstName = res[0];
      this.lastName = res[1];
    } else {
      console.log('wrong format');
    }
  }
});

console.log(user.fullName);
user.fullName = "Hello world!";
console.log(user.firstName); // Hello
console.log(user.lastName); // World!

2018년 9월 19일 수요일

재미에 대한 고민

세상에 모든 일에는 재미가 숨어있다고 믿습니다.
저는 재미있는 일보다는 해야 하는 일을 고민하고 살아갑니다.

재미는 그 안에 찾으려 합니다. 그것 또한 하나의 재미라고 자위합니다.

하지만 대부분 찾지 못하며 그것이 우리 대부분 사람들의 현실이겠지요.
재미가 없어도 어쩝니까. 이것이 내가 해야할 일인데 말입니다.

때로 재미는 그 안이 아니라 밖에 있기도 합니다.
가족, 취미 등으로 다른 재미를 찾는 것입니다.
그것이 더 쉬운 방법이라고 생각합니다.
그러니 앞만 보지말고 주위를 둘러봐야할 것 같습니다.

그때까지 우리의 의식이 깨끗하길 바라며
당신의 선택이 옳았음을 깨닫는 알았을 때
온전히 기뻐하며, 슬퍼하며
맑은 눈물을 흘리시길 바라며

2018년 9월 18일 화요일

[리뷰] 개발자라면 한번쯤은 들어봤지만 무시했을 [처음배우는암호화]를 읽고

[처음배우는암호화]를 읽고


웹 개발자로서, 암호화에 대해 많은 생각을 할 시간은 없지만 최소한의 지식이 필요함은 항상 느끼고 있었다.
특히 TLS라는 녀석은 아는 거라곤 버전이 있다는 점.
개발을 할 때, TLS버전을 숙지하고 보내야 한다는 점 정도밖에 몰랐다.

이 책은 암호학 고수로 만들어 주진 못하지만
서냥개 3년이면 풍월읊는 정도는 되게 해준다. 완벽한 이해는 할 수 없어도 이게 뭔지 그게 뭔지 감을 느끼게 해준다.

웹 개발자로서 암호에 대해 깊게 생각할 일이 아마 남은 시간 동안 없을 지도 모른다.
아마 쓸모 없는 지식일지도 모른다.

하지만 책은 결과뿐만 아니라 과정 안에도 즐거움이 있으니, 이 책을 읽는 동안 꽤 즐거웠다.
가끔 이 책을 읽으면서 수학공식 같은 것들이 나온다.
무시해도 될 정도... 라고 생각한다. 모르면 그냥 넘어가도 된다고 생각한다.

얇고 간결한 책이며, 암호학에 대해 진지하게 공부하는 분들보단 처음 시작하는 분들 또는 암호학에 대해 어느정도 귀동냥을 하고 싶은 분들에게 추천한다.

[리뷰] 피그말리온을 읽고 (조지 버나드 쇼 지음)

피그말리온을 읽고



열린책들의 [피그말리온]을 읽었다.

피그말리온의 뜻도 모른체로 글을 읽기 시작하였는데, 그래서 더 좋았던 것 같기도 하다. 피그말리온이라는 이름을 알았다면 이 책의 내용은 초중반의 내용은 다 이해될 정도였을 것 같기도 하다.

리뷰 하기 전

피그말리온은 신화의 인물이다.
조각가라는 직업을 가지고 있던 그는 자신이 조각한 여인을 사랑하게 되어 종단에는 그 여인과 결혼을 하는 인물이다.
여기서 중요한 것은 자신의 창작물을 사랑하게 된다는 것이다.

예술에 대해 잘 모르지만, 조각가의 마음을 한 번 상상해보자면 창작을 할 때, 자신이 상상하던 것, 원하는 것을 만들어 하고 싶을 것이다.
그것은 자신이 가지고 있지 않은 무언가일 것이다. 욕망은 부족함에서 오는 것일 것이니까.
하지만 창작을 하고나서도 그것이 자신이 원하는 것인지 어떻게 알 수 있을까?

피카소는 예술이 거짓이라 말했다. 그리고 그 거짓을 이용해서 진실을 투영하게 해주는 것이며, 그 거짓을 잘 만들어서 대중을 설득시키는 것이 예술가의 일이라 하였다. (내맘대로 발번역, 레퍼런스 원문참고 요망)

플라톤의 향연에서는 "오~ 이 세상이 그 누구보다 아름다운 사랑의 신 에로스여~" 라는 말에 소크라테스가 했던 말이 어렴풋이 기억에 남는다.
사랑의 신이라는 것은 사랑을 원하는 신일 것이다.
사랑은 아름다운 것이 맞는가? 맞다면 그 반대의 추함은 원하지 않을 것이다.
아름다움은 내적 아름다움도 있고, 외적 아름다움도 아름다움이라 할 것이다.

아름다움을 추구 한다는 것은 더 아름다워지고 싶어 하거나, 지금 가진 아름다움을 계속 쟁취하기 위해 노력하는 것일 것이다.
사랑의 신 에로스는 자신이 더 아름다워지고 싶어하거나, 지금 가진 아름다움을 계속 쟁취하기 위해 노력할 것이다.
에로스는 추녀일 수도 있다?


뭐 이런 이야기 였던 것같다. 그 뒤에 내용이 기억이 잘 나지 않지만
중요한 것은 욕망은 부족함에서 오는 것이라는 것이다. 그것이 건전하건 건전하지 않건 말이다.

때로 그 욕망은 저 위의 사랑의 신처럼 끝이 없을 것이다.
도올 선생은 한 강의에서 이런 말은 했다.
단어가 있기 때문에 욕망한다. 그 단어를 원하기 때문에 끝이 없다.
예를 들어,
돈, 사랑, 믿음

이 단어를 원하는 것이다. 이 단어는 끝이 없고, 얻으면 얻을 수록 그 단어의 크기는 커져서는 처음 자신이 만들었던 단어(그 욕망)이 자신을 잠식할 것이다.
그 단어가 당신을 현혹하고, 나를 현혹하고, 결국 내가 원했던 것이 그 단어, 그 개념, 이 되는 것이다.

어쩌면 그것이 행복한 삶일까?
저 위에 있는 피그말리온의 삶이 나는 그리 좋아보이지 않는다.
결혼을 하려고 아름다운 여인을 창작하였을까? 아닐 것이다.
예술가가 저 여인 안에 갇히려고 예술을 배우진 않았을 것이다.
그는 조각가였다. 조각을 하는 자였다. 그는 피카소의 말처럼 조각이 거짓임을 알고 있어야 했다.
그는 자신의 만든 조각에 눈이 멀어, 신에게 기도하고, 그 기도 부름에 신은 여인을 살아있게 하였다.

그렇다면 여인은 무엇인가?
영혼이 있다면 어떤 영혼인가? 피그말리온이 원하는 영혼이 그 안에 들어 있을까?
피그말리온을 사랑하지 않는 여인이었다면 어땠을까?
신은 피그말리온을 사랑하는 영혼을 불어 넣었던 것일까?
아니면 그저 순종하는 여인을 만들었던 것일까?

그는 결국 그것이 조각품이었다는 것을 알아야 한다.
만약 정말 조각품이 살아숨시는 무언가를 원했다면, 그녀를 홀로 설 수 있게 해줘야 한다.
피그말리온은 그저 살아숨쉬는 조각품을 원했던 것이 아닐까?
그는 아마 이것이 그저 조각품임을 알고 있었을 것이다.
아름다운 조각품을 만드는 조각가였으니 말이다. 자신의 본분을 잃을리 없다.
하지만 현실을 부정하고 싶었을 것이다.
조각품의 이름은 갈라테아, 거품의 요정을 뜻한다.

피그말리온으로 돌아오자.

책의 리뷰로 돌아와서,
조지 버나드 쇼는 이 작은 책으로 많은 것을 담았다.

귀족 사회 비판이나, 당시 빈부의 격차라던가 그런 것들을 잘 조명했다고 한다.
그것들은 시간이 지나면 저절로 붙는 수식어일 수 있다. 오래된 소설 중에 저런 말이 적혀있지 않은 소설이 어디있는가.
분명 그는 피그말리온이라는 단어에 집중했을 것이다. 그러니 피그말리온에 집중해보자.


한 신사가 한 여인을 귀족의 세계로 오도록 만든다. 그녀에게 귀족의 말투를 알려주고, 귀족처럼 입혀준다.
그리고 그녀를 다른 귀족들에게 보여줌으로써 가르침의 결과물을 확인받는다.
신사가 생각하기에는 그 여인을 새 사람으로 만들었다고 생각했을 것이다.
그녀는 신사 헨리 히긴스의 창작물인 것이다. 그리고 헨리 히긴스는 피그말리온 일 것이다.

헨리 히긴스는 창작물을 사랑했을까? 맞다 사랑했으나, 그녀를 창작물로서 사랑하는 것이었다.
그것은 진정 그녀를 위한 것이 아니었다. 게다가 그녀는 조각품이 아니다. 인간이다. 그녀의 이름은 일라이자 이다.

결국, 그녀는 자신의 삶을 개척하기 위해 떠난다.

후일담

이후에 후일담이라는 내용이 추가되었다고 한다. 왜냐하면 이 희극이 런던에서 공연을 할 때, 일라이자가 부캐를 던지는 장면을 넣었다고 한다.
조지 버나드 쇼는 그 결말을 아주 싫어했으며 뒤에 결말을 덧붙인 것이다. (이 점에서 조지 버나드 쇼가 좀 졸렬했다고 생각한다. 자신의 창작물이 파생되어 가는 것을 거부하는 피그말리온 같은 사람 같으니라고)
대중들은 이 둘이 다시 만나기는 판타지가 이뤄지길 바랬을까?
결국 일리아지와 헨리 히긴스가 만나는 결말이 있는 영화가 만들어졌을 때, 영화는 상업적으로 대성공을 거두고, 1938년 아카데미 시상식에서 각색상(Academy Award for Best Adapted Screenplay)을 받았다고 한다.(레퍼런스 2 참고)
(요즘도 그러지 않은가? 나이많은 남자와 나이적은 여자가 이루어지는 로맨스를 욕하는가하면 그런 판타지를 원하고 본다.)


후일담에 대해서 좀 이야기 해보자. 진짜 결말을 내기 위한 변명같은 느낌이 강하다. 그럼에도 글을 아주 잘쓴다.
후일담이야 말로 조지 버나드 쇼가 생각했던 이 작품의 결말을 날것으로 보여준다.
왜 이 글을 썼는지 이해해달라고 외치는 것 같았다.

일라이자가 자신이 원하는 남자와 결혼하여 꽃집을 차리고 행복해한다는 내용이다.
하지만 안타깝게도 그녀는 자신만의 힘으로는 살아남지 못한다. 나는 그녀가 당차게 홀로 힘으로 살아남기를 바랬지만 그렇지 못했다.
못살아도 행복하기를 바랬지만 그러지 못했다.
물론 변명거리는 많다.
그녀는 천재가 아니다. 또한 하층민의 교육은 제대로 되지 않았을 것이다. 교육을 받았음에도 영어발음이 좋지 않다는 것이 그 증거 중 하나이다.
그렇기 때문에 그녀가 할 수 있는 일은 많지 않다. 그렇다고 남편이 비상한 것 또한 아니다. 멋진 교육을 받지도 못했다.

일라이자 직간접적으로 피그말리온에게 수 많은 도움을 받는다.
그녀는 계속 손을 벌려 자괴감이 든다고 써있지만, 아낌없이 주는 피그말리온이 있다는 것은 얼마나 행복한 일인가.
그가 없었으면 당장 다시 거리로 꽃을 팔았어야 할지도 모른다.
역시 못배워서 그런가 싶기도 했다. 자괴감이라니...
사람은 때때로 자신이 불쌍해져야 특별해진다고 생각하는 경향이 있다.

원래 자립은 힘든 것이다. 돈은 원래 벌기 힘든 것이다.
무일푼에서 중산층으로 가는 것은 원래 힘들었다. (그전엔 더 힘들 었을 것이지만)
결국 히긴스와 그의 동료가 없었으면, 일라이자의 자립은 상상도 못했을 것이다.

창작물을 외면할 수 없는 그들이었을 것이다.

feminism

이 글이 페미니즘을 위해 글을 읽힌다고 들었다. 그렇다. 당찬 여자는 항상 멋지다.
그녀는 꽃 한송이를 팔 때도 아주 당찼다.
하지만 그렇게 보기에는 여기 모든 것이 피그말리온의 도움이 없이는 만들어질 수 없는 것들이었다.
그녀는 더 이상 길거리에서 경찰에게 잡혀갈까봐 걱정하지 않는다.
그녀는 이제 길거리에서 꽃 한송이를 팔려고 신사들을 졸졸 따라다니는 것이 아니라 꽃집 사장이다.
이 모든 것이 피그말리온으로 인해 시작되었고 완성되었다.
나는 그녀가 좀 더 자신에게 기회를 준 이에게 예를 갖췄으면 좋겠다 싶었다.

마치며

피그말리온은 당대의 계급사회를 보기좋게 풍자했으며, 또 경쾌함을 잃지 않았다.
그는 시대가 원하는 케릭터를 만드는 척하면서 보기좋게 비꼬았다. (적어도 원작에서는...)
시간이 흘렀지만 그의 위트는 아직도 유효하다.
멋진책이다.

레퍼런스

1. picaso's quote
2. Pygmalion

2018년 8월 19일 일요일

[haskell] 변경 가능성이 다분한 부분을 다루자 -02

출처 : 하스켈로 배우는 함수형 프로그래밍

변경 가능성이 다분한 부분을 하스켈로 다뤄보자.


하스켈에서는 비지터 패턴을 사용하지 않는다.

1. 표현식의 형태를 정의하자.
-- 식
data Expr a = Plus (Expr a) (Expr a)  -- 덧셈의 식
            | Square (Expr a)         -- 제곱의 식
            | Number a                -- 숫자의 식
위 내용으로 식에 대한 정의는 끝났다.

2. 이제 처리방식 기술하자.
-- 식의 평가를 실시하는 함수
evalExpr :: Expr Int -> Int
evalExpr (Plus e1 e2) = evalExpr e1 + evalExpr e2
evalExpr (Square e)   = evalExpr e ^ (2 :: Int)
evalExpr (Number n)   = n

-- 식을 문자열로 하는 함수
showExpr :: Expr Int -> String
showExpr (Plus e1 e2) = showExpr e1 ++ " + " ++ showExpr e2
showExpr (Square e)   = "(" ++ showExpr e ++ ")^2"
showExpr (Number n)   = show n
이것도 이렇게 끝났다. 간단하다.

3. 실행하자
main :: IO ()
main = do
  -- e = 1 + (2 + 3)^2
  let e = Plus (Number 1) (Square (Plus (Number 2) (Number 3)))
  putStrLn (showExpr e)
  print (evalExpr e)

여기서 수정사항이 생긴다면
- 식의 증가 (뺄셈 추가)
a. data Expr 에 뺄셈을 추가 (ex. Sub (Expr a) (Expr a) )
- 식에 대한 처리 종류 증가 (JSON 변환)
a. 단순히 evalExpr이나 showExpr과 같은 함수 증가

이렇게 전편에서 사용한 자바의 비지터 패턴을 이용한 코드와 수정하는 방식도 다르고, 코드의 양도 다르다. 왜 이렇게 다를까?
서적에서는 "환경의차이"라고 한다. 꼭 이렇게 설명을 안 하더라도 한줄한줄 비교를 해보면 다른 점이 있는데 그것들이
1. 타입을 정의하는 방식의 차이
2. 패턴매칭(타입을 확인하는 방식)의 차이

1. 타입을 정의하는 방식의 차이

하스켈에서는 data라는 키워드로 타입을 생성한다. 특이한 점은 수식의 구조를 그대로 반영할 수 있다는 것이다. 대부분 우리가 만들려는 새로운 타입들은 저마다 구조를 갖고 있다. 하스켈에서는 그 구조를 아주 쉽게 표현할 수 있다. 마치 수학의 수식처럼 말이다. 그래서 "식 := 식 '+' 식 | '('식')'^2 | 숫자 " 와 같은 (BNF로 표현되는 덧셈,제곱,숫자 자체 중 무엇이든 될 수 있는) 구조를 아주 쉽게 만들어 내는 것이다.

자바에서는 타입을 정의한다는 것은 class, interface를 생성한다는 것이다. Visitor패턴에서는 이것들을 좀 더 세련되게 만든다.
식을 만들려면 식이라는 타입을 만들어야 겠다. 그래서 Expr 인터페이스를 만들고 이것을 이용해서 Plus, Square, Number를 구현한다. 그리고 단순히 구현하는 것이 아니라 Visitor를 받기 위한 함수를 생성할 것이다! 그것은 accept함수이다. 그리고 이제 방문하는 녀석들을 이용해서 표현식들을 처리할 것이다.

이 시점에서 Haskell에서는 취급하고 싶은 식의 타입을 만드는데 3로 간단하게 정의하였다.
Java에서는 interface 1개, class 3개 총 4개를 정의하고 각 3개의 class에 3가지의 accept를 구현해야 한다.(물론 이건 별거 아니다.)
이는 분명 동일한 데이터구조를 다루고 있는데도 어떤 언어냐에 따라 구현 비용이 다르다는 것을 뜻한다.

물론 자바에서 Visitor 패턴을 버리고 Expr 클래스 하나를 만들어서 그 안에 다~ 넣을 수 있다. 그렇게 하면 Expr 클래스는 비대하게 커질 수 있고, 식을 확장하기 위해서 사용하는 비용은 점점 커질 것이다.

2. 패턴매칭(타입을 확인하는 방식)의 차이

Visitor 패턴에 의한 설계에서는 Expr인터페이스를 갖는 각 클래스가 각각의 accept 메소드 속에서 각 클래스 전용으로 준비한 Visitor 인터페이스 메소드를 호출함으로써 판별한다.

예를들어, Number 클래스는 Visitor 인터페이스의 number 메소드를 호출해 두는 식으로 처리한다. 그리고 showExpr와 evalExpr에서 대응하는 처리 또한 Show와 Eval이라는 Visitor 인터페이스를 갖는 클래스로서 구현하게 된다.

Show,Eval 클래스의 plus,square,number의 구현내용은 Haskell의 showExpr, evalExpr 함수의 구현과 거의 동일하다는 것을 알 수 있다.

반대로, 본질적으로는 Haskell 수준의 기술 방식으로 끝나겠지만, 본질적인 부분 이외의 기술에 그만큼 불필요한 비용이 발생할 것이다. 컴파일러는 "이 중 어느 것인가"를 전혀 모르는 듯한 언어 사양이므로 이렇게 할 수밖에 없다.

**Visitor 패턴을 버린 다른 설계로서 instanceof와 같은 실행 시의 타입 정보를 사용할 수 있는 언어라면, 패턴 매치를 통해 인스턴스가 어떤 타입(클래스)의 것인지를 판별하는 구현도 가능할 것이다. 그러나 이 경우 수식이 확장될 때, 그 표현식에 대응한 처리의 구현이 어디선가 누락되었다고 해도 검출할 수가 없다.
구현의 누락은 인터페이스의 부재로 인한 컴파일 오류로 감지되므로 Visitor패턴 쪽이 instanceof와 같은 방법보다 안전하다. (솔직히 instanceof로 만들어진 코드를 보면서 신용이 갈리가 있나... if문이 남발 된 코드는 항상 믿을 수 없다.)

[java] 변경 가능성이 다분한 부분을 다루자 -01 (비지터패턴이용하기)

출처 : 하스켈로 배우는 함수형 프로그래밍

비지터 패턴으로 변경 가능성이 높은 부분을 개발해보자.

파싱트리를 만드는 것에 대해서 배운 적은 없다. 제대로 만들어 본 적이 없기 때문에 한 번쯤은 제대로 배워보고 싶은 생각이 드는데 어디서 부터 공부를 해야 할지 아직도 잘 모르겠다.
일단 우리는 산술식을 다양한 방법으로 표현할 것이다. 이 식을 표현식이라고 부르자.
이 표현식으로 쪼개고 쪼개서 토큰화 시키고 그 수식들을 그대로 문자열로 보이도록 하거나, 계산을 해서 답을 내도록 해보자.
표현식은 BNF로 표현했다고 하자.


비지터패턴으로 만들 것이다.

1. 비지터 인터페이스 생성.

interface Visitor {
    R plus(Expr e1, Expr e2); // 덧셈 식을 위한 메소드
    R square(Expr e); // 제곱 식을 위한 메소드
    R number(N n); // 숫자를 위한 메소드
}

2. 표현식 인터페이스 생성 및 구현


interface Expr {
     R accept(Visitor v);

class Plus implements Expr {
    Expr e1;
    Expr e2;
    public Plus(Expr e1, Expr e2) {
        this.e1 = e1;
        this.e2 = e2;
    }
    @Override public  R accept(Visitor v) { return v.plus(e1, e2); }
}

class Square implements Expr {
    Expr e;
    public Square(Expr e) { this.e = e; }
    @Override  public  R accept(Visitor v) { return v.square(e); }
}

class Number implements Expr {
    N n;
    public Number(N n) { this.n = n; }
    @Override  public  R accept(Visitor v) { return v.number(n); }
}

3. 비지터 패턴을 구현


// 식의 평가를 실시하는 Visitor
class Eval implements Visitor {
    @Override
    public Integer plus(Expr e1, Expr e2) {
        return e1.accept(this) + e2.accept(this);
    }

    @Override
    public Integer square(Expr e) {
        Integer x = e.accept(this);
        return x;
    }

    @Override
    public Integer number(Integer n) {
        return n;
    }
}

// 식을 문자열로 하는 Visitor
class Show implements Visitor {
    @Override
    public String plus(Expr e1, Expr e2) {
        return e1.accept(this) + " + " + e2.accept(this);
    }

    @Override
    public String square(Expr e) {
        return "(" + e.accept(this) + ")^2";
    }

    @Override
    public String number(Integer n) {
        return n + "";
    }
}

이제 실행해보자.
public class TestVisitor {
    public static void main(String[] args) {
        //  e = 1 + (2 + 3) ^ 2
        // 실제로는 구문 해석 등에 의해서 좀 더 크고 복잡한 것을 가정
        Expr e = new Plus(new Number(1), new Square(new Plus(new Number(2)
                                                                ,new Number(3))));
        System.out.println(e.accept(new Show()));
        System.out.println(e.accept(new Eval()));
    }
}

이 프로그램과 설계에는 문제가 없음.
변경에 대한 유연성에 대해서도 다음과 같은 경우에 따라 각각 가산해서 대응할 수 있으므로 Visitor 패턴을 사용하는 것은
객체지향 프레임워크로서는 더할 나위 없는 방법이라고 말할 수 있다.
- 식의 증가에 대해 (뺄셈의 추가)
a. Visitor의 메소드를 늘린다.
b. 식의 인터페이스를 계승하는 클래스를 늘린다.
- 식에 대한 처리 종류의 증가에 대해 (JSON으로 변경)
a. Visitor를 계승하는 클래스를 늘린다.

이런식으로 계속 계산식 혹은 처리방식을 추가할 때, 객체지향적인 방법으로 추가를 할 수 있게 된다.

[java]NULL에게서 도망가기 위한 여정 - 01

출처 : 하스켈로 배우는 함수형 프로그래밍

https://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare

NullPointerException은 가장 많이 보이고 짜증나는 예외이다.
대부분의 언어에서 null은 아주 흔하게 존재한다.
이 녀석은 대부분 에러를 낼 때만 만나게 되는데 우리는 이 null이 없이는 코딩을 할 수 없을 것 같은 느낌을 가지고 있다.
그 만큼 null은 우리의 코딩방식을 반영한다.
null이라는 건 빈 박스를 취하는 것이다. 무엇을 넣을지는 모르겠지만 일단 취하는 것이다.
그 박스를 제공하기 위해 컴퓨터는 빈 공간을 제공해줄 것이다.

만약 빈 박스에 내용으로 뭔가 하려 한다면? 자바에서는 NullPointerException이 날 것이다.

시대가 조금씩 바뀌고 있다. NULL을 모두 체크해야 하는 것으로는 한계가 있다.
요즘에는 Optional이라는 개념이 많이 유입되고 있는 것 같다.
자바의 경우에도 java.util.Optional 이라는 녀석이 생겼다. 그래서 Optional에서 값이 있는지 없는지를 체크할 수 있도록 한다.
이제 if/else문에 대한 처리를 Optional 내부에서 처리를 할 수 있기 때문에 코드가 복잡할 필요가 없어진다.

package org.test;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Optional;
import java.util.function.BiFunction;

public class TestOptional {
 // 공백으로 분할하기, 세 개로 분할할 수 없으면 무효!
 private static Optional words(String expr) {
  String[] res = expr.split(" ");
  if (3 == res.length) {
   return Optional.of(res);
  }
  else {
   return Optional.empty();
  }
 }
 
 // 문자열을 정수로 변환, 변활할 수 없으면 무효
 private static Optional toNum(String s) {
  try {
   return Optional.of(Integer.parseInt(s));
  } catch (NumberFormatException ex) {
   return Optional.empty();
  }
 }
 
 private static Optional add(Integer a, Integer b) {
  return Optional.of(a + b);
 }
 private static Optional sub(Integer a, Integer b) {
  return Optional.of(a - b);
 }
 private static Optional mul(Integer a, Integer b) {
  return Optional.of(a * b);
 }
 private static Optional div(Integer a, Integer b) {
  if (a != b) {
   return Optional.of(a / b);
  }
  else  {
   return Optional.empty();
  }
 }
 
 // +-*/ 외 문자 무효
 private static Optional>> toBinOp(String s) {
  switch(s) {
   case "+" : return Optional.of(TestOptional::add);
   case "-" : return Optional.of(TestOptional::sub);
   case "*" : return Optional.of(TestOptional::mul);
   case "/" : return Optional.of(TestOptional::div);
   default : return Optional.empty();
  }
 }

 public static void main(String[] args) throws IOException {
  
  String expr = new BufferedReader(new InputStreamReader(System.in)).readLine();
  
  // 마치 haskell의 Monad같은 모습.
  System.out.println(
   words(expr)
    .flatMap(ss -> toNum(ss[0])
     .flatMap(a -> toBinOp(ss[1])
      .flatMap(op -> toNum(ss[2])
       .flatMap(b -> op.apply(a, b)))))
    .map(n -> "" + n)
    .orElseGet(() -> "invalid"));   
  }
 }
}

여기서 flatMap이 뭔가 하면 Optional을 받아서 Optional을 리턴하는 녀석인데, Optional의 내용이 empty()면 다음 flatMap으로 전파되지 않는다.
예를들어보자.
Optional str = Optional.ofNullable("AB");
//Optional str = Optional.ofNullable("ABC");
//Optional str = Optional.ofNullable("ABCD");
str.flatMap(ss -> {
 if (ss.length() > 3) {System.out.println("FlatMap1 fail");return Optional.empty();}
 else {System.out.println("flatMap1 pass");  return Optional.of(ss.toLowerCase());}
}).flatMap(ss2 -> {
 if (ss2.length() > 4) {System.out.println("FlatMap2 fail"); return Optional.empty();}
 else {System.out.println("FlatMap2 pass");return Optional.of(ss2.toUpperCase() + "@#$");}
}).ifPresent(System.out::println);
저 위에 있는 것들로 테스트를 해보면 empty()를 뱉는 순간 다음 내용으로 if-else문으로 이어지지 않음을 알 수 있다.

여기서 알아야 할 점은
NULL 체크를 flatMap에서 전부한다는 것이다. 그런데 온통 flatMap이 있으니!
NULL 체크를 모든 곳에서 하는 것이다.
왜 flatMap이 계속 함수 내부로 형성되는지 의아할 것이다.
각 토큰을 Optional로 리턴한 OPtional 값을 하나하나 검사하려고 flatMap을 사용하는 것이다.
그리고 첫번째 flatMap의 값 ss를 함께 사용하려는 의도도 있을 것이다.

게다가 마지막으로 flatMap은 인스턴스 메소드다. 무슨 말이냐면 객체에서 불러내야 사용할 수 있는 것이다.
Optional 객체가 있을 때, 그 객체에서 flatMap 메소드를 호출해야 가능하다. 그러므로 Optional값이 하나 나오면
그때 검사하고 다른 Optional값이 나오면 또 검사할 flatMap을 그때그때(전부) 호출한다.

이제 이 내용을 하스켈로 보자.
http://www.whynam.com/2018/08/haskell-null-02.html

2018년 8월 17일 금요일

[haskell] NULL에게서 도망가기 위한 여정 - 02

출처 : 하스켈로 배우는 함수형 프로그래밍

NULL에게서 도망가기 위한 여정 - 02

- 행간에 처리를 발생시킬 수 있는 힘


이전에는 java.util.Optional을 만나서 NULL에게서 좀 멀어진 느낌을 받았다.
하지만 (책에서는) 개별 인스턴스(객체)에 대한 실패를 동일 문맥상에서 취급할 수 없는 문제를 해결할 수 없었다.
* 결국 객체가 생성될 때마다 context(문맥)이 계속 생성되고 안으로 파고 들어간다.

하스켈을 봐보자.

-- Optional.hs
-- $ stack ghc Optional.hs
-- Optiona.exe
-- 1 + 1
-- Optiona.exe
-- 4 / 0

-- 문자열을 정수로 변환. 변환할 수 없다면 무효
toNum :: String -> Maybe Int
toNum s = case reads s of
        [(n,"")] -> Just n  -- 저절로 변환됨.
        _        -> Nothing

-- 사칙연산. 연산할 수 없다면 무효
addOp :: Int -> Int -> Maybe Int
addOp a b = Just (a + b)
subOp :: Int -> Int -> Maybe Int
subOp a b = Just (a - b)
mulOp :: Int -> Int -> Maybe Int
mulOp a b = Just (a * b)
divOp :: Int -> Int -> Maybe Int
divOp _ 0 = Nothing
divOp a b = Just (a `div` b)

-- +-*/ 연산 변환, 나머지 무효
toBinOp :: String -> Maybe (Int -> Int -> Maybe Int)
toBinOp "+" = Just addOp
toBinOp "-" = Just subOp
toBinOp "*" = Just mulOp
toBinOp "/" = Just divOp

eval :: String -> Maybe Int
eval expr = do
  -- 스페이스로 분할
  let [sa, sop, sb] = words expr
  
  a <- toNum sa     -- 문자열을 숫자로
  op <- toBinOp sop -- 문자열을 연산자로 
  b <- toNum sb     -- 문자열을 숫자로
  a `op` b          -- 연산
  
main :: IO ()
main = getLine >>= putStrLn . maybe "invalid" show . eval

일단 >>= 는 바인드라고 불리는데 getLine으로 받은 값을 바로 다음 타자에게 넘겨준다고 생각하면 된다. (여기선 그냥 이렇게 넘어가자)
putStrLn . maybe "invalid" show . eval
위 녀석이 뭔지 궁금할 것이다.
한번 타입을 보자.
Main> :t eval
eval :: String -> Maybe Int

Main> :t show
show :: Show a => a -> String

Main> :t show . eval
show . eval :: String -> String

Main> :t maybe
maybe :: b -> (a -> b) -> Maybe a -> b

Main> :t maybe "invalid" show . eval
maybe "invalid" show . eval :: String -> [Char]
자 보자.
eval은 String을 받아서Maybe Int를 리턴한다.
show는 아무거나 받아서 String을 내뱉는다.
show . eval 은 String을 받아서 String을 뱉는다. 이게 합성의 힘이다. 중간에 뭐가 있던 단순해지는 것이다.

maybe 함수는 디폴트값, 함수, Maybe 값을 받는다. Maybe값을 받아서 그 값의 내용물을 빼내서 함수를 적용시키고 리턴한다.
만약 Maybe값이 Nothing이라면 디폴트값을 내뱉는다. (Maybe 타입생성자를 없애버리는 역할인듯)

Main> eval "1 + 1"
Just 2

Main> (show . eval) "1 + 1"
"Just 2"

Main> (maybe "invalid" show . eval) "1 + 1"
"2"

Main> putStrLn $ (maybe "invalid" show . eval) "1 + 1"
2

Main> let a = putStrLn . maybe "invalid" show . eval
Main> a "1 + 1"
2

좋다 이렇게 합성이 되는 것이다.

좀 더 설명해보자면,
Haskell에서 Maybe라는 것은 java.util.Optional같은 것과 동일한 문제를 해결하기 위해 있다.
Nothing이 무효, Just가 유효값이다.

만약 앞에 처리에서 Nothing이 발생하면 이후 처리는 실행되지 않고 최정 결과도 Nothing이 된다.
자바의 Optional과 아주 비슷하지만 자바처럼 각각의 인스턴스에 하나하나 flatMap으로 덮어야 하는 수고가 필요없이
아주 그런 걸 신경쓰지 않고 코딩을 하듯 만들어졌다.

무효값이 되었을 경우 건너뛰는 코드 같은건 보이지 않는다.
(책의 내용을 빌리자면, 마치 원래 무효가 될지도 모른다는 생각조차 무시하고 작성된 것같이 보인다)

이것은 Haskell이 갖는 "문맥을 프로그래밍할 수 있는 힘 (monad의 do)"을 이용하고 있기 때문이라고 책은 설명한다.

문맥이란 문장과 문장 사이에서 이루어지는 무언가를 말한다. 자바에서 문장 끝에 붙는 ";"에 무언가 처리를 실시하고 있는 것과 같다.

Maybe는 그러한 "문맥"을 프로그래밍의 한 예 중 하나다.
Maybe가 갖는 문맥이란,

"이전 처리가 무효가 되었다면 그 이후도 모두 무효, 그렇지 않으면 정상 처리를 계속한다" 라는 것이다.
java.util.Optional처럼 인스턴스에 포함시킨 기능이 아니라 문맥에 포함시킨 기능이므로 각 값에 의존한 코딩을 하지 않아도 된다.
(대체 문맥에 포함시킨 기능이라는 것... 이런건 어떻게 프로그래밍하는 것 일까? 이런걸 가지고 놀 때가 언제일지)

[haskell] 함수를 이용한 개발-03

출처 : 하스켈로 배우는 함수형 프로그래밍

함수를 이용한 개발

- 알맞는 부분을 만들거나 알맞은 부품만 맞추는 능력


아래 소스는 책에 있는 내용을 거의 그대로 베낀 것이다.
한가지 바뀐 점은 타입을 생성하고 사용하지 않는 부분이 있어서 그 부분만 치환했으나 중요한 부분은 아니다.
-- Coord.hs
-- $runghc Coord.hs

import Text.Printf

-- 좌표의 타입
type Coord = (Double, Double)

-- 좌표 변환 설정
data Config = Config { rotAt :: Coord            -- 회전 중심 좌표
                     , theta :: Double           -- 회전량[라디안]
                     , ofs   :: (Double, Double) -- 병행 이동량
                     }

-- 좌표 변환 함수의 타입은 "좌표를 별도의 좌표로 이동"하는 함수
-- "좌표를 받아서 좌표를 뱉는 함수"타입
type CoordConverter = Coord -> Coord

-- 병행 이동의 프리미티브
trans :: Coord -> CoordConverter
trans (dx, dy) = \(x, y) -> (x+dx, y+dy)

--회전의 프리미티브
rotate :: Double -> CoordConverter
rotate t = \(x, y) -> (cos t * x - sin t * y, sin t * x + cos t * y)

-- 설정을 베이스로 한 병행 이동
transByConfig :: Config -> CoordConverter
transByConfig config = trans (ofs config)

-- 설정을 베이스로 한 회전
rotateByconfig :: Config -> CoordConverter
rotateByconfig config = postTrans . rotate (theta config) . preTrans 
    where rotateAt = rotAt config
          preTrans = trans(rotate pi $ rotateAt)
          postTrans = trans rotateAt

convertByConfig :: Config -> CoordConverter
convertByConfig config = transByConfig config . rotateByconfig config

main :: IO ()
main = do
  let config = Config { rotAt = (0.5, 0.5), theta = pi / 4, ofs = (-0.5, -0.5)}
  let unitRect = [(0,0),(0,1),(1,1),(1,0)]
  
  let convertedRect = map (convertByConfig config) unitRect
  mapM_ (uncurry $ printf "(%.6f, %.6f)\n") convertedRect
  -- mapM_ is useful for executing something only for its side effects.
  -- https://stackoverflow.com/questions/27609062/what-is-the-difference-between-mapm-and-mapm-in-haskell

하스켈에서는 (.) 을 이용해서 함수를 합성한다.
Prelude> let a = (*2) . length
Prelude> a [1,2,3,4]
8
이 형태는 수학적인 표기법과 아주 비슷하다.

또한 javascript에서 compose는 일단 함수끼리 합성을 시켰을 때, 제대로된 타입들로 합성이 된건지 알 수가 없다.
정말 g의 치역과 f의 정의역이 연결되는지 (g의 리턴타입과 f의 인풋타입이 잘 맞는지) 알 수가 없다.
실행 시에 함수를 만들 수 있으므로 실행 시에 만든 함수들끼리 합성할 수 있는지의 여부를 테스트 등을 통해서 직접 봐야 알 수 있다.

한편 Haskell은 타입 검사가 자체적으로 존재하므로 합성 가능 여부가 타입에 따라 결정된다.

여기서 중요하게 봐야 할 곳은 CoordConverter 같다. CoorConverter는 Coord를 받아서 Coord를 리턴하는 함수를 말한다.
CoordConverter타입인 함수들은 서로 결합이 쉽다.
왜냐하면 Coord가 정의역이고 Coord가 치역이기 때문에 서로 잘 맞는다. (합성을 해도 무난하다)

하스켈에서 uncurry는 curry된 녀석들을 다시 풀어버린다고 생각하면 된다.
(즉 내용을 한 번에 받는 함수가 된다. 하지만 하스켈은 기본적으로 curried function 이기 때문에 튜플로 한번에 받게 된다.)
curry는 그 반대이다. 함수를 일단 커리하여 받을 수 있는 함수로 바꿔버린다.
Prelude> curry fst 1 2
1
Prelude> :t curry
curry :: ((a, b) -> c) -> a -> b -> c
Prelude> :t fst
fst :: (a, b) -> a
Prelude> :t curry fst
curry fst :: c -> b -> c
Prelude> curry fst
error!!
Prelude> curry fst 1 2
1

하스켈에서 uncurry는 curry된 녀석들을 다시 풀어버린다고 생각하면 된다.
Prelude> :t uncurry
uncurry :: (a -> b -> c) -> (a, b) -> c
Prelude> :t (+)
(+) :: Num a => a -> a -> a
Prelude> :t uncurry (+)
uncurry (+) :: Num c => (c, c) -> c
Prelude> (+) 1 2
3
Prelude> uncurry 1 2
error!!
Prelude> uncurry (+) (1, 2)
3

[javascript] 함수를 이용한 개발-02

출처 : 하스켈로 배우는 함수형 프로그래밍

함수를 이용한 개발

- 함수의 부품화, 그리고 조합(합성)


함수를 좀 더 부품화 할 필요성이 있다고 저자는 생각했었나 보다.
사실 첫번째 소스코드에서 보면 알 듯이 기본적으로 기존의 좌표를 새로운 좌표로 변형해서 리턴하는 것이 목표이다.

config값과 coord(좌표)값을 받으면 new coord 가 리턴되어서 나오는 것이다.
이제 필자는 config와 coord를 분리해야 한다고 한다.

왜냐하면 "무언가(config)와 좌표를 받아 좌표를 반환하는" 함수들을 결합할 경우,
"무언가" 부분을 부여해야 한다는 것이 조합할 때의 표현을 복잡하게 만들기 때문이라고 한다.

즉, 이제 함수를 부품화해서 레고처럼 조합할 껀데, 받아야 하는 것이 2개보다는 1개가 낫다.
뭐 이런 말이려나.

여튼 "좌표(coord)만 받아서 좌표를 반환하는" 함수를 서로 조합한다면 서로 아무리 조합해도
"좌표를 받아서 좌표를 내뱉는 "함수들 이기 때문에 조합이 더 편해질 것이다.

아래 내용은 "무언가(config)와 좌표를 받아 좌표를 반환했던" 함수였던 것에서
"무언가를 받고나서 '좌표를 받아 좌표를 반환하는 함수'를 반환하는 함수"로 변경했다.

일종의 커링을 사용한 것인데 무언가(config)를 좌표(coord)와 분리시키기 위해서
이전에 내가 임의로 붙여봤던 커링을 사용해서 config를 미리 함수 안에 넣어버렸다. 그리고 조합을 할 때는

좌표만을 다루는 함수끼리 조합이 되도록!

// 보다 나은 부품화

function clone(obj) {
  var f = function(){};
  f.prototype = obj;
  return new f;
}


// 함수 합성
function compose (f,g) { return function(x) { return f(g(x)); } };


// 병행 이동의 프리미티브
//var trans = function (dx, dy, coord) {
// var result = clone(coord);
// result.x += dx;
// result.y += dy;
// return result;
//}
var trans = function (dx, dy, coord) {
 return function(coord) {
  var result = clone(coord);
  result.x += dx;
  result.y += dy;
  return result;
 }
}

// 회전 이동의 프리미티브
//var rotate = function (theta, coord) {
// var result = clone(coord);
// result.x = Math.cos(theta) * coord.x - Math.sin(theta) * coord.y;
// result.y = Math.sin(theta) * coord.x + Math.cos(theta) * coord.y;
// return result;
//}
var rotate = function(theta) {
 return function(coord) {
  var result = clone(coord);
  result.x = Math.cos(theta) * coord.x - Math.sin(theta) * coord.y;
  result.y = Math.sin(theta) * coord.x + Math.cos(theta) * coord.y;
  return result;
 }
}

// 설정을 베이스로 한 병행 이동
//var transByConfig = function (config, coord) {
// return trans(config.ofsX, config.ofsY, coord);
//}
var transByConfig = function(config) {
 return trans(config.ofsX, config.ofsY);
}

// 설정을 베이스로 한 회전
//var rotateByConfig = function (config,coord) {
// var preTrans = trans(-config.rotAt.x, -config.rotAt.y, coord);
// var rotated = rotate(config.theta, preTrans);
// var postTrans = trans(config.rotAt.x, config.rotAt.y, rotated);
// return postTrans;
//}
var rotateByConfig = function(config) {
 return compose(trans(config.rotAt.x, config.rotAt.y),
      compose(rotate(config.theta),
        trans(-config.rotAt.x, -config.rotAt.y)));
}

// 설정을 베이스로 한 좌표 변환
//var convertByConfig = function(config, coord) {
// return transByConfig(config, rotateByConfig(config, coord));
//}
var convertByConfig = function(config) {
 return compose(transByConfig(config), rotateByConfig(config));
}


var config = {
 rotAt : { x : 0.5, y : 0.5 }
 , theta : Math.PI / 4
 , ofsX : -0.5
 , ofsY : -0.5
}

var unit_rect = [
{x:0,y:0}, {x:0,y:1}, {x:1,y:1}, {x:1,y:0}
]

// 변환 후의 좌표
//var converted_rect = unit_rect.map(function(coord) { 
// return convertByConfig(config, coord);
//}
// 매개변수가 하나로 변하면서 map에서 바로 만들어진 함수를 지정할 수 있다. 하지만 curry를 이용해서 사용할 수도 있겠다.
var converted_rect = unit_rect.map(convertByConfig(config));

converted_rect.map(function(coord) {
 console.log('(' + coord.x.toFixed(6) + ',' + coord.y.toFixed(6)+')');
}
함수 안에 있는 알고리즘을 보는 것이 아니라. return을 하는 방식이 변경되었고 compose를 이용한 함수합성을 유심히 봐야한다.

[Java] 기본 TLSv1.1 연결 방법

public static void main(String[] args) throws NoSuchAlgorithmException, IOException, KeyManagementException {
 System.out.println("TLS TEST");
 String httpsUrl = "https://stagepg.payletter.com/TLSTest/test.html";
 
 SSLContext sc = SSLContext.getInstance("TLSv1.1");
 sc.init(null, null, new java.security.SecureRandom());
 
 URL url = new URL(httpsUrl);
 HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
 connection.setSSLSocketFactory(sc.getSocketFactory());
 
 BufferedReader br = null;
 
 br = new BufferedReader(new InputStreamReader(connection.getInputStream(), "EUC-KR"));
 
 String line = "";
 while ((line = br.readLine()) != null){
  System.out.println(line);
 }
 br.close();
}

[javascript] 함수를 이용한 개발-01

출처 : 하스켈로 배우는 함수형 프로그래밍

함수를 이용한 개발

- 맞을지 안 맞을지도 모르는 부품을 만들거나 맞추는 능력


이 코드의 내용은 전적으로 "하스켈로 배우는 함수형 프로그래밍"의 내용을 가져온 것이다. 그곳에서 내가 더한 내용은 소스 맨밑에 currying을 이용한 부분밖에 없다.
해당 내용은 서적을 읽어야 왜 이러한 기술이 중요한 지 알 수 있다.
서적에서는 C언어로는
  1. 실행시간에 함수를 생성할 수 없어서 결국 for-loop만을 고집해야 하는 점.
  2. 그 작은 차이가 조금씩 코드를 변화하고
  3. 결국 개발자의 생각하는 방식마저 변화시킬 것이며
  4. 자유를 얻게 될 것이라고 말하는 것 같았다.
해당 소스는 꽤나 단순하다.(??) 그리고 내용을 이해하지 않고 함수들의 구조들이 어떻게 변화되는지만 봐도 흥미롭다.
좌표에 한 점이 있는데 이 점을 dx,dy로 이동시키느냐, 혹은 각도(theta)를 이용해서 회전함으로 좌표를 옮기느냐 하는 것이다. (혹은 둘다)
// 객체 복사용
function clone(obj) {
  var f = function(){};
  f.prototype = obj;
  return new f;
}

// 병행 이동의 프리미티브
var trans = function (dx, dy, coord) {
 var result = clone(coord);
 result.x += dx;
 result.y += dy;
 return result;
}

var rotate = function (theta, coord) {
 var result = clone(coord);
 result.x = Math.cos(theta) * coord.x - Math.sin(theta) * coord.y;
 result.y = Math.sin(theta) * coord.x + Math.cos(theta) * coord.y;
 return result;
}

// 설정을 베이스로 한 병행 이동
var transByConfig = function (config, coord) {
 return trans(config.ofsX, config.ofsY, coord);
}

// 설정을 베이스로 한 회전
var rotateByConfig = function (config,coord) {
 var preTrans = trans(-config.rotAt.x, -config.rotAt.y, coord);
 var rotated = rotate(config.theta, preTrans);
 var postTrans = trans(config.rotAt.x, config.rotAt.y, rotated);
 return postTrans;
}

// 설정을 베이스로 한 좌표 변환
var convertByConfig = function(config, coord) {
 return transByConfig(config, rotateByConfig(config, coord));
}
var config = {
 rotAt : { x : 0.5, y : 0.5 }
 , theta : Math.PI / 4
 , ofsX : -0.5
 , ofsY : -0.5
}

var unit_rect = [
{x:0,y:0}, {x:0,y:1}, {x:1,y:1}, {x:1,y:0}
]

// 여기서 coord가 map으로 연결되서서 계속바뀌고
// config는 그대로 계속 공통으로 쓰게 되는 함수를 콜해서 리턴하는 익명함수를 각각 만들어서 실행한다.
var converted_rect = unit_rect.map(function(coord) { 
 return convertByConfig(config, coord);
});

converted_rect.map(function(coord) {
 console.log('(' + coord.x.toFixed(6) + ',' + coord.y.toFixed(6)+')');
});

/// curry를 사용하면 좀 더 이뻐 보일 수 있겠다.
console.log('====');
var curriedConvertedByConfig = function(coord) {
 return convertByConfig(config, coord);
}
var converted_rect2 = unit_rect.map(curriedConvertedByConfig);
converted_rect2.map(function(coord) {
 console.log('(' + coord.x.toFixed(6) + ',' + coord.y.toFixed(6)+')');
});
위 내용을 html을 하나 생성해서 로드해보자.

다음페이지
http://www.whynam.com/2018/08/javascript-02.html

2018년 8월 7일 화요일

펑터, 어플리커티브 펑터, 모나드에 대해 공부해보자.

출처들:
http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html
https://ko.wikipedia.org/wiki/%EB%AA%A8%EB%82%98%EB%93%9C_(%EB%B2%94%EC%A3%BC%EB%A1%A0)
https://ko.wikipedia.org/wiki/%EB%AA%A8%EB%85%B8%EC%9D%B4%EB%93%9C
https://ko.wikipedia.org/wiki/%EB%B2%94%EC%A3%BC%EB%A1%A0
https://en.wikipedia.org/wiki/Monad_(category_theory)
https://en.wikipedia.org/wiki/Monad_(functional_programming)


펑터, 어플리커티브 펑터, 모나드에 대해 공부해보자.



1. 펑터(Functor)란 무엇인가.

박스 안에 당신이 사랑하는 무엇 인가를 가둔다. 박스 안에 있는 물건은 마음대로 변경할 수 없다.
[1]  # [] 가 박스의 형태라고 하자. 
# (어짜피 리스트는 박스의 한 형태이며, 또한 박스처럼 생겼으니 박스라고 하자.)
당신은 박스 안에 1을 넣었다. 나중에 시간이 지나서 이 박스 안에 1에 +1을 하고 싶어졌다고 하자.
당신은 박스 안에 있는 것을 마음대로 빼내서 2로 바꿀 수 없다.
이럴때 필요한 것이 functor이다.
class Functor f where
fmap :: (a -> b) -> f a -> f b

여기서 f는 박스의 형태를 말한다. 위에서 우리는 []가 박스의 한 형태라고 말 한적이 있다.
위에서 a는 당신이 소중하게 여기는 값의 형태이다. 나는 여기에 1 을 넣었으니, Number 일 것이다.

내 박스 안에 값을 더하고 조심스럽게 변화시키고 싶다. 당신은 박스에 내용을 꺼내서 변경할 수 없다.
왜냐하면 그것은 너무 소중해서 바깥 세상에 더럽혀지면 안된다. 대신
박스 안에 함수를 넣는다. 그것이 (a -> b)이다. a형태의 값을 b로 변경하는 함수를 박스 안에 넣는다고 상상해보자.
내가 너무나도 사랑하기에 꺼낼 수는 없지만 변경해야 할 때, 당신은 그 박스 안에 함수를 넣는것이다.

>fmap (+2) [1]
[3]
나는 [1] 에서 1을 꺼내서 2를 더하지는 못했지만, [1] 박스 안에 (+2) 를 넣어서 박스 안에 값을 3으로 바꾼후 박스에 넣어진 채로 리턴받았다.
<$>에 대해서 알아야 할 것이 있는데 <$>는 fmap의 infix버전이라고 보면 된다. (그러니까 줄임말 같은 것인데 infix로 쓰인다는 것)
(+2) <$> [1]
[3]


2. 어플리커티브 펑터

이제 내 안에 있는 박스를 소중하게 변경하는 법을 알았다. 박스안에 값들을 숨기고 (혹은 그 박스 안에 넣어야만 가치있는 값일 지도 모른다. 아니면 효율적인 것일지도) 그 박스 안에 내용들이 더럽혀지지 않도록 하는 것.
그럼으로써 나는 값을 변경하는데 실수하지 않았다는 확신이 들 것이다. 왜냐하면 내가 fmap(펑터)로 박스안에 주입한 함수만이 박스 안의 내용을 변경할 것이니 그것들만 체크하면 되기 때문이다.
(바깥의 내용이 변화를 일으키지 않는다는 확신을 갖게 할 것이다.)

하지만 이렇게 생각해보자. 박스 안에 값들도 중요하지만, 내 소중한 값들을 변경할 녀석(함수)은 중요한 녀석일까 아닐까?
당연히 중요하다.
이녀석들도 박스로 꽁꽁 숨겨서 보관해야할 경우가 있다. 아니면 박스 형태로 올 경우가 있다.
박스 안에 있는 함수를 꺼내서 다른 박스에 넣을 수 있을까? 현재로썬 불가능하다. 그렇기 때문에 펑터를 사용한 것이다. 박스 안에 있는 함수를 꺼낼 수 있다면, 값을 꺼낼 수도 있을 것 아닌가! (왜냐하면 하스켈에서 함수는 값과 별반 다르지 않기 때문에!)
이럴때 어플리커티브 펑터를 이용하는 것이다.
Control.Applicative의 <*>로 가능하게 한다.
>[(+2),(+10)] <*> [10,20,30]
[12,22,32,20,30,40]
앞에 [(+2),(+10)] 는 함수들을 담은 박스다. 이 것을 이용해서 값을 담을 박스 [10,20,30]를 변형시킬 것이다. (아니 변형시키는 것이 아니라 새로 만들어서 리턴하는 것이다.)

다른 예제를 보자.
> (+) <$> [1,2] <*> [10,20,30]
[11,21,31,12,22,32]
<$>는 fmap의 infix버전임을 아까 말했다. <*>는 이제 어플리커티브 펑터라고 보자.
이녀석들은 왼쪽부터 실행된다. 왜냐고?
Prelude> :info <$>
(<$>) :: Functor f => (a -> b) -> f a -> f b
-- Defined in ‘Data.Functor’
infixl 4 <$>

Prelude> :info <*>
class Functor f => Applicative (f :: * -> *) where
...
(<*>) :: f (a -> b) -> f a -> f b
...
-- Defined in ‘GHC.Base’
infixl 4 <*>
infixl 4 <*>, <$> 이 보일 것이다. infix는 중위연산자라는 것이고 그 다음에 붙어있는 l은 left인 것으로 알고 있다. 그러니 left부터 인것이다. (4)는 우선순위이다. 누구먼저 실행되냐인데 둘 다 4 이므로
왼쪽에 있는 녀석이 실행될 것이다.

(+) <$> [1,2] 가 어떻게 실행되는 것인지 보자.
Prelude> :t (+) <$> [1,2]
(+) <$> [1,2] :: Num a => [a -> a]
박스 안에 a -> a 가 들어있다. 함수라는 말이다. 아마 [(+1), (+2)] 가 들어있을 것이다. 하지만 볼 수는 없다. Show가 구현되지 않았기 때문이다.
그렇다면 [(+1),(+2)] <*> [10,20,30]과 같아진다.

이런 일을 한번에 해주는 녀석이 있다.
(<*>) = liftA2 id
liftA2 f x y = f <$> x <*> y
liftA2로 한번 실행해보자.
Prelude> liftA2 (+) [1,2] [10,20,30]
[11,21,31,12,22,32]

3. 모나드

지금까지 무엇을 배웠나 생각해보자.
펑터 : fmap 혹은 <$>, 박스로 보호되고 있는 값에 함수를 주입하여 [박스 안에 새로운 값]을 넣는다.
어플리커티브 펑터 : <*> 혹은 listA2, 박스로 보호되고 있는 값과 함수로 [박스 안에 새로운 값]을 넣는다.


모나드는? 이것들과 별반 다른 것이 없을 것이라고 믿어보자.
(사실 잘 모르겠으며, 만약 이 글을 읽어주는 자비심 많은 독자가 있으시다면, 아래 내용은 저의 의식의 흐름으로 적어간 기록이기 때문에 스킵 하시고 내려가세요. 아래 ==END== 로 )
=========================== START ========================
일단 모나드가 뭐냐? 위키를 봐보자.
https://ko.wikipedia.org/wiki/%EB%AA%A8%EB%82%98%EB%93%9C_(%EB%B2%94%EC%A3%BC%EB%A1%A0)
범주론에서, 모나드는 내부 함자 범주의 모노이드 대상이다.
모노이드란?
https://ko.wikipedia.org/wiki/%EB%AA%A8%EB%85%B8%EC%9D%B4%EB%93%9C
추상대수학에서, 모노이드는 항등원을 갖는, 결합 법칙을 따르는 이항 연산을 갖춘 대수 구조이다.
항등원을 갖고, 결합 법칙을 따르는 이항연산(binary oeprator)을 갖춘 구조인데 항등원은 아무리 연산을 해도 같은 것이고, 결합 법칙은 다르게 결합해도 같은 것을 말한다.
곱하기(*)를 예로들자
15 * 1 = 15 (항등원)
(15 * 2) * 10 = 15 * (2 * 10) (결합법칙)
이런 형태이면 모노이드라고 부르는가 보다.

여기서 범주론은 뭘까?
https://ko.wikipedia.org/wiki/%EB%B2%94%EC%A3%BC%EB%A1%A0
수학에서 범주론(category theory)는 수학적인 구조와 그 사이의 관계를 "범주"라는 추상적 개체를 다루는 이론이다.
20세기 전반에는 수학을 이해하는 도구로 집합론을 꼽았다.
(집합론 : 수학의 이론이란 것은 모두 어떤 대상을 가지고 있는데 이 대상의 모입을 '집합'이라고 부르고 각각의 대상은 이 집합의 원소로 보는 방법)
그러나 이 대상을 이해하려면 '집합'으로 부족함을 깨달았다. '집합 사이의 관계'를 파악해야만 가능하다는 사실을 파악한다.
하여 두 집합 사이의 관계(relation)특히 함수(function)을 써서 이해한다는 집합론을 만들었다. (그것이 범주론인듯... 집합이긴 하지만 관계를 중시한다는 건가)

어떤 구조를 갖는 대상 전체의 모임(위상공간의 모임, 군의 모임, 등등)을 category라 부르고, 두 category 사이에 대응을 functor라고 부른다.
그리고 homology 이론(상동성? 어떤 형질의 진화 과정 동안 보존된 것)을 잘 보면(난 모르지만...) 대응관계(functor)만 알면 이 이론의 결과를 얻게 된다는 것을 알 수 있었다.

즉, category의 여러 성질을 알아내는데 그 성질은 functor가 다 가지고 있다는 것이다. 즉 어떤 집합을 알려 할 때, 이 집합의 요소는 몰라도! 집합에서 나오고 들어가는 함수들 전체의 합성관계만 알아도 집합의 성질을 알 수 있게 된다.
이말이다.

여기서 알 수 있는 것은 functor란 것은 이쪽 집합(세계)에서 저쪽 집합(세계)로 매핑해주는 녀석이라는 것.
https://en.wikipedia.org/wiki/Functor
- In mathematics, a functor is a map between categories.

쓰면서도 뭐가 뭔지 모르겠다. 일단 모나드로 돌아오자. 내부 함자 범주가 대체 뭐야? (찾아보니 함자가 functor의 한국말이다... 하...)
내부 함자 범주가 뭔진 잘 모르겠지만, 모나드에만 이제 집중해서 다시 찾아보자.
https://en.wikipedia.org/wiki/Monad_(category_theory)
In category theory, a branch of mathematics, a monad is an endofunctor (a functor mapping a category to itself), together with two natural transformations.
여기서는 모나드가 endofunctor라는데 괄호안에 있는 것이 뭘 말하는지는 잘 모르겠다. (functor인데 범주 자신에 매핑된다는 걸보니 아마 이런걸 말하는 가보다. f S -> S, 그러니까 이 박스에서 다른 박스 형태로 매핑되는 것이 아니라.
자기자신과 같은 박스형태로 매핑된다는 뜻인가보다... 사실 잘 모르겠다.)

함수형프로그래밍쪽 위키를 봐보자.
https://en.wikipedia.org/wiki/Monad_(functional_programming)
- In functional programming, a monad is a design pattern that defines how functions, operations, inputs, and outputs can be used together
to build generic types, with the following organization:
1. Define a data type, and how values of that data type are combined.
2. Create functions that use the data type, and compose them together into operations, following the rules defined in the first step.
모나드는 함수, 연산자, 인풋, 아웃풋이 제네릭 타입에서 어떻게 이용되는지 '아래와 같은 방식으로' 정의하는 패턴이다.
1. 데이터타입을 정의하고, 데이터타입의 값들이 어떻게 연결되는지 정의한다.
2. 첫번째 스텝에서 정의된 룰을 따라 함수들을 만든다. 그 함수들은 해당 데이터타입을 사용하고, 연산자로 그 둘을 섞는다(구성한다).
잘 모르겠지만, 수학에서의 monad랑 함수형프로그래밍의 모나드랑은 좀 다른 것 같다. 더 읽어보자.
A monad may encapsulate values of a particular data type, creating a new type associated with a specific additional computation,
typically to handle special cases of the type. For example, the simple Maybe monad encapsulates variables which may have a null value,
representing an option type, and automatically ensures that null values are not passed as arguments to functions that cannot handle them,
serving as an alternative programming technique to throwing and catching exceptions when null values arise.
모나드는 특정 값을 캡슐화 한다. (계산을 하고 박스를 생성해서 거기에 넣는다고 생각해보자.) 일반적으로 특별한 타입을 다룰 때 쓰인다고 한다.
예를들어 Maybe monad가 있다. Maybe monad는 null 값이 있을 수도 있는 값을 캡슐화 한다. 하스켈에서 Maybe 타입은 Just a | Nothing 이 두개가 있는데 Nothing이 바로 null 값을 대신한다고 생각하면 된다.
이 Maybe monad는 자동적으로 null 값을 넘기지 않는다. Just null 뭐 이렇게 넘기는 것이 아니라. Nothing이라는 값이 넘어간다. 아예 그 값(null)이 함수에 패스가 되지 않는 것이다. 그러므로 전혀 그 값을 다룰 수 없다.
그러면 일단 코딩을 할 때 그 녀석이 들어오지 않는다고 생각하고 코딩하면 된다. (null이 들어오는 것을 생각하며 코딩하면 더러워진다고 항상 코드가 더러워지지 않는가!!)
이렇게 만듬으로써 예외를 던진다거나 nullpointerException같은거 캐치하는 일은 이제 필요없게 된다.
Another example is the List monad,
where the empty list is a constant value of type List,
and the cons operator binds a plain value as the head of a previous list.
또 다른 예제는 List 모나드라고 한다.
빈 리스트는 List의 상수값이라고 한다. 그리고 cons 연산자는 값을 list의 앞(head)에 붙이는 역할을 한다.

=========================== E N D ========================
여기까지 읽고 한 번 되돌아 보자.
모나드는 그냥 프로그래밍을 할 때 쓰이는, 디자인패턴 중 하나라고 생각하는게 마음에 편할 것 같다.

자 그렇다면 이 Monad를 어떻게 쓰냐. 일단 Monad의 정보를 한번 보자.
Prelude> :info Monad
class Applicative m => Monad (m :: * -> *) where
(>>=) :: m a -> (a -> m b) -> m b
(>>) :: m a -> m b -> m b
return :: a -> m a
fail :: String -> m a
여기서 (>>=)는 bind라고 불리는 녀석인데 이 녀석이 핵심이다. (>> 이녀석은 then 이었나?) 한번 파헤쳐보자.
이 >>= 녀석이 하는 일은 한마디로 말하자면 박스에 있는 값을 함수 안에 넣는다고 생각해보자. *뇌피셜주의* (펑터에서는 박스 안에 함수를 넣었다면 이번엔 반대다! 왜냐하면 이 함수는 박스를 리턴하기 때문)
아래 타입을 보면 더 명확해진다.
(>>=) :: m a -> (a -> m b) -> m b
중간에 있는 (m -> m b)를 봐보자. 박스 안에 있는 값을 다루는데 input 값이 m a가 아니라 a 다.
이 말은 박스 안에 값만을 추출해서 함수의 아규먼트로 들어갔다는 말이다. 사용법을 봐보자.
m a >>= (a -> m b)
[]를 박스라고 해ㄷ보자.
[a] >>= a를 받아 b를 만든다 그리고 박스([])를 만들어서 거기에 b를 넣는다. -> 그러면 m b 즉, [b] 가 리턴될 것이다.
(이렇게, [a] >>= (\x -> ...) -> [b])

여기서 중요한 것은 어떤 타입(어떤 박스가 들어가 있던)이던 그 녀석들을 넣어서 계산하고 뱉어내는 것이다.
Prelude> (Just 2) >>= (\x -> return $ x + 2)
Just 4
Prelude> (Just 2.0) >>= (\x -> return $ x + 2)
Just 4.0
보면 알겠지만, 박스 안에 내용을 가져와서 x에 바인딩(그래서 >>=의 이름이 bind인가보다)되어 새로운 박스에 넣어서 리턴한다. 위에 return의 타입을 보자. a를 가져와서 m a를 뱉어낸다. 새로운 박스를 만들어서 뱉어내는 것이다.

여기까지가 오늘 정리한 내용이다.
틀린 내용이 있으면 수정을 계속 할 것이나...
무엇이 틀린 내용인지도 잘 모르는 멍청이 이기 때문에 안타깝다.

어떤 수학을 공부해야 이런 수학이론을 위키백과로 검색했을 때 나오는 내용들을 이해할 수 있을까?
함께 공부하고 아니면 함께 이걸로 무언가 만들 수 있으면 재미있을 것 같다.
남은 생도 열심히 살자.

2018년 8월 6일 월요일

[책리뷰] 처음배우는 블록체인을 읽고.



이 책은 블록체인에 대한 개론과 구현을 5:5로 나눠서 설명하고 있다.
처음 책의 반은 여러가지 블록체인의 방법들을 설명하고 있으며, 나머지 반은 구현을 하고 있다.
블록체인에 대해 어느정도 개론을 알고 싶고, 구현 또한 어떻게 하고 있는지 알고 싶다면 괜찮은 구성인 것같다.
필요한 용어들을 아주 간결하게 설명해준다. 구현 또한 큰 설명없이 구현을 해나간다.
위에서 설명했듯이 설명이 거창하거나 세세하지 않기 때문에 종사자가 아니라면 이해가 쉽지 않을 수 있다.
그러므로 이 책 한권으로 블록체인을 알 것이라고 생각하면 안된다. 이 책은 블록체인을 알 수 있는 길을 밝혀준다고 생각하면 된다.
세세하지는 않지만 알아야 되는 용어들이 아주 많이 담겨 있기 때문에, 그 용어들을 이용해서 질문을 하거나, 검색을 하거나 할 수 있기 때문에 괜찮은 전략이라고 생각한다.
장점이라고 생각한다.

하지만 구현을 하는 파트에서는 그 점이 단점이라고 생각한다.
어떻게 구현을 하는지 아주 간결하게 보여지면서 빠르게 넘어가기 때문에 보면서 따라하기에는 꽤나 힘들지 않을까 싶다.
하지만 그럼에도 어떻게 구현되고 어떻게 완성되가는지 지켜보는 것은 나쁘지 않은 것 같다. 정말 구현을 하고 싶으면 그것에 집중된 책을 구매하면 될 것이다.

블록체인에 대한 큰 그림을 보고 싶은 분.
블록체인에 대해 알고 있지만 정리가 필요하신 분.
위의 분들에게 추천합니다.
하지만 블록체인에 대해 상세히 알고 싶은 분이라면 위 책만으로는 부족함을 인지하시고 읽어주시면 좋겠습니다.

2018년 7월 14일 토요일

[haskell]하스켈을 접한지 일주일이 되었다.

하스켈을 접한지 일주일

나는 자바 개발자이지만 자바에 대해 제대로 공부하려 하지 않는다. 정확히 말하면 이제 공부를 하다가 관둔 느낌이랄까... 재미가 없다.
왜 재미가 없을까?

첫번째. 자바를 공부하면서 재미있어 하는 사람을 본 적이 없다.


약간 과장을 하였지만 정말 그러하다. 결국에는 슬픔과 좌절(?)로 귀결된다. 내 경험상 적지 않은 고객사이 대부분(전부) JAVA6을 사용하고 있었다.
누가 JAVA8을 배우고 람다를 사용하는가. 처음 자바 학원을 다니면서 JAVA8을 사용하면서 이것저것 해보고 있는 우리들을 보면서 강사는 "절대 안쓴다..." 라고 했던 말이 생각난다.
우리를 무시하면서 했던 말이었다고 그때는 믿었었으나, 지금 생각하니 씁쓸함이었던 것 같다.
분명 쓰는 사람들도 있겠지만 대부분은 쓰지 않을테니...

그런데
왜 그것이 씁쓸했을까?
그것은 개발자가 생각하는 방식을 방해하기 때문이다. 개발자가 생각하는 방식으로 생각하고 싶지만 우리는 JAVA6 안에 굳어간다. JAVA6 안에서 모든 것을 다 만들 수 있다고 하여도 고통스럽다.
우리는 하루하루 기획서를 받고 요구사항을 얻어서 그것을 구.현.하는데 시간을 쓴다. 구현을 하기 위해 더 많은 자유가 있고 선택권이 있다면 우리는 더 편하게 더 즐겁게 노동을 할 것이다.

JAVA8을 신나게 사용하고 업무로 돌아오는 사람들의 표정을 보면, 아니 키보드 소리만 들어도... 화가 느껴진다. 사실 JAVA8이 람다만을 준 것은 아니기에 더 그러할 것이다.

두번째. 누군가에게는 큰 차이가 없어보인다.


사실 큰 차이가 있다. Executors 로 스레드를 가지고 놀다가 parellel 이란 단어를 붙이는 것만으로 뭔가가 되는 것이 보이는 것은 참으로 대단한 일이다.
하지만 자바8에서 주어지는 병렬기능의 뭐 붙이기만 하면 빨라지는 만능도 아니고, Executors에 붙어있는 기능들도 아주 좋아 보인다.
이전에는 대량으로 이메일을 보내는 회사에 있었다. 가끔(?) 문제가 발생하긴 했지만 Executors와 BlockingQueue AtomicInt 같은 것들로 잘 작동했다.

이렇게 되는 것이다. JAVA8로 해야만 버그가 없습니다. 라고 할 수가 없다.
JAVA6으로도 잘 못짜는데, JAVA8로 잘 짤다고?

이렇게 되는 것이다.
기존에 있는 거나 잘써.

세번째. 새로운 거 배울꺼면 뭣하러 JAVA 하나?


이게 가장 큰 것 같다. 나는 이쪽에 속한다. 사실 자바 개발이 싫은 것보다, 자바 웹개발이 좀 그렇다.
Spring과 JSP 로 이루어지는 기능들을 뚜까뚜까 만드는 내 모습은 참으로 공장에서 찍어내는 듯한 생각이 든다.
아마 대부분 사람들이 그러할 것이다.
다들 공장의 부품으로 살아가고 싶지는 않을 것이다.
새로운 것을 하고 싶을 것이다.
내 자신이 변하고 싶을 것이다.

그러니 새로운 언어를 기웃거리며, 새로운 생각을 습득하고, 내 자신이 뭔가 더 많은 것을 수용하고 있다는 확신이 들게 된다.

하스켈 일주일 접한 후기

나는 여기저기 찔러보는 방식으로만 배운다. 이게 내 잘못된 습관인지도 모르지만 "어짜피 안 쓸거 깊게 들어가서 뭐하나 필요할 때 빨리 배우면 되지" 라는 마음으로 기웃거리기만 한다.
하스켈도 기웃거리기 위해서 배우기 시작했다. 하스켈을 설치할 때는 stack을 선택했다. 패키징 매니저인 것 같았다. 사실 뭣도 모르고 찍어서 일단 설치한 건데, 잘 고른 것 같다.

그리고 이맥스를 세팅했다. 코드컴플릿은 안되더라도 이쁘게는 보이게 해야 했다. 코드 자동완성은 없어도 구글신이 있지만, 이쁘지 않으면 보질 않으니까...
첫날 일단 변수의 종류는 빠르게 훑고 함수를 생성하는 법을 익혔다. 별반 차이 없어보였다. (Clojure를 공부했던 경험에서 끈기를 배웠다)
그러다가

Monad에서 좌절했다. 뭐라는 거야.
하나도 모르겠다. 왜 사람들이 Monad에서 막히는지 알았다. 대부분 하스켈을 가르쳐주는 사람들은 그전까지는 "참 쉽죠?" 라는 식으로 가르쳐주다가
Monad로 오자마자 잘난척을 하고 싶은지 수학적 내용으로 얼버무린다.
그런데 이정도 수학적 내용을 모르면 하스켈을 사용할 수 없는 것인가. 하면서 계속 보게 된다.

하루에 한 시간정도 시간을 내는 것으로는 부족함을 느꼈다.

그리고, 아래 내용이 내가 이해한 Monad에 대한 파편이다. (전혀 이게 맞는지 모르겠음...)
Monad는 카테고리 이론에서 나온 개념이다. 카테고리이론은 어떤 집합(세계)에서 다른 집합(세계)를 함수로 연결시킬 수 있다고 믿었다.
더 나아가서, 집합A를 알기 위해서는 그 집합 자체를 몰라도 집합A에서 집합B으로 연결되는 함수를 가지고 있으면 집합A를 알 수 있다고 한다.

뭐 그렇다고 하고, Monad는 사실 저 위 내용보다는 다른 집합을 가두기 위해서 사용하는 것처럼 보인다.
Haskell은 함수형 언어이다. 인풋이 있다면 그에 따른 아웃풋을 만들어줘야 한다. 그 사이에 몰래 시간값 바꾸고 count값 바꾸고 그런 헛다리짓을 하면 안된다.
하지만 바깥세상에서 들어오는 값들은 그 딴게 어디있나 진흙탕에 들어가야 진흙탕에 빠진 사람들을 구해주지 혼자 깨끗할 순 없다. 그래서 만든 것이. 보호막 같은 것이다.

그래서 Monad라는 개념을 만들었다. 값을 가두는 것이다. 예를 들면 많은데, Maybe 나 [] 가 있고 가장 중요한 IO가 있다.
이제 IO에서 가져온 값을 변경 하려면 IO에서 그냥 꺼낼 순 없다.
IO라는 배리어는 아주 강하다. Haskell 세상에서 그냥 바꿀 순 없는 것이다. 바깥세상 내용을 Hakell 세상에서 맘대로 꺼낼 순 없다.

그렇다면 어떻게 해야 하나?

IO라는 배리어 안에 들어가야 한다. 그 일은 하는 녀석은 bind operator이다. ">>=" 로 보이는데 이 녀석을 이용하면 된다.
이 녀석을 사용하면 안에 들어가서 무언가를 할 수 있다. 안에 들어가서 꺼내올 것인가? 내가 보기에는 그럴 수는 없는 것 같다.
한번 배리어 안으로 들어가면 그 값을 가지고 나올 수는 없다. 대신 그 안에서 여러 함수들로 바깥 세상의 값을 변경할 수 있고 그 값을 다시 내보내서 세상에 반영할 수 있다.

e그래서 일단 짬짬이 토이 프로젝트를 만들어봤다. 정말 단순해서 100줄도 안짰다. 그런데 Monad가 이해가 안되서 끙끙댔다.
이것은 내가 필요해서 만든 것인데. 그냥 여러 패스에 파일들을 한번에 각각 다른 패스에 복사해주는 것이다. 바깥세상과 단순히 어떻게 연결이 되나 실험을 해볼 겸 해서 만들어봤다.
https://github.com/ssisksl77/dockeybay

여기까지가 일주일을 바짝 익혀본 소감이다. 이젠 심심할 때마다 구경하면서 하스켈에 대해 더 친해져 봐야겠다. 이 녀석은 정말 재미있다.
다른 사람들하고 같이 공부했으면 좋겠다.

동적설계법 배낭문제 - 2

package algorithm;

import java.util.Arrays;

public class Knapsack {

    static final int MAX_N = 100;
    static int[][] dp = new int[MAX_N][MAX_N];
    static int W;
    static int[] v;
    static int[] w;
    static int n;

    public static void main(String[] args) {

        n = 4;
        w = new int[] {2, 1, 3, 2};
        v = new int[] {3, 2, 4, 2};
        W = 5;
        // Arrays.fill(dp, -1);  error!
        for (int[] row : dp)
            Arrays.fill(row, -1);

        // int res = solve(0,W);
        // System.out.println(res);
        solve2();
    }

    private static int solve(int idx, int weight) {
        if (dp[idx][weight] >= 0) {
            return dp[idx][weight];
        }
        int res;
        if (idx == n) {
            res = 0;
        } else if ( weight < w[idx]) {  // skip
            res = solve(idx+1, weight);
        } else {  // 넣는 경우, 넣지 않는 경우.
            int a = solve(idx + 1, weight);
            int b = solve(idx + 1, weight - w[idx]) + v[idx];  // 이 경우에만 물건을 넣는 경우 v[idx]로 이때만 추가된다.
            res = Math.max(a, b);
        }

        return dp[idx][weight] = res;
    }

    private static void solve2() {  // i=idx, j=weight
        for (int[] row : dp)
            Arrays.fill(row, 0);  // -1이 아님을 주의. 캐시를 하는 것이 아니라. 전부 더할 것이다.

        for (int i = n - 1; i >= 0; i--) {
            for (int j = 0; j <= W; j++) {
                if (j < w[i]) {
                    dp[i][j] = dp[i + 1][j];
                } else {
                    dp[i][j] = Math.max(dp[i + 1][j], dp[i + 1][j - w[i]] + v[i]);
                }
            }
        }

        for (int i = 0; i <= W; i++) {
            for (int j = 0; j <= W; j++) {
                System.out.print(dp[i][j] + " ");
            }
            System.out.println();
        }
        System.out.println(dp[0][W]);
    }
}

동적설계법 - 배낭문제 -1

package algorithm;

import java.util.Arrays;

public class Knapsack {

    static final int MAX_N = 100;
    static int[][] dp = new int[MAX_N][MAX_N];
    static int W;
    static int[] v;
    static int[] w;
    static int n;

    public static void main(String[] args) {

        n = 4;
        w = new int[] {2, 1, 3, 2};
        v = new int[] {3, 2, 4, 2};
        W = 5;
        // Arrays.fill(dp, -1);  error!
        for (int[] row : dp)
            Arrays.fill(row, -1);

        int res = solve(0,W);

        System.out.println(res);

    }

    private static int solve(int idx, int weight) {
        if (dp[idx][weight] >= 0) {
            return dp[idx][weight];
        }
        int res;
        if (idx == n) {
            res = 0;
        } else if ( weight < w[idx]) {  // skip
            res = solve(idx+1, weight);
        } else {  // 넣는 경우, 넣지 않는 경우.
            int a = solve(idx + 1, weight);
            int b = solve(idx + 1, weight - w[idx]) + v[idx];  // 이 경우에만 물건을 넣는 경우 v[idx]로 이때만 추가된다.
            res = Math.max(a, b);
        }

        return dp[idx][weight] = res;
    }
}

동적설계법 - 배낭문제 0

/**
동적설계법
01 배낭문제(Knapsack problem) - 0

n = 4
(w, v) = {(2,3),(1,2),(3,4),(2,2)}
W = 5
ouput : 7

유명한 배낭 문제이다. 일반적인 방법으로 각 물건에 대해 (넣는다/넣지않는다)로 두 가지 상태로 나눠서 탐색한다.

**/
import java.io.*;
import java.lang.*;
class Main {

  static int W;
  static int[] v;
  static int[] w;
  static int n;
  
  public static void main(String[] args) throws IOException {
    //BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));

    n = 4;
    w = new int[] {2, 1, 3, 2};
    v = new int[] {3, 2, 4, 2};
    W = 5;

    int res = solve(0,W);
    System.out.println(res);
  }

  
  static int solve(int i, int W) {
    if (i == n) {
      return 0;
    } else if ( W < w[i]) {
      //skip
      return solve(i+1, W);
    } else {
      // 넣지 않는 경우, 넣는 경우 조사.
      int a = solve(i+1, W);
      int b = solve(i + 1, W - w[i]) + v[i];
      return Math.max(a, b);
    }

  }
}

2018년 7월 13일 금요일

부분집합 [C언어]

문제로 풀어보는 알고리즘.
// 부분집합
#include 

void print_arr(int arr[], int arr_len)
{
  int i;
  for (i = 0; i < arr_len; i++)
    printf("%d ", arr[i]);
  printf("\n");
}

/*
부분집합 공식
P(A) = (e*P(A')) U P(A')
e를 포함한 A' 와 e를 포함하지 않는 A'의 합집합
A'는 A' = A - {e} 라고 A에서 집합 {e}를 뺀 것.
그렇지만 코드가 좀 이해가 안된다.
*/
void subset(int set[], int set_size, int n, int index)
{
  if (index == n) {
    print_arr(set, set_size);
    return;
  }
  /*
  set[set_size]가 계속 변경됨.
  index가 올라감에 따라 set[set_size]의 기존 집합이 덮어씌워진다.
  ex) 
  set = [0 1 2] 에서 index가 오르면서
  set = [0 1]
  set = [0 2]
  뭐 이런식으로... 다른 언어였으면 그냥 set같은 걸 사용하면
  좋을 것 같다.
  */
  set[set_size] = index;  // set[size_size]로 집합을 흉내.
  subset(set, set_size+1, n, index+1);  // index(e) 포함한 경우.
  subset(set, set_size, n, index+1);  // index(e)를 포함안한 경우.
}

#define N 100
int main() {
  int set[N], n;
  printf("input n : ");
  scanf("%d", &n);
  subset(set, 0, n, 0);
  return 0;
}

[java] 탐욕알고리즘 연습 - 3

// Fence Repair p.68
/**
널빤지 N개를 자르려고 한다. 필요한 길이는 L1, L2, L3는 배열로 주어진다.
널빤지 N개의 Ln의 길이로 자르려고 할 때 최소 비용을 구하라.
N = 3, L = {8,5,8}
21을 13 과 8로 자르는 비용은 21
13을 5, 8로 자르는 비용 13
총 비용은 34다

탐욕 알고리즘으로 풀 수 있다. 
- 가장 짧은 널빤지 노드와 그 다음으로 짧은 널빤지의 노드는 형제면 된다.

**/
import java.io.*;

class Main {
  public static void main(String[] args) throws IOException {
    BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    int ans = 0, N = 3;
    int[] L = {8, 5, 8};

    // 널빤지가 1개가 될때까지 
    while (N > 1) {
      // 가장 짧은 널빤지 mil1, 다음으로 짧은 널빤지 mil2를 구함.
      int mil1 = 0, mil2 = 1;
      if (L[mil1] > L[mil2]) {
        int tmp = mil1;
        mil1 = mil2;
        mil2 = tmp;
      }

    //3번째 배열부터 또 mil1, mil2보다 작은 것들이 있는지 확인하고 있다면 위치를 갱신한다.
    for (int i = 2; i < N; i++) {
      if (L[i] < L[mil1]) {
        mil2 = mil1;
        mil1 = i;
      }
      else if (L[i] < L[mil2]) {
        mil2 = i;
      }
    }

    // L[mil1] + L[mil2] 병합 (제일 작은 서로를 합침)
    int t = L[mil1] + L[mil2];
    ans += t;
    bw.write("ans : " +  ans + "\n");
    if (mil1 == N - 1) {
      int tmp = mil1;
      mil1 = mil2;
      mil2 = tmp;
    }

    L[mil1] = t;  // mil1을 합친값을 넣어서 커지게 만든다. (합쳐졌으니.)
    L[mil2] = L[N-1];  // mil2는 가장 큰 값을 넣는다. (왜지??)
    N--;
    }

    bw.write("ans : " +  ans + "\n");
    bw.close();
    br.close();
}

탐욕알고리즘 - 2

// 구간 스케줄링 p.59
/**
  n개의 강의가 있다. 각 강의 시간은 si에서 ti까지다.
  가장 많은 강의에 참가하고 싶은 경우, 몇 개의 강의에 참가하는 것이 가능할까?

**/
import java.io.*;
// import java.util.TreeMap;
import java.util.*;

class Main {
  public static void main(String[] args) throws IOException {
    
    BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
    // BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    final int MAX_N = 100000;  // 비교 최대값
    int N = 5;
    int[] S = {1,2,4,6,8};
    int[] T = {3,5,7,9,10};
    Map map = new TreeMap<>();

    for(int i = 0; i < N; i++) {
      map.put(T[i], S[i]); // 종료시간순으로 정렬한다.
    }

    int ans = 0;
    int t = 0;
    for (int i = 0; i < N; i++) {
      if (t < map.get(T[i])) {
        ans++;
        t = T[i];
        bw.write("선택한 강의 : " + (i+1) + "\n");
      }
    }

    bw.write("ans : " +  ans + "\n"); // 출력 3 (1, 3, 5 선택)
    bw.close();
    // br.close();
  }
  public static int min(int a, int b) {
    if (a > b) return b;
    else return a;
  }
}

탐욕알고리즘 - 1

// 코인문제 p.57
/**
  1, 5, 10, 50, 100, 500원짜리가 각각 C1, C5, C10, C50, C100, C500개씩 있다.
  A원을 지불하려면 몇 개의 동전이 있어야 할까. 지불 방법은 적어도 1개는 존재한다고 가정한다.
  **/
import java.io.*;

class Main {
  public static void main(String[] args) throws IOException {
    int ans = 0;
    BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
    // BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    final int[] V = {1,5,10,50,100,500};
    int[] C = {3,2,1,3,0,2};
    int A = 620;

    // 큰 것부터 넣는다. 탐욕알고리즘
    for (int i = 5; i >= 0; i--) {
      int t = min(A / V[i], C[i]); // 
      A -= t * V[i];
      ans += t;
    }
   
    bw.write("ans : " +  ans + "\n"); // 출력 6(500x1, 50x2, 5x2 합계 6개)
    bw.close();
    // br.close();
  }
  public static int min(int a, int b) {
    if (a > b) return b;
    else return a;
  }
}

[python][hackerrank] Repeated String

https://www.hackerrank.com/challenges/repeated-string/problem

strings = input()

# number_of_a = len(''.join(x for x in strings if x == 'a'))
number_of_a = strings.count('a')

n = int(input())

_div, _mod = divmod(n, len(strings))
res = _div * number_of_a

for s, m in zip(strings, range(_mod)):
  if s == 'a':
    res += 1
    
print(res)

python cookbook Strings and Text

String

2.1. Splitting Strings on Any of Multiple Delimiters
>>> line = 'asdf fjdk; afed, fjek,asdf,       fo'
>>> import re
>>> re.split(r'[;,\s]\s*', line)
['asdf', 'fjdk', 'afed', 'fjek', 'asdf', 'fo']

>>> files = re.split(r'(;|,|\s)\s*', line)
>>> files
['asdf', ' ', 'fjdk', ';', 'afed', ',', 'fjek', ',', 'asdf', ',', 'fo']
>>> values = files[::2]
>>> values
['asdf', 'fjdk', 'afed', 'fjek', 'asdf', 'fo']
>>> delimiters = files[1::2]
>>> delimiters
[' ', ';', ',', ',', ',']
>>> ''.join(v+d for v,d in zip(values, delimiters))
'asdf fjdk;afed,fjek,asdf,'

>>> re.split(r'(?:,|;|\s)\s*',line)
['asdf', 'fjdk', 'afed', 'fjek', 'asdf', 'fo']


2.2 Matching Text at the Start or End of a String
>>> filename = 'peter.txt'
>>> filename.endswith('.txt')
True
>>> filename.startswith('peter')
True

>>> choices = ['http:', 'ftp:']
>>> url = 'http://www.python.org'
>>> url.startswith(tuple(choices))
>>> re.match('http:|https:|ftp', url)
<_sre.SRE_Match object; span=(0, 5), match='http:'>

2.3 Matching Strings Using Shell Wildcard Patterns
>>> from fnmatch import fnmatch, fnmatchcase
>>> names = ['Dat1.csv', 'Dat2.csv', 'config.ini', 'foo.py']
>>> [name for name in names if fnmatch(name, 'Dat*.csv')]
['Dat1.csv', 'Dat2.csv']

>>> fnmatchcase('foo.txt.','*TXT')
False


2.4. Matching and Searching for Text Patterns
>>> from fnmatch import fnmatch, fnmatchcase
>>> names = ['Dat1.csv', 'Dat2.csv', 'config.ini', 'foo.py']
>>> [name for name in names if fnmatch(name, 'Dat*.csv')]
['Dat1.csv', 'Dat2.csv']

>>> fnmatchcase('foo.txt.','*TXT')
False

2018년 5월 15일 화요일

[리뷰] 머신러닝을 간결하게 정리한 [핸드온 머신러닝]을 읽고


이 책은 정말 머신러닝으로 무언가를 해보려는 분들에게 필요한 책입니다.
배우기 위한 책은 아니며 새롭게 익히고 싶은 사람들에게는 다른 책을 찾아보시길 바랍니다.
이 책은 친절한 책이 아니며, 그것을 위한 책또한 아닙니다.

머신러닝을 안다는 가정하게 해결책을 제시하기 위해 만들어진 책처럼 보입니다.
각각 경우에 쓰이는 공식들을 설명하고 어떻게 쓰는 지 설명되어 있습니다.
지극히 간결하여 찾아보기 좋으나, 실제로 이것이 어떤 것을 의미하는 지는 잘 알 수 없어 아쉬웠습니다.
아무리 간결해도 확률에 대한 설명은 좀 더 친절해도 좋지 않았나 싶었습니다.

무언가 막혀 있을 때, 해법을 줄만한 뭔가가 필요할 때. 도움이 될 책입니다.
문제를 해결하기 위한 여러가지 방법을 제시하기 때문입니다.
Python을 이용하고 있지만 사실 Python, 싸이킷런, 이런것들을 설명하고 있다는 점 보다는
어떤 확률공식이 있는지, 어떻게 사용되고 있는지를 알 수 있다는 점에서 높은 점수를 주고 싶습니다.
여러분이 머신러닝을 공부를 어느 정도 하였고, 어떻게 쓰이는 지 알고 싶으신 분들에게 추천합니다.

아직 다 읽지는 못하였지만
읽으면서 느낀 점은 수학공부 확률공부를 해야겠다는 것입니다.

2018년 4월 22일 일요일

조합/중복조합 [파이썬]

역시 파이썬이 더 재미있다.
def factorial(n):
  if n <= 1:
    return 1
  else:
    result = 1
    for i in range(2, n+1):
      result *= i
    return result

# nCk = n! / (n-k)! * k!
def comb_slow(n, k):
  return factorial(n) / (factorial(k)*factorial(n-k))
  
print(comb_slow(6,3))

def comb_fast(n, k):
  numerator = 1
  denominator = 1
  k = min(n-k, k)  # nCk에서 더 적은 수로 계산하기 위해서.
  den_list = []
  num_list = []
  for i in range(k):  # 기존 소스에서 이곳을 변경함.
    denominator *= i + 1
    numerator *= n-i
    den_list.append(i+1)
    num_list.append(n-i)
  print('den_list={}'.format('*'.join(str(x) for x in den_list)))
  print('num_list={}'.format('*'.join(str(x) for x in num_list)))
  
  return numerator/denominator
  
print(comb_fast(6,3))

# 번외 중복조합
# nHr = n+r-1Cr
def ham(n, k):
  return comb_fast(n+k-1, k)
  
print(ham(6, 3))
# 참고문헌 : https://smlee729.github.io/python/algorithm/2015/03/08/1-nchoosek.html

최대 이익 투자 [C언어]

출처 : 문제로 풀어보는 알고리즘

#include 
/*
최대이익투자 : 문제로 풀어보는 알고리즘

4 3  투자금액, 투자 가능 기업의 수
2 3 1  기업별 1만원 투자시 이익
4 5 3  기업별 2만원
7 6 7  기업별 3만원
9 8 9  기업별 4만원

return 10 : 최대이익.
4 3
2 3 1
4 5 3
7 6 7
9 8 9
*/

#define M 100
#define N 100

int r[M][N];
int max_return[M][N];

void calculate_return(int n, int m)
{
  int i, j, k, max, t;

  // dynamic programming을 위해 미리 첫번재 기업을 세팅을 한다. (처음이니까 무조건 max)  
  for (i = 0; i <= m; i++)  {
    // 맨 윗줄.
    max_return[0][i] = r[0][i];
  }
  
  for (i = 0; i < n; i++) {
    for (j = 0; j < m+1; j++) {
      printf("%d ", r[i][j]);
    }
    printf("\n");
    
  }
  
  
  // 두번째줄부터 시작함.
  for (i = 1; i < n; i++)  // i는 기업.
    for (j = 1; j < m+1; j++) {  
    // j는 총 투자할 양 m+1인 이유는 m=0인 경우(투자안한경우)도 다뤄야 하기 때문.
    // 기업의 수(i) 투자할 양(j)를 작은 수로 시작해서 기록-> 큰 수로 간다.
      max = -1;
      for (k = 0; k <= j; k++) {  
// 이전에 투자한 회사와 액수에 대한 내용을 바탕으로 더한다.
// t = 이전 회사에 k를 투자한양과 지금 회사에 j에서 k만큼 이전회사에 투자한 양을 뺀 경우.
// 왜 계속 이전회사만하고만 비교를 하냐면 동적프로그래밍으로 계속 그 최대값이 각 배열마다 누적될 것이다. 
// 그러니 이전 내용과는 비교할 이유가 없다.
        t = max_return[i-1][k] + r[i][j - k];  // 무조건 왼쪽 + 위쪽인데.
        //printf("i=%d, j=%d, k=%d t: %d\n", i,j,k,t);
        if (t > max)
          max = t;
      }
      max_return[i][j] = max;
    }
    

}

int main(void) {
  int m, n, i, j;
  
  scanf("%d %d", &n, &m);
  for (i = 0; i < n; i++)
    r[i][0] = 0;

  for (i = 1; i <= m; i++)
    for (j = 0; j < n; j++)
      scanf("%d", &r[j][i]); // 정보를 받아온다.
      
  calculate_return(n, m);
  printf("Max return: %d\n", max_return[n-1][m]);
  return 0;
}

2018년 4월 21일 토요일

무게가 있는 길찾기 [C 언어]

#include 

#define M 100
#define N 100

int map[M][N];

int max(int x, int y)
{
  if (x > y)
    return x;
  return y;
}

int max_joy(int m, int n)
{
  if (m == 0 && n == 0) {
    return map[0][0];
  }
  // 맨 위인경우.
  if (m == 0) {
    return max_joy(m, n-1) + map[m][n];
  }
  // 맨 왼쪽인경우.
  if (n == 0) {
    return max_joy(m-1, n) + map[m][n];
  }
  // 위,왼 중에 max를 골라서 더함.
  return max(max_joy(m-1, n), max_joy(m, n-1)) + map[m][n];
}

int joy[M][N];
void dynamic_joy(int m, int n)
{
  int i, j;
  
  joy[0][0] = map[0][0]; // 0,0 처음세팅.
  // 맨위,맨왼 세팅
  for (i = 1; i < m; ++i) {
    joy[i][0] = joy[i-1][0] + map[i][0];
  }
  for (j = 1; j < n; ++j) {
    joy[0][j] = joy[0][j-1] + map[0][j];
  }
  
  // 그 사이들 세팅.
  for (i = 1; i < m; ++i)
    for (j = 1; j < n; ++j)
      joy[i][j] = max(joy[i-1][j], joy[i][j-1]) + map[i][j];
}

/*
5 5
1 2 2 1 5
1 4 4 3 1
2 1 1 1 2
1 3 5 1 1
1 5 1 2 2
*/
int main() {
  int m, n, i, j;
  
  scanf("%d %d", &m, &n);
  for (i = 0; i < m; i++)
    for (j = 0; j < n; j++)
      scanf("%d", &map[i][j]);
  printf("%d\n", max_joy(m-1, n-1));
  
  dynamic_joy(m, n);
  
  for (i = 0; i < m; i++) {
    for (j = 0; j < n; j++)
      printf("%d ", joy[i][j]);
    printf("\n");
  }
  return 0;
}

동적프로그래밍 길찾기 [C 언어]

#include 

#define M 100
#define N 100

int map[M][N];

long long num_path(int m, int n)
{
  // 장애물이면 0
  if (map[m][n] == 0) {
    return 0;
  }
  // m=i,n=j에서 0,0으로 갔으면 +1
  if (m == 0 && n == 0) {
    return 1;
  }
  
  return ((m > 0) ? num_path(m - 1, n) : 0)
    + ((n > 0) ? num_path(m, n - 1) : 0);
}

// 와... 값을 리턴하는 것이 아닌 map에 값을 넣는다.
long long path[M][N];
void dynamic_programming_path(int m, int n)
{
  int i, j;  // 이중포문용
  path[0][0] = map[0][0];
  
  
  // 1. 세로 맨 왼쪽은 왼쪽을 더할필요가 없음.
  for(i = 1; i < m; ++i) { //i=0 : path[-1][0]이 없기 때문에 스킵
    if (map[i][0] == 0) {
      path[i][0] = 0;
    } else {
      path[i][0] = path[i-1][0];  // 2. 여기 path[i][0-1]
    }
  }
  // 2. 가로 맨 왼쪽은 위쪽을 더할 필요가 없음.
  for(j = 1; j < n; ++j) { // j=0 은 path[0][-1]가 없기 때문에 스킵
    if (map[0][j] == 0) {
      path[0][j] = 0;
    } else {
      path[0][j] = path[0][j-1]; // 2. 여기 path[0-1][j]
    }
  }

  for (i = 1; i < m; i++) {
    for (j = 1; j < n; j++) {
      if (map[i][j] == 0) {
        path[i][j] = 0;
      } else {
        // 동적프로그래밍 한칸 한칸 나아간다.
        path[i][j] = path[i-1][j] + path[i][j-1];
      }
    }
  }
}


/*
5 5
1 1 1 1 1
1 1 0 0 1
1 1 1 1 1
1 1 1 0 1
0 0 1 1 1
*/
int main(void) {
  int m, n, i, j;

  scanf("%d %d", &m, &n);
  for (i = 0; i < m; i++) {
    for (j = 0; j < n; j++) {
      scanf("%d", &map[i][j]);
    }
  }
  // 반대로 가는듯.
  // printf("%lld\n", num_path(m-1, n-1));
  dynamic_programming_path(m, n);

  return 0;
}
출처: 문제로 풀어보는 알고리즘

2018년 4월 15일 일요일

수 분할 [C 언어]

#include 
// 수분할

// partition(0, m) =1 
// partition(n,m) = i~m partition(n-i,i)
int partition(int n, int m)
{
  int count = 0, i;
  
  // n/m 수분할은 n/n 수문할과 같으므로 m에 n을 대입한다.
  // 5를 6까지 이용해서 분할할 수는 없으니까
  if (n < m) {
    // printf("n=%d, m=%d\n", n, m);
    m = n;
  }
  if (n == 0) // Base Case 0
    return 1;

  for (i = 1; i <= m; i++) {
    // printf("call partition(%d, %d);\n", n-i, i);
    count += partition(n - i, i);
  }
  return count;
}

#define MAX 1000
int partition_cache(int n, int m)
{
  static int cache[MAX][MAX];
  int count = 0, i;
  
  if (n < m) {
    m = n;
  }
  if (cache[n][m]) {
    return cache[n][m];
  }
  if (n == 0) {
    return cache[n][m] = 1;
  }
  
  for (i = 1; i <= m; i++) {
    count += partition_cache(n - i, i);
  }
  
  return cache[n][m] = count;
}

int main(void) {
  // printf("first %d\n", partition(1000,4));
  printf("second %d\n",  partition_cache(1000, 4));
  return 0;
}

금액계산 경우의 수 [C 언어]

#include 
// 금액 맞추기
// 브루트포스 / 재귀
// 일정금액을 1,2,5,10,20,50 만원으로 계산 가능 가짓수.


void brute(int* bills, int money) {
  int i0, i1, i2, i3, i4; // 각 계산 후 남은 수.
  int count = 0;
  for (i0 = money; i0 >= 0; i0 -=bills[0])
    for (i1 = i0; i1 >= 0; i1 -= bills[1])
      for (i2 = i1; i2 >= 0; i2 -= bills[2])
        for (i3 = i2; i3 >= 0; i3 -= bills[3])
          for (i4 = i3; i4 >= 0; i4 -= bills[4])
            if (i4 % bills[5] == 0)
              count++;
              
  printf("count = %d\n", count);
}

int better(int* bills, int n, int money)
{
  int count = 0, i;
  
  if (n == 1) {
    if (money % bills[0] == 0)
      return 1;
    else
      return 0;
  }

  int len = money / bills[n-1];
  for (i = 0; i <= len; i++) {
    count += better(bills, n-1, money - (bills[n-1] * i));
  }
  
  return count;
}

int main(void) {
  int bills[6] = {1,2,5,10,20,50};
  int money = 100; // res = 4562
  int numberOfBills = 6;
  
  brute(bills, money); 
  // printf("better way : %d\n", better(bills, numberOfBills, money));
  
  return 0;
}

[fibonacci] 피보나치 [C 언어]

피보나치 지긋지긋하다.
#include 
#include 
// 피보나치 수열

long long fibo(int n)
{
  if (n == 1 || n == 2) {
    return 1;
  }
  
  return fibo(n-1) + fibo(n-2);
}

// cache
#define MAXSIZE 100
long long fibo2(int n) {
  static long long cache[MAXSIZE];
  
  if (cache[n])
    return cache[n];
    
  if (n == 1 || n == 2)
    // cache[n] = 1;
    return cache[n] = 1;
    
  // cache[n] = fibo2(n-1) + fibo2(n-2);
  return cache[n] = fibo2(n-1) + fibo2(n-2);
}

// 재귀 없이 풀기
long long fibo3(int n) {
  int i;
  long long f_res, f_a, f_b;
  
  if (n == 1 || n == 2)
    return 1;

  // f(3) = f(2) + f(1);
  // f(n) = f(n-1) + f(n-2)
  f_a = 1;
  f_b = 1;
  for (i = 3; i <= n; ++i) {
    f_res = f_a + f_b;
    f_a = f_b;
    f_b = f_res;
  }
  
  return f_res;
}


int main(void) {
  printf("Hello World\n");
  // printf("%lld\n", fibo(100)); 
  printf("%lld\n", fibo2(100));
  printf("%lld\n", fibo3(100)); 
  return 0;
}

[binomial_coefficient] 이항계수 [C 언어]

// 이항계수
#include 

// nCr = n-1Cr-1 + n-1Cr
long long binomial_coefficient(int n, int r)
{
  if (r == 0 || r == n)
    return 1;
    
  return binomial_coefficient(n-1, r-1) + binomial_coefficient(n-1, r);
}

#define MAX_SIZE 100
// cache
long long binomial_coefficient2(int n, int r)
{
  static long long cache[MAX_SIZE][MAX_SIZE] = { 0, };
  
  if (cache[n][r]) {
    return cache[n][r];
  }
  
  if (r == 0 || r == n)
    return 1;
    
  cache[n][r] = binomial_coefficient2(n-1, r-1) + binomial_coefficient2(n-1, r);
  return cache[n][r];
}
int main(void) {
  printf("Hello World\n");
  //printf("%lld\n", binomial_coefficient(100,52)); // 5C2
  printf("%lld\n", binomial_coefficient2(100,52)); // 5C2
  return 0;
}

factorial 두가지 방법 [C 언어]

#include 

int factorial(int n)
{
  int res, i;
  
  res = 1;
  for(i = 2; i <= n; i++) {
    res *= i;
  }
  return res;
}

int factorial2(int n)
{
  if (n == 1)
    return 1;
    
  return n * factorial2(n - 1);
}

int main(void) {
  printf("Hello World\n");
  printf("%d\n", factorial(5));
  printf("%d\n", factorial2(5));
  return 0;
}

2018년 4월 10일 화요일

[책리뷰] C++를 대하는 한 남자의 철학적 사견 [이것이 C++다]

C++에 대해서 나는 잘 모르지만 교보문고에 항상 이 책이 진열되어 있는 것을 보아왔다.
Java개발을 하기에 C++에 대해서 잘 모르지만,
가끔 간단하게 C언어로 된 언어를 봐야 한다거나,
C++로 된 내용을 볼 때 조금이나마 도움이 될까 해서 읽게 되었다.

모든 언어가 그렇듯이 한권의 책을 읽고 그 언어를 이해할 수는 없을 것이다.
모든 언어를 익힐 때 그렇듯이 결국 도큐먼트를 읽고 레퍼런스를 읽어야 할 것이다.

그렇다면 왜 우리는 왜 이런 책을 읽어야 하는가
바로 먼저 개발을 시작한 선배 개발자의 사견이 들어가 있기 때문이다.
또한 그들의 통찰력을 얻을 수 있기 때문일 것이다.

저자는 제목부터 그런것들을 보여줄 것임을 공표한다. [이것이 C++이다]는 C++에 대한 깔끔한 레퍼런스를 제공한다는 것보다는
"이것이 내가 생각하는 C++이며, 나의 세상이다." 라는 느낌으로 이해하는 것이 더 옳다.

그러므로 저자는 C++을 가르치기보다 C++을 이용하여 자신이 생각하는 "C++을 이용한 개발"에 초점을 둔다.

그러므로 언어보다는 개발을 문서보다는 철학을 말한다.
인터넷에 검색을 해보면 저자의 강의도 어렵지 않게 구할 수 있다.
그곳에는 책에서는 못다한 저자의 사견을 마저 들을 수 있다.

짜임새가 있는 책인지는 잘 모르겠다. 하지만 일관적이다.
이 책을 다 읽지는 않았다. 아마 C++을 업무에서 주로 쓰게 된다면 다시 읽을 일은 없을 것이다.
다른 언어로 개발을 해도 이 책은 초중반부는 아주 중요한 것을 느끼게 해준다.

진지하게 읽어주기 바란다.
이 책은 깔끔하고 간결하게 쓴 책이 아니다.
오히려 이말 저말이 뒤섞여 있고 같은 말을 반복하는 세상 풍파를 겪은 어르신의 말처럼 어지럽다.
하지만 철학이 담겨 있고, 마음이 담겨 있다.

아마 저자는 이 책을 쓰면서 때로 가슴 속에 무언가가 울컥하기도 하였을 것이다.
C++을 처음 시작하는 분에게는 추천하지 않는다. (나처럼...)
하지만 언젠가는 읽어야 할 것이다.

2018년 4월 9일 월요일

[python] property 알기

# property

class Square(object):
  def __init__(self, side):
    self.side = side
  def aget(self):
    return self.side ** 2
  def aset(self, value):
    print('no')
  def adel(self, value):
    print('no')
  area = property(aget, aset, adel, doc='area') # 이게 중요!!
  
s = Square(5)
print(s.area)



"""Properties with decorators.
데코레이터를 사용하게 된다. 위에 내용과 같다.
"""

from __future__ import print_function

class Square(object):
    """A square using properties with decorators.
    """
    def __init__(self, side):
        self.side = side
    @property
    def area(self):
        """Calculate the area of the square when the
        attribute is accessed."""
        return self.side * self.side
    @area.setter
    def area(self, value):
        """Don't allow setting."""
        print("Can't set the area")
    @area.deleter
    def area(self):
        """Don't allow deleting."""
        print("Can't delete the area.")

if __name__ == '__main__':

    square = Square(5)
    print('area:', square.area)
    print('try to set')
    square.area = 10
    print('area:', square.area)
    print('try to delete')
    del square.area
    print('area:', square.area)

[python] 스레드 상속하기(subclass)

import time
import threading

class MyThread(threading.Thread):
  def __init__(self, number, style, *args, **kwargs):
    super().__init__(*args, **kwargs)
    self.number = number
    self.style = style

  def run(self, *args, **kwargs):
    print('thread starting')
    super().run(*args, **kwargs)
    print('thread has ended')

  def sleeper(num, style):
    print('sleeping for {} {}'.format(num, style))
    time.sleep(num)


t = MyThread(number=3, style='yellow', target=sleeper, args=[3, 'yellow'])
t.start()

[python] 난수 생성하기

출처 : https://stackoverflow.com/questions/2257441/random-string-generation-with-upper-case-letters-and-digits-in-python

파이썬에서 난수를 생성할때 알파벳과 숫자를 더해서 만들 수 있겠다.


파이썬의
''.join(secrets.choice(string.ascii_uppercase + string.digits) for _ in range(N))
random_numbers = set()
for _ in range(240):
    res = ''.join(random.choices(string.ascii_uppercase + string.digits, k=10))
    random_numbers.add(res)

with open('random_number.txt', 'w', newline='\n') as f:
    [f.write(num+'\n') for num in random_numbers]
print(len(random_numbers))

하지만 이건 좀 안전하지 않다.
res = ''.join(random.SystemRandom()
                    .choice(string.ascii_uppercase + string.digits) for _ in range(10))
이렇게 사용하는게 좋겠다.

[largorithm][mergesort] 머지소트 (C / Python)

C Language


main.c

#include 
#include 
#include "mergesort.h"

void printArray(int value[], int count);

int main()
{
    int values[] = { 80, 75, 10, 60, 15, 49, 12, 25 };
    int count = sizeof(values)/sizeof(int);

    mergesort(values, count);
    printArray(values, count);
    return 0;
}


void printArray(int value[], int count)
{
    int i = 0;
    for(i = 0; i < count; i++) {
        printf("%d ", value[i]);
    }
    printf("\n");
}

mergesort.h

#ifndef MERGESORT_H_INCLUDED
#define MERGESORT_H_INCLUDED

void mergesort(int* arr, int count);

#endif // MERGESORT_H_INCLUDED

mergesort_impl.c

#include 
#include 

#include "mergesort.h"

void _mergesort(int* arr, int* pBuffer, int start, int end);
void _mergesort_division(int* arr, int* pBuffer, int start, int middle, int end);

void mergesort(int* arr, int count)
{
    int* pBuffer = (int*)malloc(sizeof(int) * count);
    if(pBuffer != NULL)
    {
        _mergesort(arr, pBuffer, 0, 7);

        free(pBuffer);
    }
}

void _mergesort(int* arr, int* pBuffer, int start, int end)
{
    int middle = 0;
    if (start < end)
    {
        middle = (start + end) / 2;
        _mergesort(arr, pBuffer, start, middle);
        _mergesort(arr, pBuffer, middle+1, end);
        _mergesort_division(arr, pBuffer, start, middle, end);
    }
}

void _mergesort_division(int* arr, int* pBuffer, int start, int middle, int end)
{
    int i = 0, j = 0, k = 0, t = 0;
    i = start;
    j = middle + 1;
    k = start;

    while(i <= middle && j <= end)
    {
        if(arr[i] <= arr[j])
        {
            pBuffer[k] = arr[i];
            i++;
        }
        else
        {
            pBuffer[k] = arr[j];
            j++;
        }
        k++;
    }

    if(i <= middle)
    {
        for(t = i; t <= middle; t++, k++)
        {
           pBuffer[k] = arr[t];
        }
    }
    else
    {
        for(t = j; t <= end; t++, k++)
        {
            pBuffer[k] = arr[t];
        }
    }

    for (t = start; t <= end; t++)
    {
        arr[t] = pBuffer[t];
    }
}

Python

def merge_sort(arr):
    print('split', arr)
    if len(arr) > 1:
        mid = len(arr) // 2
        left = arr[:mid]
        right = arr[mid:]
        
        merge_sort(left)
        merge_sort(right)

        i, j, k = 0, 0, 0

        while i < len(left) and j < len(right):
            if left[i] < right[j]:
                arr[k] = left[i]
                i += 1
            else:
                arr[k] = right[j]
                j += 1
            k += 1

        while i < len(left):
            arr[k] = left[i]
            i += 1
            k += 1

        while j < len(right):
            arr[k] = right[j]
            j += 1
            k += 1
    print('merge', a)
a = [80, 75, 10, 60, 15, 49, 12, 25]
merge_sort(a)
print(a)

[quicksort] 퀵소트 (quicksort with C / Python)

C-language


main.c

#include 
#include 
#include 

#include "selection_sort.h"
#include "quicksort.h"

void printArray(int value[], int count);

int main()
{
    int values[] = { 80, 75, 10, 60, 15, 49, 12, 25 };
    int count = sizeof(values)/sizeof(int);


    printf("quicksort\n");
    // selection_sort(values, count);
    quicksort(values, count);
    printArray(values, count);
    return 0;
}


void printArray(int value[], int count)
{
    int i = 0;
    for(i = 0; i < count; i++) {
        printf("%d ", value[i]);
    }
    printf("\n");
}

quicksort.h

#ifndef QUICKSORT_H_INCLUDED
#define QUICKSORT_H_INCLUDED

void quicksort(int* value, int count);
void _quicksort(int* value, int start, int end);
int quicksort_division(int* value, int left, int right);
void swap(int* left, int* right);
#endif // QUICKSORT_H_INCLUDED

quicksort.c

#include 
#include "quicksort.h"

void quicksort(int* value, int count)
{
    _quicksort(value, 0, count -1);
}

void _quicksort(int* value, int start, int end)
{
    int pivot = 0;

    if (start < end) {
        pivot = quicksort_division(value, start, end);
        _quicksort(value, start, pivot - 1);
        _quicksort(value, pivot+1, end);
    }
}

int quicksort_division(int* value, int left, int right)
{
    int pivot = left;

    while(left = value[pivot]) && (left < right))
            right--;

        if(left < right)
        {
            swap(&value[left], &value[right]);
        }
    }

    swap(&value[pivot], &value[right]);

    return left;
}
void swap(int* left, int* right)
{
    int temp = *left;
    *left = *right;
    *right = temp;
}

Python

def quicksort(arr):
    _quicksort(arr, 0, len(arr)-1)

def _quicksort(arr, start, end):
    if start < end:
        pivot = _quicksort_division(arr, start, end)
        _quicksort(arr, start, pivot-1)
        _quicksort(arr, pivot+1, end)

def _quicksort_division(arr, left, right):
    pivot = left
    while left < right:
        while arr[left] < arr[pivot] and left < right:
            left += 1
        while arr[right] >= arr[pivot] and left < right:
            right -= 1

        if left < right:
            arr[left], arr[right] = arr[right], arr[left]

    arr[left], arr[pivot] = arr[pivot], arr[left]

    return left

a = [80, 75, 10, 60, 15, 49, 12, 25]

quicksort(a)

[python][hackerrank] Append and Delete


link : https://www.hackerrank.com/challenges/append-and-delete/problem
#!/bin/python3
from itertools import zip_longest

s = input().strip()
t = input().strip()
k = int(input().strip())
output = 0

for idx, (i,j) in enumerate(zip_longest(s, t)):
    if i is None or j is None or i != j:
        output = len(s[idx:] + t[idx:])
        break

if k >= len(s+t):
    print('Yes')
elif k >= output and (k - output) & 1 == 0:
    print('Yes')
else:
    print('No')

[python][hackerrank] Find Digits (3 ways)



input1 = '1012'

number = int(input1)
n = len(input1)
res = 0
for i in range(n):
    _in = int(input1[i])
    if (_in != 0 and number % _in == 0):
        res += 1
       
print(res)

input2 ='1012'
number2 = int(input2)
a = (int(x) for x in input1)
res2 = 0
for i in a:
    if i != 0 and (number2 % i) == 0:
        res2 += 1
print(res2)

input3 = '1012'
number3 = int(input3)
# int_generator = (int(x) for x in input1)
res3 = sum( 1 for y in (int(x) for x in input1)
                    if y != 0 and number3 % y == 0)

print(res3)