project euler 38

오일러 프로젝트 38 번

숫자 192에 1, 2, 3을 각각 곱합니다.

    192 × 1 = 192
    192 × 2 = 384
    192 × 3 = 576

곱한 결과를 모두 이어보면 192384576 이고, 이것은 1 ~ 9 팬디지털(pandigital)인 숫자입니다. 이런 과정을 편의상 '곱해서 이어붙이기'라고 부르기로 합니다.

같은 식으로 9와 (1, 2, 3, 4, 5)를 곱해서 이어붙이면 918273645 라는 1 ~ 9 팬디지털 숫자를 얻습니다.

어떤 정수와 (1, 2, ... , n)을 곱해서 이어붙였을 때 얻을 수 있는 가장 큰 아홉자리의 1 ~ 9 팬디지털 숫자는 무엇입니까? (단 n > 1)

http://euler.synap.co.kr/prob_detail.php?id=38

n 을 증가시켜가면서 1, 2, 3 .. 9 와 곱한 결과들을 모아서 팬디지털이 되는지 검사한다. 즉 문제의 내용 그대로를 코딩하면 된다.

def test(n):
    s = ""
    for i in range(1, 10):
        s += str(i * n)
        if len(s) >= 9:
            break
    if "".join(sorted(s)) == "123456789":
        return s
    return None

def e038():
    a = []
    print(max(filter(lambda x:x is not None, (test(x) for x in range(1, 100000)))))

%time e038()
# 932718654
# Wall time: 645 ms

m.mount

m.mount

마운팅은 미스릴 컴포넌트를 DOM 트리로 렌더링하는 과정을 말한다. 하지만 m.mountm.render와 달리 이벤트 핸들러가 트리거될 때 자동으로 렌더링을 갱신하는 매커니즘을 포함한다. 만약 특정 요소가 URL에 따라서 언로드/재마운팅되게 하려면 m.route를 이용한다.

my-comp = 
  controller: -> greeting: \hello
  view: (ctrl) -> m \h1 ctrl.greeting
m.mount document.body, my-comp

m.mount()의 첫 인자는 컴포넌트가 마운트될 DOM요소이고, 두 번째 인자는 컴포넌트 객체이다. 실행이되면 먼저 컴포넌트의 컨트롤러 함수를 호출 한 후, (컴포넌트 함수의 리턴값이나 컴포넌트 함수로 생성한 객체를 이용해서) view 함수를 호출한다. 뷰 함수의 호출결과가 DOM을 치환하게 된다.

m.component

본격 mithril 탐구. 가독성을 위해서 본 문서에서는 LiveScript로 예제를 작성합니다.

m.component

미쓰릴의 컴포넌트는 보다 앱보다도 더 작은 단위의 요소를 작성할 수 있게 해준다. 실질적으로 DOM에 마운트되는 앱 역시 기본적으로는 컴포넌트에 해당한다.

my-component = do
  controller: (data) -> greeting: \Hello
  view: (ctrl) ->
    m \h1, ctrl.greeting

m.mount document.body, my-component

기본적으로 컴포넌트내의 컨트롤러는 view에서 사용하는 헬퍼 함수를 모은 객체를 생성해내는 함수라 보면 된다. (따라서 기본적으로는 앱의 컨트롤러는 완전히 숨겨지게된다.) 뷰는 기본적으로 컨트롤러(정확히는 컨트롤러 객체를 만드는 컨스트럭터)를 받을 수 있고, 컨트롤러 인스턴스가 노출하는 메소드나 객체를 사용할 수 있다. 만약 컨트롤러가 앱 외부의 데이터를 조작할 수 있다면, view 내에서도 이를 호출할 수 있다.

model = count: 0
my-component = do
  controller: (data) -> do
    increment: !-> model.count += 1
  view: (ctrl) ->
    m "a[href=javacript:;]", 
      onclick: ctrl.increment
    , "count: #{model.count}"
m.mount document.body, my-component
# 링크를 클릭할 때마다 카운트 값이 오른다.

컨트롤러와 뷰를 반드시 타이트하게 묶어야 하는 규칙은 사실 없다. 이들은 각각 작성되고, 마운트 시점에 말아서 넣어도 된다.

controller = (data) -> greeting: \hello
view = (ctrl) -> m \h1, ctrl.greeting
m.mount document.body, controller: controller, view: view

사실 뷰에서 참조할 동적 요소가 없다면, 컨트롤러는 없어도 된다.

my-component = view: -> m \h1, \hello
m.mount document.body, my-component

컨트롤러는 클래스 컨스트럭터일 수 있다. (보통은 이렇게 작성하지 않던가?)

my-component = do
  * controller: (data) !->
      @greeting = \hello
  view: (ctrl) -> m \h1, ctrl.greeting
m.mount document.body, my-component

view는 그외의 인자도 받을 수 있다. 컴포넌트 문법을 사용하면 된다.

my-component = do
  * controller: -> greeting: \hello
  view: (ctrl, args) -> m \h1, ctrl.greeting + ' ' + args.data

m.render document.body,
  * m my-component, data: \hello
  m.component my-component, data: \world

혹은

컴포넌트를 생성할 때 두 번째 이후의 인자가 있으면 이는 view, controller에 각각 바운드되어 전달되기도 한다.

my-component = do
  controller: (args, extras) ->
    console.log args.name, extras
    greeting: \hello
  view: (ctrl, args, extras) ->
    m \h1, "#{ctrl.greeting} #{args.name} #{extras}"

component = m.component my-component, {name: \world}, "this is a test"

ctrl = new component.controller!
# logs "world", "this is a test"

m.render document.body, component.view(ctrl)
# <body><h1>Hello world this is a test</h1></body>

이 때, controller 함수는 단 한번만 호출되며, view 함수는 바인딩된 데이터에 변경이 발생하여 뷰 업데이트가 되는 시점마다 호출된다.

정리하면

  1. 컴포넌트는 controller, view 두 개의 함수를 가지는 객체이다.
  2. 컨트롤러는 1)없어도 되며, 2) 데이터를 컨트롤하는 객체를 리턴하거나, 3)데이터 컨트롤러의 컨스트럭터이다. (이 경우는 리턴이 없다)
  3. view의 첫번째 인자는 반드시 컨트롤러 객체이다. (controller 함수의 리턴값이거나, 해당 함수로 생성한 객체)
  4. controllerview는 추가적인 인자를 받을 수 있으며, 해당 인자는 각각의 함수 호출시에 바인드되어 들어간다.

컴포넌트 네스팅

m.component() 를 이용하면 외부에 작성한 컴포넌트 객체를 특정 컴포넌트 내로 삽입할 수 있다. (반복적으로 쓰이는 요소라면 이 패턴을 사용하는 편이 편리하다.)

app = do
  view: ->
    m ".app",
      * m \h1, "My App"
      m.component my-component, {message: \hello}

my-component = do
  controller: (args) -> greeting: args.message
  view: (ctrl) -> m \h2, ctrl.greeting

m.mount document.body, app

이해가는지? 좀 더 복잡한 모양을 도전해보자.

app = do
  controller: -> data: [1 2 3]
  view: (ctrl) ->
    m \.app,
      * m "button[type=button]",
          onclick: !-> ctrl.data.reverse!
        , \MyApp
      item <~ ctrl.data.map
      m.component my-component, do
        message: "hello #{item}"
        key: item

my-component = do
  controller: (args) -> greeting: args.message
  view: (ctrl) -> m \h2, ctrl.greeting

m.mount document.body, app

가장 간단하게 앱을 구성하는 것은 하나의 객체에 컨트롤러와 뷰를 넣고 이 객체(이렇게 구성한 객체는 컴포넌트가 된다)를 특정 DOM에 마운트하는 것이다. 만약 앱의 크기가 커지거나 앱을 쪼개서 관리하고 싶다면 각각의 영역을 다시 컴포넌트로 분리한 다음, 한 컴포넌트에서 다른 컴포넌트를 m.component 혹은 m()을 이용해서 네스팅할 수 있다.

언로딩

만약 컴포넌트의 컨트롤러가 onunload라는 함수를 가지고 있다면, 아래 조건 중 하나에서 해당 함수가 호출된다.

  1. 컴포넌트가 가리키고 있는 DOM의 루트가 새로 마운트 될 때
  2. 라우팅이 변경될 때

이를 통해서 컴포넌트가 언로드되기 직전에 필요한 처리를 할 수 있다. 다음의 코드는 다른 주소로 라우트 될 때 변경사항을 저장하라는 경고를 표시할 수 있다.

comp = 
  controller: -> do
    @unsaved = m.prop no
    unsaved: @unsaved
    onunload: (e) !->
      if unsaved! then e.prevent-default!

이 케이스에서 미스릴은 주소가 변경되는 시점에 언로딩을 직접 후크하지 않기 때문에 언로드 시도를 통해서 이를 감지하도록 이벤트 핸들럴르 추가해주어야 한다.

window.onbeforeunload = -> if not (m.mount root-element, null) then "Are you sure want to leave?"

project euler 37

오일러 프로젝트 37 번

소수 3797에는 왼쪽부터 자리수를 하나씩 없애거나 (3797, 797, 97, 7) 오른쪽부터 없애도 (3797, 379, 37, 3) 모두 소수가 되는 성질이 있습니다.

이런 성질을 가진 소수는 단 11개만이 존재합니다. 이것을 모두 찾아서 합을 구하세요.

(참고: 2, 3, 5, 7은 제외합니다)

http://euler.synap.co.kr/prob_detail.php?id=37

조건에 맞는 소수들을 세면서 11개가 될때까지 검사를 수행해야 한다. 왼쪽에서 한자리씩 없애거나 오른쪽에서 한자리씩 없애는 것은 간단한 편인데 (상용로그를 이용하면 쉽다) 시간이 적지 않게 소모된다. 성능을 최적화하는 방법 몇 가지를 살펴보자.

  1. 숫자를 하나씩 제거해나가면 원래 값보다 계속 작아진다. 따라서 소수 판별함수는 메모이제이션한다.
  2. 문제의 조건에 의해 1의 자리는 3, 7 중 하나의 숫자만 올 수 있다. 이걸 미리 검사하면 수행시간이 절반으로 줄어든다.
  3. 마찬가지로 첫자리에는 2가 올 수 있지만, 그외 나머지 자리에는 짝수 숫자가 올 수 없다. 이 부분이 엄청나게 많은 경우를 제거한다.

2, 3의 제약 조건이 없을 때 약 6~7초 가량 걸리던 것이 저 제약 조건만으로 500ms대로 단축됐다.

from math import log10

def rFront(n):
    a = [n]
    while n > 0:
        l = 10**int(log10(n))
        n = n % l
        a.append(n)
    return a[1:-1]

def rRear(n):
    a = [n]
    while n > 0:
        n = n // 10
        a.append(n)
    return a[1:-1]

def memoize(f):
    cache = {}
    def wrapper(a):
        if a in cache:
            return cache[a]
        r = f(a)
        cache[a] = r
        return r
    return wrapper

@memoize
def is_prime(n):
    if n < 2:
        return False
    if n is 2 or n is 3:
        return True
    if n % 2 == 0 or n % 3 == 0:
        return False
    if n < 9:
        return True
    k = 5
    l = n ** 0.5
    while k <= l:
        if n % k == 0 or n % (k+2) == 0:
            return False
        k += 6
    return True


def check(n):

    if n % 10 not in (3, 7):
        return False

    s = str(n)[1:]
    for c in "02468":
        if c in s:
            return False

    if is_prime(n):
        for i in rFront(n):
            if not is_prime(i):
                return False
        for i in rRear(n):
            if not is_prime(i):
                return False
    else:
        return False
    return True

def e37():
    res = []
    a = 11
    c = 0
    while len(res) < 11:
        if check(a):
            res.append(a)
        a += [2, 4][c]
        c = (c + 1) % 2
    print(sum(res))

%time e37()
# 748317
# Wall time: 565 ms

jquery 기본

좀 뒤늦게 jquery 기본 사용법(?)에 대해서 정리. 처음에는 JS문법으로 예제를 썼지만, 뒤로 갈 수록 LS문법을 씁니다.(괄호라든지 괄호라든지 괄호때문에…)

jQuery Object

jQuery 객체는 DOM 요소(단일요소 혹은 집합)에 대해 $()를 이용하여 생성한 객체를 의미한다. 이는 내부적으로 DOM 요소를 참조하면서 jQuery에 의해 제공되는 공통 API를 적용받게 된다.

속성들

.attr() 메소드는 getter/setter로 동작하며, 해당 DOM요소의 속성을 얻거나 변경할 수 있다.

$('a').attr('href', 'allMyHrefsAreTheSameNow.html');
$('a').attr({
    title: 'all titles are the same too!',
    href: 'somethingnew.html'
});
$('a').attr('href'); //=> 첫 A 요소의 'href' 값을 리턴한다.

요소 선택

요소 선택의 방법

  1. id : $('#myID')
  2. class: $('.myClass') – 복수 요소가 집합으로 선택된다.
  3. attributes: $('input[name=first_name]') – 역시 복수요소 가능하다.
  4. 컴마로 구분한 요소: $('div.myClass, ul.people') – 복수 요소
  5. 가상 셀렉터: $('a.external:firset'), $('tr:odd'), $('#myForm :input')

필터링

요소 선택 후 필터링

$('div.foo').has('p') // <p> 태그를 포함하고 있는 div.foo 요소들
$('h1').not('.bar') // bar 클래스가 아닌 h1
$('ul li').filter('.current') // .current 클래스가 적용된 li
$('ul li').first() // 첫번째 ul li 요소
$('ul li').eq(5) // 6번째 요소 [5]로 subscription 하면 언래핑됨

몇 가지 폼 요소에 대한 유사 셀렉터로는 다음의 것이 있다.

  1. checked
  2. :disabled
  3. :enabled
  4. :input
  5. :selected
  6. :password
  7. :reset
  8. :radio
  9. :text
  10. :submit
  11. :button
  12. :image
  13. :file

셀렉션 다루기

id등의 유니크 식별자를 이용하지 않는 이상, jQuery 선택은 복수 요소에 대한 집합을 생성한다. 이 때 여러 속성 setter는 집합 내 모든 요소에 공통으로 적용된다. getter를 호출하면 그 중 맨 처음 요소의 값을 갖고오게 된다.

$('h1').html('hello world!');
// 모든 h1 요소의 내부 html 값이 'hello world!'가 된다.
$('h1').html(); // 'hello world!'

체이닝

$('#content').find('h3').eq(2).html('new text for the third h3!')

// 나눠쓰기
$('#content')
  .find('h3')
  .eq(2)
  .html('new text for the third h3!');

.end()를 쓰면 현재 체이닝을 완료하고 상위 체인으로 돌아갈 수 있다.

$('#content')
  .find('h3')
  .eq(2)
    .html('new text for the third h3!')
    .end()
  .eq(0)
    .html('new text for the first h3!')

DOM 요소 조작하기

특정 DOM 요소들을 캐치했다면 다음과 같은 조작 메소드를 이용할 수 있다.

  1. .html()
  2. .text()
  3. .attr()
  4. .width()
  5. .height()
  6. .position()
  7. .val()

appendTo, insertAfter, after 등을 이용해서 위치를 옮기거나 추가할 수 있다.

var li = $('#myList li:first').appendTo('#myList')
// 첫 원소를 맨끝으로 이동
// 혹은
$('#myList').append($('#myList li:first'))

이동 말고 복사는 clone()을 이용해서 복사하여 추가하면 된다.

$('#myList li:first').clone().appendTo('#myList')

제거

.remove(), .detach()가 있다. .remove()는 해당 객체를 완전히 제거하는 것이고 .detach()는 문서에서 떼어내지만 해당 객체의 속성이나 데이터를 유지한다.

새 요소 만들기

$()의 인자로 태그를 포함한 HTML 문자열을 전달하면 해당 요소를 생성한다.

$('<p>this is a new paragraph</p>');
$('<li class="new">new list item</li>');


var myNewElement = $('<p>new element</p>')
myNewElement.appendTo('#content') // 추가 
myNewElement.insertAfter('ul:last') // 밴 후에 ul 끝으로 이동
$('ul').last().after(myNewElement.clone()) // 복사해서 하나 더 추가

다음은 새 리스트를 파퓰레이팅하는 법이다.

var lists = new Array();
for(var i=0; i < 100; i++) {
    lists.push($("<li>item " + i + "</li>"));
}
for(var i=0; i< 100; i++){
    $('ul').append(lists[i]);
}

객체 대신에 한꺼번에 하위 HTML을 모두 넣어도 된다. 따라서

[0 til 100].map -> "<li>item #it</li>"
.join '' |> $ \ul .append

이렇게 쓸 수 있다.

jQuery 객체와 DOM 요소

target = document.get-element-by-id \target
target.innerHTML = "<td>hello <b>World</b>!</td>"


$ target .html "<td>Hello <b>World</b>!</td>"

jQuery 객체와 DOM요소는 별개의 것이다. jQuery 객체는 DOM요소 객체를 감싼 computational context라고 보면 된다.

또한 래핑할 때마다 새로운 객체가 생성되기 때문에, 두 개의 jQuery 객체가 같은 DOM에 대해서 여러번 생성됐을 때 이들은 같은 게 아니다.

var logo1 = $('#logo');
var logo2 = $('#logo');
logo1 === logo2; // false

요소 순회

하위 요소 찾기

.children()은 선택된 요소의 하위 요소 중에서 특정한 조건을 만족하는 하위요소들을 리턴한다. .find()는 자손 요소 계층을 모두 탐색한다.

부모 요소 찾기

.parent()는 직계 상위 부모 요소를 리턴한다. .parents()는 지정한 요소까지의 부모 계층에 속한 요소들을 리턴한다. parentsUntil()은 주어진 요소까지의 계층을 탐색하는데, 최종 계층인 주어진 요소는 포함하지 않는다. .closest()는 부모 계층 중에서 가장 가까운 노드를 찾는다.

이웃/형제 요소

.next(), .nextAll(), prev(), prevAll(), nextUntil(), prevUntil() 등으로 찾을 수 있다.

데이터

.data는 특정 DOM 요소에 데이터를 바인딩해둘 수 있다. 별도의 스크립트 코드에 데이터를 바인딩하는 것은 메모리누수의 원인이 되리 수 있는데, 이 방법을 이용하면 해당 DOM이 제거될 때 메모리도 반환된다.

유틸리티 함수

$.trim()

string.trim()과 동일하다.

$.inArray()

Array.index()와 동일하다.

$.extend()

두 객체를 더하여 새로운 객체를 만든다.

$.proxy()

특정 함수와 객체를 바인드한다. function.bind()와 같다고 보면 된다.

타입테스팅

$.isArray, $.inFunction, $.isNumeric 함수는 주어진 객체의 타입을 확인한다.

$.each()

주어진 배열 혹은 객체의 요소들을 순회한다.

sum = 0
arr = [1 to 10]
$.each arr, (i, e) !-> sum += e
console.log sum #=> 55

sum = 0
obj = a:1, b:2, c:3
$.each obj, (k, v) !-> sum += v
console.log sum #=> 6

jQuery 셀렉션을 순회하는 경우는 다음과 같이 생각할 수 있는데,

$.each ($ \p), (i, e) -> $ e .addClass \special

이 경우에는 $().each()를 쓸 수 있다.

$ \p .each (i, e) -> $ e .addClass \special

아는 줄여서

$ \p .addClass \special

위와 같이 쓸 수 있다. 하지만, jQuery 컬렉션은 getter를 호출하는 경우에는 첫 원소의 값만을 가져오므로 다음과 같이 써야 하는 경우도 있다.

$ \p .each (i, e) -> console.log $ e .text!

$.map()

$.filter는 지원하지 않는듯?

일반 배열 객체에 대해서 함수형 스타일의 map을 지원하는 함수이다.

a = [1 to 10]
$.map a, (i, e) -> e * 2 + 1
|> console.log
## [3, 5, 7 ... 21]

역시 jQuery 셀렉션은 .map()을 쓴다.