자바스크립트 소팅, 멀티 소팅

자바스크립트 소팅, 멀티소팅에 관하여





웹포넌트 그리드에는 기본기능으로 소팅, 멀티소팅이 있습니다. 기능을 구현하면서 많은 시행착오가 있었습니다. 이 글에서는 당시에 얻은 "자바스크립트를 이용한 클라이언트사이드 소팅"관한 지식을 공유하려고 합니다.


자바스크립트의 Array.prototype.sort()메서드에 대해서 알아보고 메서드의 응용, 멀티소팅은 어떤식으로 구현했는지 소개하려고 합니다.


sort()메서드의 동작방식


sort()메서드를 아무인자도 주지 않고 실행시키면 사전식으로 정렬이 됩니다.


 

1
2
3
var letters = ["R","O","F","L"];    
letters.sort();    
alert(letters);    //produces ["F","L","O","R"]
cs


하지만 실제세계에서는 날짜비교, 통화비교, 제곱갑 비교 등등 사전식 정렬만으로는 해결되지 않는 문제가 많이 있습니다. 그래서 sort()메서드는 인자로 비교함수(comparison function)를 받을수 있게 설계되어 있습니다. 이 함수는 두개의 인자를 받습니다. 이 두 인자는 배열안의 두 값을 가정합니다. 두 인자를 순서대로 a, b라고 했을때 비교함수는 다음과 같은 규칙을 따르게 됩니다.


- 만약에 함수가 0보다 작은값을 반환하면 a를 b보다 먼저 배치합니다.

- 만약에 함수가 0보다 큰 값을 반환하면 b를 a보다 앞에 배치 합니다.

- 만약에 함수가 0을 반환하면 a와 b의 순서는 바뀌지 않습니다.


아주 간단한 숫자를 정렬하는 예제로 그 성질을 확인해 봅시다. 만약에 비교함수가 (a-b)를 반환하게 되면 배열은 숫자를 작은 순서부터 배치하게 됩니다. 역으로 (b-a)를  반환하면 큰 숫자부터 반환합니다.

 

1
2
3
4
5
6
7
8
var numbers = [8,5];
    
numbers.sort(function(a, b)
{
    return a - b;    
});
    
alert(numbers);    //produces [5,8]
cs

 

 

1
2
3
4
5
6
7
8
9
var numbers = [4,3,5,9];
    
numbers.sort(function(a, b)
{
    return b - a;    
});
    
alert(numbers);    //produces [9,5,4,3]
 
cs

 


간단하게 문자를 소팅하는 비교함수는 다음과 같이 됩니다. 아래 예제에서는 대소문자를 구분하기위해 toLowerCase()메서드를 이용했습니다.


 

1
2
3
4
5
6
7
8
var letters = ["R","O","F","L"];
    
letters.sort(function(a, b)
{
    var x = a.toLowerCase(), y = b.toLowerCase();
    
    return x < y ? -1 : x > y ? 1 : 0;
});
cs


sort()메서드 응용


배열의 내용이 Primitive Type이 아닌상황에서도 비교함수를 사용 할 수 있습니다.


다음은 배열안에 배열이 들어있을때 각 배열의 특정값을 이용해서 소팅을 하는 예제입니다. 처음예제는 숫자를 기준으로 소팅, 두번째 예제는 문자를 기준으로 소팅을 하게 됩니다.


 

1
2
3
4
5
6
7
8
9
10
11
var shapes = [
    [5"Pentagon"],
    [3"Triangle"],
    [8"Octagon"],
    [4"Rectangle"]
    ];
    
shapes.sort(function(a, b)
{
    return a[0- b[0];
});
cs

 

 

1
2
3
4
5
6
7
8
9
10
11
var shapes = [
    [5"Pentagon"],
    [3"Triangle"],
    [8"Octagon"],
    [4"Rectangle"]
    ];
    
shapes.sort(function(a, b)
{
    return a[1- b[1];
});
cs

 


조금더 복잡한 상황을 생각해봅시다. 위와 같은상황에 숫자를 기준으로 정렬하는데, 같은 숫자인경우는 문자를 기준으로 소팅을 해야하는 경우입니다. 이럴경우 비교함수의 리턴을 적절히 조절해서 해결할 수 있습니다.


 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var shapes = [
    [4"Trapezium"],
    [5"Pentagon"],
    [3"Triangle"],
    [4"Rectangle"],
    [4"Square"]
    ];
    
shapes.sort(function(a, b)
{
    if(a[0=== b[0])
    {
        var x = a[1].toLowerCase(), y = b[1].toLowerCase();
        
        return x < y ? -1 : x > y ? 1 : 0;
    }
    return a[0- b[0];
});
cs

 


이런 기본원리로 Array<Array>형태의 자료뿐만 아니라 Array<Map>등의 모든 배열을 원하는 방식대로 소팅을 할 수 있습니다. 


멀티소팅


윗장에서 본 마지막 예제는 기본적인 멀티소팅에 대한 예제입니다. 하지만 그리드나 다른 실제상황에서는 비교해야하는 대상의 갯수가 동적으로 변하거나 비교방식이 동적으로 변하기때문에 비교함수를 특정할 수 없습니다. 그래서 비교함수를 동적으로 만들어내는 함수가 필요합니다. Function을 인자로 넘기거나 리턴값으로 자유롭게 받을 수 있는 자바스크립트의 성질을 이용합니다. 이 함수는 제가만든건 아니고 Teun Duynstee라는 분이 만든걸 이용했습니다. 

https://github.com/Teun/thenBy.js/tree/master


 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/***
   Copyright 2013 Teun Duynstee
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
var firstBy = (function() {
    /* mixin for the `thenBy` property */
    function extend(f) {
        f.thenBy = tb;
        return f;
    }
    /* adds a secondary compare function to the target function (`this` context)
       which is applied in case the first one returns 0 (equal)
       returns a new compare function, which has a `thenBy` method as well */
    function tb(y) {
        var x = this;
        return extend(function(a, b) {
            return x(a,b) || y(a,b);
        });
    }
 
    return extend;
})();
 
cs

 


사용법은 다음과 같습니다.


 

1
2
3
4
5
6
// first by length of name, then by population, then by ID
data.sort(
    firstBy(function (v1, v2) { return v1.name.length - v2.name.length; })
        .thenBy(function (v1, v2) { return v1.population - v2.population; })
        .thenBy(function (v1, v2) { return v1.id - v2.id; })
);
cs

 


기본사용법은 위와 같지만 조금더 자동화 해보기로 합니다.


 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
 * 멀티소팅을 위한 Array.sort의 iterator를 만들어준다
 * @param  {Function} interator
 * @param  {Function} compareFunction
 * @return {Function}
 */
function getComparator (interator, compareFunction) {
 
  if (interator === null) {
 
    return firstBy(compareFunction);
  } else  {
 
    return interator.thenBy(compareFunction);
  }
}
cs

 


 

1
2
3
4
5
6
7
8
9
10
11
var compareIterator = null;
 
if (condition_1) {
    getComparator(compareIterator, condition_1_compare);
}
 
if (contition_2) {
    getComparator(compareIterator, condition_2_compare);
}
 
myArray.sort(compareIterator);
cs

 


getComparator()함수을 이용해서 원하는 상황에 비교함수를 계속 추가 할 수 있습니다. 단계적으로 추가되는 비교함수(condition_1_compare, condition_2_compare)는 독립적으로 움직이므로 유지보수에도 유리해집니다. 저와같은경우는 날짜비교, 통화비교, 문자열비교, 소수점비교 등의 비교함수를 독립적으로 만들어놓고 그리드에서 원하는 상황에 getComparator()를 이용해 데이터를 소팅했습니다.


마치며


이 글이 같은 문제에 난착해있는 분들에게 도움이 되었으면 합니다.  자신만의 비교함수를 만들어서 상황별로 멋지게 적용시키는 모습을 기대합니다. 좋은 비교함수를 만드시면 공유도 해주시면 감사하겠습니다.







 


New Multi-Channel Dynamic CMS