Posts

Showing posts from December, 2020

design pattern #2 Creational pattern - Abstract Factory

Factory Method 의 좀더 큰 버젼이다. 비슷한 유형의 product 를 하나로 묶어 이것을 interface 로 만들고,   2개 이상의 interface  를  다시 묶어 interface  로  만든다 Factory Method 의 아이디어와 방식이 비슷한데,  creator 가 product 의 abstract type 을 리턴 한다는 것이다. 아래 예시를 보면, button, checkbox 각각 Mac, Window 버젼이 있다.  그리고 이 두 UI 를 abstract 로 묶은 GUIFactory 도 각각 Mac, Window 버젼이 있다. 프로그램 시작시 state 를 구분해서 Mac, 혹은 Window 버젼의 GUIFactory 를 생성한다. 장단점은 factory method 와 같다. public interface Button { void paint(); } public class MacOSButton implements Button { @Override public void paint() { // implement } } public class WindowsButton implements Button { @Override public void paint() { // implement } } public interface Checkbox { void paint(); } public class MacOSCheckbox implements Checkbox { @Override public void paint() { // implement } } public class WindowsCheckbox implements Checkbox { @Override public void paint() { // implement ...

design pattern #1 Creational pattern - Factory Method

Factory 라고 해서, 뭔가 찍어내는 듯한 느낌 이었는데, 그것과는 좀 거리가 멀다. 이론적으로 읽어선 한번에 이해가 안가고, 예제를 통해서 보는게 역시 쉽다. 아래처럼 File  이 Product  라고 하고, Directory 를 Creator 라고 하고, Application 을 client  라고 하면.  요점은 product 의 공통 분모를 interface 로 설계 한다는것 Creator 의 공통 분모는 abstract class 로 설계 한다는것 그리고 Creator 에 여기서 말하는 Factory Method 인 createFile 이 abstract  로 들어가 있는 게 중점 같다. 그러면 creator 와 product  가 tight 하게 coupling 되지 않고 Single Responsibility Principle  을 지킬 수 있으며(factory method 는 createFile 하나뿐이다) 기존 프로그램을 건드리지 않고 new product type 을 추가 할 수 있다. 그리고 각각 공통된 기능은 interface 나 abstract  로 뺄 수 있다. 대신 물론, 더 복잡해진다. *Product   public interface File { void size(); void onClick(); } public class MusicFile implements File { public void size() { //implement } public void onClick() { //implement } } public class PhotoFile implements File { public void size() { //implement } public void onClick() { //implement } }...

21. Merge Two Sorted Lists

해결은 했는데, 공간 복잡도 측면에서 매우 비효율적이었다. 주어진 두 list  의 pointer 들만 잘 바꿔줘도 되는걸, 복잡해서 새로운 node 를 매번 만들었기 때문이다.  아래의 iterative 가 원래 내가 풀고싶던 방식이고, recursive 도 풀기 전 접근 단계에서 생각은 했으나 구현은 못한 방식이다. LinkedList  유형의 문제를 풀때는,  sentinel node 를 하나 만들어서 return 을 쉽게 하는 것 node.next.next  식의 접근 방식도 유용하게 쓰이니 기억할 것 null 체크를 항상 주의 할것 *Iterative public ListNode mergeTwoLists(ListNode l1, ListNode l2) { ListNode ptr1 = l1; ListNode ptr2 = l2; ListNode sentinel = new ListNode(); ListNode ptr3 = sentinel; while (ptr1 != null && ptr2 != null ) { if (ptr1. val <= ptr2. val ) { ptr3. next = ptr1; ptr1 = ptr1. next ; } else { ptr3. next = ptr2; ptr2 = ptr2. next ; } ptr3. next . next = new ListNode(); ptr3 = ptr3. next ; } ptr3. next = (ptr1 == null ) ? ptr2 : ptr1; return sentinel. next ; } *Recursive public ListNode mergeTwoLists (ListNode l1, ListNode l2) { if (l1...

Postgres SELECT WHERE 에서 EXIST / NOT EXIST 에 대해

EXIST 의 parameter 로  임의의  select 구문이나 subquery  를 받는다 판단은, param 의 결과가 최소 1개 이상의 row 를 return 하면 TRUE, 아니면 FALSE 이다 최소 하나라도 return 하는지가 중요하므로, 실제  return  결과는 관심이 없다 그래서 판별식 내부는 주로 select 1 이 쓰인다 far enough to determine 이라서,  statement 를 끝까지 완료하지 않을 수 있다(1개 발견되면 끝) NOT EXIST 는 반대로 판별식을 만족하는 row  가 하나도 없을때 TRUE 를 return 한다 *EXIST 한번이라도 100 보다 큰 amount 를 지불한 적 있는 고객 SELECT name FROM customer c WHERE EXISTS ( SELECT 1 FROM payment p WHERE p.customer_id = c.customer_id AND amount > 100 ) ORDER BY name; *NOT EXIST 한번도 100 보다 큰 amount 를 지불한 적 어는 고객 SELECT name FROM customer c WHERE NOT EXISTS ( SELECT 1 FROM payment p WHERE p.customer_id = c.customer_id AND amount > 100 ) ORDER BY name;

572. Subtree of Another Tree

 Easy  이고, 예전에 풀어봤었고, tree 문제는 잘 파악만 하면 몇줄 안에 해결되는 문제여서 좀 만만하게 생각하고 풀었는데. 대실패.  논리적으로 생각하기보다, 풀었던 기억을 되살리려 했다. Tree 문제의 경우, signature 로 주어진 함수를 자식 node 로 recursive 하게 호출하면 해결되는 문제들을 많이 봐서, 그런식으로 접근하려고 했는데 다양한 edge case  에 번번히 실패 그리고 결국 실행이 되긴 했는데 LTE 떠서 실패 recursive 하게 호출 하려면, 그 함수가 정확히 뭘 하는지 구분 해야한다. 이번 문제에선 recursive 하게 호출하되,  두 TreeNode 가 정확히 matching 되는지 체크하는 함수는 또 따로 recursive 로 돌리는것이 관건이었다. 말하자면 2개의 recursiv 함수가 있는 것이다.  null 체크를 제외한 주요 호출은 아래처럼 된다.  public boolean isSubtree (TreeNode s, TreeNode t) { return exactSame (s, t) || isSubtree (s. left , t) || isSubtree (s. right , t); } private boolean exactSame (TreeNode s, TreeNode t){ if (s. val ==t. val ){ return exactSame (s. left , t. left ) && exactSame (s. right , t. right ); } return false ; } Complexity tree s  의 node 개수를 S, tree t 의 node 개수를 T  라 한다면, 각각의 s 의 노드를 root 로 하는 subtree가 tree t 와 exactly matching 하는지 확인 하므로,  최악의 경우 O(ST) 가 되는것 같 다....

refactoring #6 Dealing with Generalization

subclass 들에 중복되고 거의 유사한 field, method 는 부모 class 로  끌어올린다 부모 class에 있으나 하나의 subclass 에서만 사용된다면, 그쪽으로 내린다 한 class 에 있으나 일부만 따로 떼어질거 같으면 subclass 를 만들고 그쪽으로 다 옮긴다 서로 다른 class 에 비슷한 method 나 비슷한 부분이 있으면 superclass 를 만들고 상속받는다 subclass 가 시간이 지나며 superclass와 거의 똑같아졌다면, 합한다. 대신 다른 subclass 는 없는지, 합쳤을때 문제는 없는지 고려해야한다 시간이 지나면서, subclass 들 사이에 비슷한, 정렬 같은 알고리즘이 거의 비슷하게 존재하게 될 수가 있는데. template 형태로 superclass 로 옮길수 있다면 옮긴다. 경우에 따라 delegation 을 inheritance 로, inheritance 를 delegation 으로 바꿀 수 있다.

refactoring #5 Simplifying Method Calls

아래 내용들은 바꿀 경우, super/sub class 에도 해당 되는지도 체크 해야한다 method  이름은 간결하되, 내용을 제대로 표현하게.  method  에  parameter 추가는 좋으나, parameter 는 늘 추가하는건 쉽고, 삭제하는건 어려우며, 대개 param 사이즈가 엄청 늘어나게 되며 이경우 다른 refactoring 이 필요하다 사용되지 않는 parameter 는 삭제한다   조회하는  query 와 수정을 가하는 modifier 는 따로 다른 method   로 분리되어야 한다.  비슷한 method 인데 parameter 만 다른 경우, 하나로 합치고 parameter 로 분기할수 있는데, 자칫하다간 매우 길고 복잡한 하나의 method 로 합쳐질 수 있어 이 경우는 지양해야한다 위와 반대로, 하나의 method  가 길고 복잡하고 불필요하게 parameter 가 많은 경우, 두개의 method  로 나누고, parameter 도 필요에 따라 쪼갠다. parameter 여러개가 한 object 에서 나올 경우, object 를 그냥 넘겨도 된다. 하지만 int, String 같은 거였다가 object 로 바꿀 경우그럴 경우, 그 object 만이 method 에 쓰일 수 있다.  method 안에서 해결 될 수 있는 계산이면, method 안에서 처리 한다. 밖에서 해서 parameter 로 넘길 필요 없이 parameter  들이 많고, 자주 method 에서 쓰이면 parameter object 로 하나의 class 로 묶고, 관련된 method 들도 거기로 옮겨버리면 깔끔해질 수 있다 생성자에서 변수 할당 이외에 복잡한 작업을 할 경우, factory method  로 대체한다. -1  같은 에러코드를 리턴하는 대신 exception 을 던진다.  0, -1 같은 코드가 아니므...

refactoring #4 simplifying conditional express

if 구문 안이 장황해지면 읽기 힘들다.  가능하면 extract 한다 nested if 를 and 로 묶거나,  동일한 결과를 return 하는 if를 or 로 묶어서 extract한다 if 와 else 모두에 중복된 코드가 있으면 if-else 밖으로 뺀다.  nested hell은 피한다. exception, 바로 return 하는 것들을 최대한 빼고 평평하게 만든다 switch-case 가 길어지고 복잡해지면, 각각의 case 를 abstract- subclass 로 뺄 수 있는지 본다. 이 경우 새로 추가되는 case 가 있다 해도 기존  class 수정 없이 subclass만 신규 추가된다는 부가적인 장점도 있다. 메소드 실행시 Assert.isTrue 로 분명히 할건 assert  걸어둔다. 꼭 JUnit  에서만 쓰이는게 아니다. assertion 을 확실히 해두면 live documentation 이 된다. 물론  overdo 해선 안된다. introduce Null object 는 처음 본다.  예를들어 if(customer==null) 같은 체크가 많으니, NullCustomer 라는  class 를 Customer class  에서 상속받아 만들어서 null check 를 더 간결하게 하는 것이다. 더 복잡하게 하는거 같은데. 어쨌든 이런 방법도 있다니.

819. Most Common Word

 String 을 sanitize 하는 부분에서 애를 먹었다. Map traverse 방법은 물론이고 그게 최적인지 잘 모르겠다.  *Trial and Error sanitize 를 하면서 특수문자를 ""로 바꿨더니 "a,b"  가 "ab" 가 되어버렸다.   trim 하는것을 까먹어,  "cat" 과 "cat " 이 각각 다른 단어가 되어버렸다. "" 가 하나의 단어가 되었다. isEmpty 로 걸렀어야했다. *Complexity Paragraph 의 length 를 l 이라고 하고,  banned 의 size 를 s  라고 하면. set 에 ban 넣는 O(s) 특수문자 sanitize 하는데 O(l) split 하는데 O(l) split  한 단어의 개수를 n 이라 하면, HashMap 에 넣으니까, average O(1), worst O(n) HashMap traverse 한번 하니까 O(n) 다 더하면  O(s)+2O(l)+O(1)/O(n)+O(n) 이다. average : O(s)+2O(l)+O(1)+O(n) = O(s)+O(l)+O(n) worst : O(s)+2O(l)+O(n)+O(n) = O(s)+O(l)+O(n) 결국 O(s+l+n) 의 linear 한 complexity 를 갖는것 같다.  처음 문제를 보고 접근법을 생각할 때, 이를 Trie 를 통해 접근하면 Paragraph 를 한번만 읽으면 되지 않을까 생각했는데, Trie 구현을 안찾아보고 하기 어려워서 그냥 Map 으로 했다. 가장 성능이 좋은 알고리즘을 보니 역시 Trie 로 접근했다. Trie 를 익숙하게 다룰 줄 알아야겠다. 

Map 다루기

 put, getOrDefault *put, getOrDefault Map<String, Integer> map = new HashMap<>(); for (String word: words){ String sanitized = word.trim(); if (!bannedSet.contains(sanitized) && !sanitized.isEmpty()){ map.put(sanitized, map.getOrDefault(sanitized, 0 )+ 1 ); } } *entry traverse for (Map.Entry<String,Integer> entry : map.entrySet()){ if (entry.getValue()>count){ Integer count= entry.getValue(); String result = entry.getKey(); } }

String 다루기

  *String sort in Arrays with comparator Arrays. sort (res, 0 , pLet, (str1, str2) -> { int index1 = str1.indexOf( " " ); int index2 = str2.indexOf( " " ); return str1.substring(index1).compareTo(str2.substring(index2)); }); *String split, indexOf String str = "cat banana dog" ; String[] splitted = str.split( " " , 2 ); int idx = str.indexOf( " " ); String substr = str.substring(idx); System. out .println(splitted[ 0 ]); //cat System. out .println(splitted[ 1 ]); //banana dog System. out .println(idx); //3 System. out .println(substr); // banana dog

TreeMap 과 HashMap

Average 로 보면 HashMap 은  put, get 모두  O(1) 이다.  하지만 collision 이 매번 발생하는 Worst 의 경우에는,  put, get 모두 O(n) 이다.   TreeMap  는 key,val  에서 key 를 중심으로 key,val entry  가 정렬이 되며,   중복된 key 로 put 을 할 경우 당연히 덮혀 씌워진다.  내부적으로 Red-Black tree (balanced tree) 구조로 저장이 되며,  Average/Worst 모두 put,get 에 대해 O(logn) 이다.

코드리뷰에 대하여

우리 개발팀에는 DoR, DoD,DoP 라는 것이 있다. 이전에는 겪어본적이 없는것들이다. 나중에 따로 정리를 하겠지만, 우선은 코드리뷰에 대해 정리를 해보려고 한다.  이전 회사에서도 코드리뷰가 있었다. 처음부터 있었던건 아니고, 나중에 생겼다. 하지만 반드시 reviewer 가 accept 해야 commit 이 되는것도 일부 모듈에 대해서만 적용되었기 때문에 모든 code commit  에 대해 approval  이 필요하지 않았다. 때로는 빨리 검증 받고 배포해야 하는데 review 안되서 이게 걸림돌이라는 느낌이 강했고, 어떤 경우엔 담당자가 나 혼자여서 review 를 해줄 사람이 없었다. 모듈별로 주담당-백업이 나뉘어 있었는데, 백업 모듈에 대해 review 를 하기엔 잘 알지 못하고, 주담당인 모듈에 대해선 누군가 review  를 해줘야하는데 오래 걸리니 걸림돌이 되고. 백업이 없는 경우는 review 가 없는 상황. code reivew  에 대한 인식이 없다가 생긴것은 좋았으나, 그것이 개발에서 꼭 필요한 일부분이라기 보단 형식적인 통과의례로서 병목처럼 여겨졌다. 여기서는 아니다. review 를 받지 못하면 merge 되지 않는다. 우리 팀은 이제 7명. 옆 팀은 더 많다. 한 9명 되나? frontenf, backend 가 한 팀에 있고,  내 모듈, 네 모듈의 개념이 아닌, 우리 팀의 모듈이다. 그래서 특별히 어려운 티켓을 제외하면 누구나 develop, review, test 를 할 수 있게 되어있다.  reviewer  를 찾는 문화도 있다. daily  에서, 혹은 팀 채널에서 티켓 개발이 끝났으니 reviewer  를 찾고 있다, 라든지, 지금 할거 없는데  도움 필요한 사람? 이런식으로 나서기도 한다.  review 도 대충하지 않는다. 철저하다. 여러번 퇴짜를 맞았다. 나도 마찬가지로 적극적으로 의견을 내려고 한다. 아무리...