2018년 10월 29일 월요일

[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();
}