[일일코딩 #35] String incrementer

Question

스트링에서 넘버만 따다가 +1 하는것. 자릿수 지키기.

https://www.codewars.com/kata/string-incrementer/javascript

Your job is to write a function which increments a string, to create a new string.

If the string already ends with a number, the number should be incremented by 1.
If the string does not end with a number. the number 1 should be appended to the new string.
Examples:

foo -> foo1

foobar23 -> foobar24

foo0042 -> foo0043

foo9 -> foo10

foo099 -> foo100

Attention: If the number has leading zeros the amount of digits should be considered.

My answer

정규식으로 number만 따다가 +1 해주고 원래 자릿수를 구해서 0을 그만큼 채워줬다.
마음에 안 듦.

function incrementString (str) {
    const numRegex = /[0-9]+/.exec(str);
    if (numRegex) {
        const numStr = numRegex.pop();

        const newNum = Number(numStr) + 1;
        const zeroNums = numStr.length - newNum.toString().length;
        const zeroStr = (zeroNums > 0) ? Array(zeroNums).fill("0").join("") : "";

        return str.replace(numStr, `${zeroStr}${newNum}`)
    }
    return `${str}1`;
}

Others' answer

Azuaron, Nakid..

function incrementString (input) {
    // 맨끝이 not a number면 그냥 + "1" 
    if(isNaN(parseInt(input[input.length - 1]))) return input + '1';

    // "001"을 넘겼다면 match는 001, p1은 00, p2는 1
    return input.replace(/(0*)([0-9]+$)/, function(match, p1, p2) {
        var up = parseInt(p2) + 1;

        // +1한 숫자 자릿수가 원본자릿수보다 크다면 0을 하나 뺀만큼 앞에 붙여주고 아니면 원본 0갯수만큼 붙여주기
        return (up.toString().length > p2.length) ? p1.slice(0, -1) + up : p1 + up;
    });
}
  • String.replace 첫번째 인자에 정규식을 넘길수 있고, 두 번째 인자에 함수를 넘길 수 있구나!
  • 정규식에 ()으로 그룹을 지어서 두번째 이후 인자로 받을 수 있구나
  • String.slice(0, -1)은 마지막 한글자를 떼는거구나

[일일코딩 #33] Remove Duplicates from Sorted Array

일일코딩 #32가 2017년 6월이었고, 오늘의 일일코딩 #33은 2019년 11월이다. 2년이 넘었다.
2년만에 푼 알고리즘 문제는 아주 노답이었다고 할 수 있다.
Leetcode easy난이도 문제인데 한시간 걸렸거든.
와 정말 부끄럽다.

영어 설명을 제대로 읽지 않은 탓도 있다.
Array의 duplicated된 엘리먼트를 지우라기에 당연히 지워진 array를 반환하는줄 알았지.
그런데 반환값은 Array의 length뿐이었고 Params로 온 Array의 reference를 받아 원본 Array를 중복 없이 만드는거였다. 메모리 새로 안쓰고.

담엔 잘 읽어.

Question

Leetcode 링크
Given a sorted array nums, remove the duplicates in-place such that each element appear only once and return the new length.

Do not allocate extra space for another array, you must do this by modifying the input array in-place with O(1) extra memory.

즉 아래와 같다.

removeDuplicates([1,1,2]); // returns 2 (원본 Array는 [1, 2]로 modify됨)

removeDuplicates([0,0,1,1,1,2,2,3,3,4]); // returns 4 (원본 Array는 [0, 1, 2, 3]로 modify됨)

My answer

While문을 돌면서 해당 element가 Array의 맨 마지막에 있는지 확인했다.
맨 마지막이면 살려주고 아니면 splice로 제거.
정렬된 배열이라 다 뭉쳐있을테니 각 뭉치의 제일 오른쪽 애들만 남긴다는 아이디어였다.

var removeDuplicates = function(nums) {
    var count = nums.length - 1;
    while(count >= 0) {
        if(count == nums.lastIndexOf(nums[count])) {
            count--;
        } else {
            nums.splice(count, 1);
        }
    }

    return nums.length;
};

잘 되긴 한다.

성능은 후지다. 이 퍼센트가 뭔가 했더니 하위 5%, 18%더라.

Runtime: 224 ms, faster than 5.75% of JavaScript online submissions for Remove Duplicates from Sorted Array.
Memory Usage: 37.8 MB, less than 18.75% of JavaScript online submissions for Remove Duplicates from Sorted Array.

처음에 param으로 받은 Array를 reference로 고쳐야한단걸 몰랐을때는
Array.prototype.reduce로 접근했다. prev, next를 돌며 앞뒤가 다르면 새로운 Array에 push해줬음. 그건 그것나름대로 제일 앞 or 제일 뒤가 edge case라는 헛점은 있더라 – 무튼 안됨.

다른 사람들은 어케 했는지 보자

Others’ answer

Best practice

var removeDuplicates = function(nums) {
    if (nums.length == 0) return 0;
    var i = 0;
    for (var j = 1; j < nums.length; j++) {
        if (nums[j] != nums[i]) {
            i++;
            nums[i] = nums[j];
        }
    }
    return i + 1;
}

첨엔 이해가 안갔다
이건 [0,0,1,1,1,2,2,3,3,4][ 0, 1, 2, 3, 4, 2, 2, 3, 3, 4 ]로 만들텐데 그럼 뒤에 더러운 숫자들이 남지 않는가?

근데 다시 Question을 읽어보니 뒤에 더러운 숫자가 있던 없던 반환한 lengthNum 길이만큼만 일치하면 되는거더라구.
문제가 여러모로 깔끔하지 못하네…

Answer by @Qfab

var removeDuplicates = function(nums) {
    nums.splice(0, nums.length, ...(new Set(nums)));
};

변태코드

광광 울며 정리하는 Javascript의 this

전역에서 함수를 할당하면 window로 들어가는구나. 그래서 그 속에서 this를 찍어보면 Window객체가 나온다. new Foo()로 초기화하면 예상했던 대로 Foo { }가 나오고. 면접에서 털리고 광광 울며 정리중

 
js에서 모든 함수는 실행시마다 함수 내부에 this가 추가. arguments라는 유사 배열 객체와 함께 암묵적으로 전달. 그렇기 때문에 함수가 호출된 상황에 따라 그 모습을 달리 함.

  • this: 함수의 현재 실행 문맥. js에선 4가지의 함수 실행 타입이 있기 때문
      1. 함수 실행: alert(‘hi’)
      1. 메소드 실행: console.log(‘hello’)
      1. 생성자 실행: new RegExp(”)
      1. 간접 실행: alert.call(undefined, ‘hello’)
    • 각각의 타입은 서로 다른 문맥을 가진다. 더욱이 strict mode또한 실행 문맥에 영향을 미친다.
  • 즉시실행함수 역시 함수 실행의 종류 중 하나
var greeting = (function(name) {
    return 'Hi' + name;
})('Jay');
console.log(greeting)
  • 함수 실행에서의 this는 전역 객체다. 전역 객체는 실행 환경에 따라 결정. 웹 브라우저에선 window가 전역 객체.
function sum(a, b) {
    console.log(this === window); //true
    this.myNum = 20; // this에 추가했고, 전역인 window에 추가됨
    return a + b;
}

function strictSum(a, b) {
    'use strict';
    console.log(this === undefined); //true
    return a + b;
}
sum(1, 2); // 3. 함수 호출 -> 여기서 this는 전역 객체
window.myNum; // 20. this에 추가했는데 윈도우에 추가됨
  • 엄격 모드에서의 함수 실행 this
    • 위의 예제에서 만약 위에 ‘use strict’;를 적어줬다면, this는 undefined가 된다.
    • 엄격 모드는 현재 스코프 뿐만 아니라 내부 스코프에도 적용됨
var numbers = {
   numberA: 5,
   numberB: 10,
   sum: function() {
     console.log(this === numbers); // => true
     function calculate() {
       // this는 window, 엄격 모드였으면 undefined
       console.log(this === numbers); // => false
       return this.numberA + this.numberB; // numbers에 접근 불가
     }
     return calculate();
   }
};
// 이는 객체 내에 있는 메소드를 실행하는 것. 이 실행시의 this는 window
numbers.sum(); // NaN, 엄격 모드였으면 TypeError

refer: http://webframeworks.kr/tutorials/translate/explanation-of-this-in-javascript-1/

[일일코딩 #32] Two Sum

[일일코딩 #32] Two Sum

Question

링크
Given an array of integers, return indices of the two numbers such that they add up to a specific target.

You may assume that each input would have exactly one solution, and you may not use the same element twice.

Example:
Given nums = [2, 7, 11, 15], target = 9,

Because nums[0] + nums[1] = 2 + 7 = 9,
return [0, 1].

My Answer

var twoSum = function(nums, target) {
  for (var i = 0; i &lt;nums.length; i++) {
    var lastIdx = nums.lastIndexOf(target - nums[i]);
    if(lastIdx &gt; 0) return new Array(i, lastIdx);
  }
};

lastIndexOf로 풀었다. 처음엔 Array 초기화 할 때 var answerArr = []로 해서 answerArr.push(a, b)로 넣었는데 코드 줄인다고 위처럼 바꿨더니 성능이 조금 더 줄었다. 변수 할당보다 new Array 로 하는게 더 오래걸리나보다.

Other’s answer

3가지 방법을 제시함.
1. 브루트 포스: 포문 2번 돌면서 뺀 값이 있나 찾기
2. Two pass hash table: 잘 이해 안감 왜 굳이?
3. One-pass hash table: 내가 푼 방법. 뺀 값이 map에 있나 확인

body overflow:hidden 이 모바일에서 안 먹을때 javascript로 해결방법

스크린샷 2017-05-09 오후 9.21.17

상황: 모바일에서 사이드 네비게이션이 열린 상태에서 스크롤시 body는 스크롤 되지 않게 하고싶다

Solution A: CSS

html,
body {
    overflow: hidden;
    position: relative;
    height: 100%;
}

PC에선 문제 없이 작동한다. 하지만 iOS 사파리랑 크롬에서는 무용지물.

Solution B: CSS – fix

body {
    position: fixed;
    width: 100%;
    height: 100%;
}

PC, 모바일 모두 되긴 하는데 Side nav를 닫은 후에는 position이 꼬임 -> DOM 클릭해도 이벤트가 활성화 안된다.

Solution C: jQuery

<body>
<div id="side-nav">사이드 네비게이션</div>
<div class="contents">여기에 body의 내용 넣는다</div>
</body>
$(function(){
    $('#side-nav').on('show.bs.offcanvas', function (e) {
      $('#sidenav-overlay').addClass('active');

      /* Disable scroll */
      $('.contents').on('scroll touchmove mousewheel', function(e){
        e.preventDefault();
        e.stopPropagation();
        return false;
      });
    });
    $('#sidenav-overlay').click(function() {
      $('#side-nav').offcanvas('hide');
      $('#sidenav-overlay').removeClass('active');

      /* Enable scroll */
      $('.contents').off('scroll touchmove mousewheel');
    });
  });

jQuery on handler로 스크롤을 막는다.
HTML에서 사이드 네비게이션과 Contents 영역을 분간해두면
사이드 네비게이션 자체에서는 스크롤 가능하게 할 수 있다.

Refer

http://stackoverflow.com/questions/3656592/how-to-programmatically-disable-page-scrolling-with-jquery

[Javascript]원하는 HTML 영역 프린트하기

스크린샷 2017-03-28 오전 10.48.19스크린샷 2017-03-28 오전 10.49.44

상황: 페이지의 일부만 프린트하고 싶다!

구글링 하면 2가지 방법이 나온다.
근데 둘다 내 상황에는 문제점이 있었다.

  1. 팝업으로 DOM을 복사해 프린트: 편하긴 한데 css, js가 떨어짐
  2. 전체 돔, 프린트 영역 원하는 돔 따로 저장한다음에 프린트영역만 살리고 프린트 후 전체 돔 살림: 편하긴 한데 js 이벤트 바인딩이 떨어짐

아래와 같이 display noneblock 을 toggle 해주는걸로 간단히 해결 가능하다.
React 사용중이라 jquery 말고 pure javascript로 코딩했다.

onPrint() {
const html = document.querySelector('html');
const printContents = document.querySelector('.modal-body').innerHTML;
const printDiv = document.createElement(&quot;DIV&quot;);
printDiv.className = &quot;print-div&quot;;

html.appendChild(printDiv);
printDiv.innerHTML = printContents;
document.body.style.display = 'none';
window.print();
document.body.style.display = 'block';
printDiv.style.display = 'none';
}

[javascript] 함수 할당시 실행 없이 인자 넘기기

Problem

var apiCRUD  = {
  downloadExcel: function($http) {
    //랄랄랄
  }
}

rc.downloadExcel = apiCRUD.downloadExcel($http);

apiCRUD.downloadExcel함수를 저렇게 rc.downloadExcel에 할당하면 바로 실행이 되는데,
나는 rc.downloadExcel이 호출되었을 때 apiCRUD의 함수가 실행되길 바란다.

그렇다고

rc.downloadExcel = apiCRUD.downloadExcel;

처럼 하면 $http인자를 못 넘긴다.

Solution

rc.downloadExcel = function() {
    apiCRUD.downloadExcel($http);
}

요러면 됨.
호이스팅 없이 함수 실행 시에 불리움.

Refer

j2p님의 도움 감사합니다.

momentJS를 angularJS 필터에서 사용하기

Problem

스크린샷 2016-01-25 오후 6.55.20

angularJS로 ERP를 만드는 중이다.
ng-repeat로 반복되는 칼럼에 타임스탬프가 안 예쁘게 찍혀서 moment.js로 가독성을 높이려 한다.

javascript에서 가로채서 솰라솰라 하는거 말고 angular로 예쁘게 하는 방법은 없을까?

 

Solution

angularjs filter를 사용한다.

sandiApp.filter('moment', function() {
    return function(dateString, format) {
        return dateString? moment(dateString).format(format): null;
    };
});

이 jsfiddle을 참고하고, 값을 입력 안하면 null이 반환되게 살짝 추가해두었다.

<td>{{ invoice.ordered_time | moment:'YYYY년 MMMM Do dddd a h:mm' }}</td>

원하는 출력 형태를 써주면 filter에서 moment(원래적히던값).format(적어준포맷)으로 모멘트를 통과해서 뿌려준다.

Outcome

스크린샷 2016-01-25 오후 7.03.20

예뻐짐.

django, bower, grunt로 사이트 구조잡기

상황

재고/창고관리 웹페이지를 만들려고 한다.
전체적으로 djangodocker로 돌아가고, 화면은 6개 정도 되는 크지 않은 admin페이지다.
나는 프론트엔드 담당이다.

생각하기

앞단을 짜는 것엔 두 가지 방법이 있다.
1. django템플릿을 써서 서버사이드 렌더링을 한다.
2. api를 받아와서 ajax로 만든다.

관리자 페이지니 봇이 긁어가야 할 필요도 없고, 이미 api들이 만들어지고 있는 상태라 2번을 사용하기로 했다. 기간이 2주뿐이라 조금이나마 익숙한 ang`ular를 쓰기로 했다(리액트 써보고 싶다 힝).

구조 잡기

현재 디렉토리 구조는

- api
    + migrations
    + serializers
    + test
    + views
    + (등등 django REST Framework기반의 api서버이다.)
- conf
    + development
    + production
    + testing
- etc
    + (docker들어가있음)
- stock
    + (django 메인 앱)
    + __init__.py
    + settings.py
    + urls.py
    + wsgi.py
- web
    + (여기다 웹 프론트를 짜면 된다)
- manage.py 등 장고 관련 파일들

로 되어있다. 나는 web에다 프론트를 짜면 된다.
angularjsone page web을 만들 예정이라 루트(/)로 접속했을 때 web/base.html로 연결되도록 stock/urls.py에 명시해주어야 했다.

static file 사용하기

urls.py

from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    url(r'^$',
        TemplateView.as_view(template_name='base.html'),
        name='main'),
    url(r'^api/', include('api.urls')),
    url(r'^admin/', admin.site.urls),
    url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]+ static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

base.html을 템플릿뷰로 연결해주었다. 하지만 그냥 url만 적어주면 web폴더 내의 base.html은 템플릿파일로 인식을 못하기에 뒤에 static을 연결해준다.

settings.py

TEMPLATES = [
    {
        ...
        'DIRS': [
            'web'
        ],
        ...
    },
]

...

STATICFILES_DIRS = [
    ("web", os.path.join(BASE_DIR, "web")),
]

settings.py TEMPLATES내부의 DIRS에 폴더명을 적어준다.
그리고 밑의 STATICFILES_DIRSweb이란 이름으로 BASE_DIRweb폴더를 연결해둔 패스를 지정해준다. 그 후로부턴 저 BASE_DIR/web폴더 내부 staticfile을 사용할 때 앞에 ‘web’이란 이름을 적어주면 된다.

web/base.html

{% load staticfiles %}

base.html위에 staticfiles를 로드해주고

<link rel="stylesheet" href="{% static 'web/dist/css/base.css' %}">

불러올 땐, 폴더를 아까 정해준 이름(web)으로 호출하면 된다.

의존성 관리 – Bower

의존성 관리 툴은 bower와 npm을 사용하였다.
최상위 폴더에 bower 설정파일을 만들어준다.

bower.json

{
"name": "sandi",
"dependencies": {
"admin-lte": "latest",
"fastclick": "latest"
}
}

이름과 dependencies만 적어주었다.
admin-lte라는 admin사이트 만드는 곳에 특화된 부트스트랩 템플릿과, 모바일에서 touch evnet를 도와주는 fastclick을 설치하였다.
콘솔에서 bower install하면 이들이 bower_componenets폴더 내에 설치된다.
이도 staticfile로 접근해야하니, settings.py에 한번 더 명시해준다.

settings.py

STATICFILES_DIRS = [
    ("components", os.path.join(BASE_DIR, "bower_components")),
    ("web", os.path.join(BASE_DIR, "web")),
]

components란 이름으로 연결해주었다.

<link rel="stylesheet" href="{% static 'components/admin-lte/bootstrap/css/bootstrap.min.css' %}">

이는 아까와 같이 사용할 수 있다(web대신 componenets라고 명시)

프론트엔드 태스크 자동화 – grunt

태스크 자동화는 grunt로 하였다. 저번 프로젝트는 gulp로 했었는데, admin-lte가 준 grunt파일이 좋아보여 얘로 결정!

web/package.json

{
"name": "sandi",
"version": "0.1.0",
"repository": {
"type": "git",
"url": `https://github.com/haha`
},
"devDependencies": {
"R2": "^1.4.3",
"grunt": "~0.4.5",
"grunt-contrib-clean": "^0.6.0",
"grunt-contrib-csslint": "^0.5.0",
"grunt-contrib-cssmin": "^0.12.2",
"grunt-contrib-jshint": "^0.11.2",
"grunt-contrib-less": "^0.12.0",
"grunt-contrib-uglify": "^0.7.0",
"grunt-contrib-watch": "~0.6.1",
"grunt-cssjanus": "^0.2.4",
"grunt-image": "^1.0.5",
}
}

npm 설정파일을 만들어 필요한 grunt파일들을 넣어준다.

web/Gruntfile.js

module.exports = function (grunt) {

  'use strict';

  grunt.initConfig({
    watch: {
      files: ["static/less/*.less", "build/less/skins/*.less", "static/js/app.js"],
      tasks: ["less", "uglify"]
    },
    /* LESS Compile */
    less: {
      development: {
        options: {
          compress: false
        },
        files: {
          "dist/css/base.css": "static/less/base.less",
        }
      },
      production: {
        options: {
          compress: true
        },
        files: {
          "dist/css/base.css": "static/less/base.less",
        }
      }
    },
    /* Javascript Uglify */
    uglify: {
      options: {
        mangle: true,
        preserveComments: 'some'
      },
      my_target: {
        files: {
          'dist/js/app.js': ['static/js/app.js']
        }
      }
    },
    /* Image Compression */
    image: {
      dynamic: {
        files: [{
          expand: true,
          cwd: 'static/img/',
          src: ['**/*.{png,jpg,gif,svg,jpeg}'],
          dest: 'dist/img/'
        }]
      }
    },

    // Validate JS code
    jshint: {
      options: {
        jshintrc: '.jshintrc'
      },
      core: {
        src: 'static/js/app.js'
      }
    },

    csslint: {
      options: {
        csslintrc: 'static/less/.csslintrc'
      },
      dist: [
        'dist/css/base.css',
      ]
    },

    /* Compression 전 이미지 삭제 */
    clean: {
      build: ["static/img/*"]
    }
  });

  grunt.loadNpmTasks('grunt-contrib-less');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-image');
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-clean');
  grunt.loadNpmTasks('grunt-contrib-csslint');

  grunt.registerTask('default', ['watch']);
};

web/static폴더에 less, js, img를 넣고,
grunt를 돌리면 web/dist폴더 minify되고 컴파일되고 compression된 css, js, img가 들어가도록 해두었다.(+ validation)

gitignore

dist

### Frontend ###
node_modules
bower_components

distribution에서 사용하는 dist폴더와,
npm으로 설치한 node_modulesbower로 설치한 bower_components는 git에서 제외시켜뒀다.
이로서 깨끗한 깃헙이 되었다!!! >.<

돌리자!

다른 개발자들을 위해 README.md에 써준다.

bower install
cd web
npm install
grunt