2021년 1월 9일 토요일

활기찬 하루의 시작, 정원삼 6년근 고려홍삼정 365 스틱으로!

안녕하세요, 건강과 활력을 사랑하는 여러분! 오늘은 일상에 에너지를 더해줄 특별한 제품을 소개해드리려고 합니다.

바로 정원삼 6년근 고려홍삼정 365 스틱 + 쇼핑백, 300g입니다.

https://link.coupang.com/a/bS8PWZ
"이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다."

6년근 고려홍삼의 진한 에너지

정원삼의 홍삼정 365 스틱은 엄선된 6년근 고려홍삼만을 사용하여 만들어졌습니다. 오랜 시간 정성껏 키워낸 홍삼의 깊은 풍미와 농축된 유효성분을 그대로 담아냈습니다.

  • 진한 풍미: 홍삼의 깊고 진한 맛을 간편하게 즐길 수 있습니다.
  • 풍부한 사포닌: 홍삼의 핵심 성분인 사포닌이 풍부하게 함유되어 있습니다.


언제 어디서나 간편하게, 스틱 포장

바쁜 현대인을 위해 스틱형 포장으로 제작되어 휴대가 간편합니다. 출근길, 운동 전후, 피로를 느낄 때 언제든지 한 스틱으로 활력을 충전하세요.



고급스러운 쇼핑백 포함, 선물용으로도 최고!

함께 제공되는 고급스러운 쇼핑백으로 소중한 분들께 감사의 마음을 전해보세요. 부모님, 친구, 동료 누구에게나 건강을 선물할 수 있습니다.

정원삼을 선택해야 하는 이유

  • 신뢰할 수 있는 품질: 엄격한 품질 관리로 안전하고 믿을 수 있는 제품만을 제공합니다.
  • 합리적인 가격: 고품질의 홍삼을 합리적인 가격에 만나보세요.
  • 고객 만족도 1위: 많은 고객들이 만족한 베스트셀러 제품입니다.

지금 바로 경험해보세요!

일상의 활력을 찾고 싶으신가요? 그렇다면 정원삼 6년근 고려홍삼정 365 스틱이 그 해답입니다. 건강한 에너지로 가득한 하루를 시작해보세요.


https://link.coupang.com/a/bS8PWZ

"이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다."

2020년 2월 25일 화요일

2020 Google Hash Code

2020 Google Hash Code Online Qualification Round에 참가했습니다.

저포함 4명의 팀원들과 3번정도의 연습문제와 기출문제(2019, 2018)를 풀어 대략적인 대회 참여 전략을 세웠고 
어느정도 heuristic optimization algorithm(GA, SA)를 학습하였습니다. (실제 대회에서는 사용하지 않음ㅠㅠ)



대회시간은 새벽 02:45 ~ 06:30으로 일개 회사원들에겐 최악의 시간대였습니다.
저흰 새벽1시쯤 뜨끈~뜨끈하고 든~든한 국밥을 먹고 24시 스터디룸에서 대회에 참가하기로 하였습니다.

대회가 시작되었고 문제는 기출과 비슷하게 간단 명료하였습니다. 다만 data set이 기존에 주었던 a~e까지가 아닌 
a~f까지 6개의 data set이 있었습니다.




문제를 요약하자면 다음과 같습니다.
[0, B-1]까지의 unique한 book id가 주어지고 [0, L-1]의 unique한 Library가 주어집니다.
각 Library에는 book들이 존재하고 하루동안 scan할 수 있는 book 수가 정해져있습니다.


또 Library마다 'signup process'가 주어지는데 'signup process'가 진행되어야 book을 scan할 수 있습니다.
이 'signup process'는 해당 시간에 최대 한개의 Library만 진행할 수 있습니다.
book$_{i}$을 scan할 때 얻는 score$_{i}$가 제공될 때 점수를 최대화하는 문제입니다.


처음 떠올린 풀이는 현재 $L_{i}$를 선택하였을 때 'signup process'를 끝내고 얻을 수 있는 
최대점수의 Library를 택하고 이를 반복하는 naive한 풀이였습니다.
이 풀이로 B는 만점풀이를 받았지만 D,E,F의 점수는 많이 낮았습니다. 
(hyperparameter tuning으로 비빈결과 C에서는 다음에 나올 풀이와 점수차이가 많이 나지 않았습니다.)

그 다음 풀이로는 'signup process'당 얻을 수 있는 점수가 가장 큰 $L_{i}$를 택하는 풀이였습니다.
해당 풀이로는 기존의 점수보다 상당히 높은 점수를 얻었습니다.
사실상 이 풀이로 C, E, F (D는 얻을 수 있는 max치의 점수를 얻었다 생각하고 pass하였음)를 비벼 
다음과같은 점수를 받았습니다.


등수는 많이 낮았지만 대회동안 상당히 재미있었기 때문에 즐겁게 마무리했던것 같습니다.

대회가 끝나고 상위등수 분들의 풀이는 다양했지만 greedy하게 $L_{i}$를 택하고 순서를 정하였을 때
mcmfbook을 선택할 때 중복을 제거하여 택하는 솔루션이 흥미로웠습니다.
또한 데이터를 분석하는 능력과 여러 최적화 기법을 알게되어 좋은 계기가 되었던것 같습니다.

2019년 9월 15일 일요일

SOLID 원칙 - SRP, OCP, LSP

개발에 있어서 구조(Architecture)는 매우 중요하다.
수정에 있어서 용이하게 구현을 해놓는것이 곧 생산성과 직결되며 유능한 개발자로 보여질 수 있다.

Robert C. Martin은 5가지 원칙(S.O.L.I.D)을 제시하였다. 
해당 원칙은 디자인 패턴에 녹아있으며 
가독성과 유지보수가 좋은 프로그램을 개발할 수 있게 도와준다.

1. Single Responsibility Principle (단일 책임 원칙)
- 각 클래스는 단 한가지의 책임을 부여받아 수정할 이유가 한가지여야 한다.

제목 하나에 내용이 들어있는 메모장과 메모장의 내용을 파일에 저장하는 기능을 개발한다고 하자.
class Journal {
public :
    explicit Journal(const string &title) :title(title){}
    ~Journal() {}
 
    string title;
    vector<string> entries;
 
    void add(const string& entry) {
        entries.push_back(entry);
    }
    void save(const string& filename) {
        ofstream ofs(filename);
        for (auto& s : entries)
            ofs << s << endl;
    }
};
cs

위와 같이 add,save함수를 이용하여 구현할 수 있다.
하지만 Journal class의 목적은 메모 항목들을 기입/관리하는 것이지 디스크에 쓰는것은 아니다.
앞선 방법으로 구현하게 되면 차후 수정사항에 대해 고통스러운 상황에 처할 수 있다.

따라서 Journal class의 저장 기능을 새로운 class에서 수행하는것이 옳다.
class Journal {
public :
    explicit Journal(const string &title) :title(title){}
    ~Journal() {}
 
    string title;
    vector<string> entries;
 
    void add(const string& entry) {
        entries.push_back(entry);
    }
};
 
class PersistenceManager {
public:
    static void save(const Journal &journal, const string& filename) {
        ofstream ofs(filename, std::ofstream::out);
        for (auto& s : journal.entries)
            ofs << s << endl;
    }
};
int main() {
    Journal memo("design pattern");
    memo.add("builder pattern");
    memo.add("factory pattern");
 
    PersistenceManager::save(memo, "Design Pattern.txt");
    return 0;
}
cs
 
2. Open-Closed Principle (열림-닫힘 원칙)
- 확장에는 열려있지만 수정에는 닫혀있어야 한다.

단순히 파일에만 저장하는 방식이 아닌 서버에 저장하거나 다른 종류의 저장공간에 저장하는것을 구현한다고 하자.
class PersistenceManager {
public:
    static void save_file(const Journal &journal, const string& filename) {
        ofstream ofs(filename, std::ofstream::out);
        for (auto& s : journal.entries)
            ofs << s << endl;
    }
    static void save_server(const Journal& journal, const Server& server) {
        // ...
    }
    static void save_blabla(const Journal& journal, ...) {
        // ...
    }
};
cs

위의 구현은 OCP를 만족하지 않고있다.
새로운 저장방식이 추가됨에 따라 해당 클래스를 수정해야하기 때문이다.
class SavingMethod {
public:
    virtual void saving(const Journal &journal, const string &filename) = 0;
};
class FileSave : public SavingMethod{
public :
    void saving(const Journal& journal, const string& filename) override{
        ofstream ofs(filename, std::ofstream::out);
        for (auto& s : journal.entries)
            ofs << s << endl;
    }
};
class ServerSave :public SavingMethod {
public :
    void saving(const Journal& journal, const string& filename) override {
        // ...
    }
};
class PersistenceManager {
public:
    static void save(const Journal &journal, const string& filename, SavingMethod &saving_method) {
        saving_method.saving(journal, filename);
    }
};
int main() {
    Journal memo("design pattern");
    memo.add("builder pattern");
    memo.add("factory pattern");
 
    SavingMethod *file_save = new FileSave;
    SavingMethod *server_save = new ServerSave;
 
    PersistenceManager::save(memo, "Design Pattern.txt"*file_save);
    PersistenceManager::save(memo, "Design Pattern.jar"*server_save);
 
    delete file_save;
    delete server_save;
    return 0;
}
cs
SavingMethod class를 이용해서 기존의 PersistenceManager의 save 기능은 유지하되(수정)
추가 저장 기능이 요구될때(확장) 인터페이스를 건드리지 않는 방식을 제시할 수 있다.


3. Liskov Substitution Principle (리스코프 치환 원칙)
- 객체는 프로그램의 정확성을 깨지 않으면서 하위 타입의 인터페이스 객체로 교체될 수 있어야한다.

상당히 많은 글에서 Rectangle과 Square를 예시로 리스코프 치환 원칙을 설명한다.

class Rectangle {
public:
Rectangle() = default;
void set_width(int width) {
this->width_ = width;
}
void set_height(int height) {
this->height_ = height;
}
protected:
int width_, height_;
};
class Square : public Rectangle {
public:
Square() = default;
void set_width(int width) {
this->width_ = width;
this->height_ = width;
}
void set_height(int height) {
this->height_ = height;
this->width_ = height;
}
};
// Client method
void SetRectangle(Rectangle *obj, int width, int height) {
obj->set_width(width);
obj->set_height(height);
}

위와 같이 Rectangle의 하위 클래스를 Square로 상속받게하면 width와 height을 저장할 때 문제가 발생한다.
다른 Client에서 Rectangle의 원소값을 설정할 때 Square의 너비와 높이가 다르게 설정이 될 수 있다.