개발/Frontend

JavaScript로 윈도우 탐색기처럼 정렬하기

HuiYa 2019. 10. 16. 12:24

JavaScript에는 많은 정렬 함수가 있지만, 우리가 흔히 보는 파일 정렬 순서와는 결과가 좀 다르다.

 

Electron으로 파일 관련 프로젝트를 개발하다 파일 정렬 때문에 골머리를 앓았다.

 

앞서 이야기한 것처럼JavaScript에는 많은 정렬 함수가 있지만 윈도우 탐색기에서 보는 정렬 순서와는 결과가 좀 다르다.

JavaScript로는 이런 결과가 흔히 나타난다.

var arr = ["filename 0.txt","filename 1.txt","filename 9.txt","filename 10.txt","filename 11.txt"];
arr.sort();
console.log(arr);

// 0: "filename 0.txt"
// 1: "filename 1.txt"
// 2: "filename 10.txt"
// 3: "filename 11.txt"
// 4: "filename 9.txt"

9가 10보다 뒤로 간다!

 

이걸 볼 때마다 뒷목을 잡으면서 쓰러지고 싶어진다.

 

JavaScript의 Array.sort 함수는 매개변수로 정렬 순서에 사용할 함수를 넘길 수 있다.

그렇기 때문에 https://tonks.tistory.com/124 같은 많은 정렬 순서 함수들이 넘쳐난다.

 

하지만 결과는 위 테스트 결과와 크게 다르지 않다.

 

왜냐하면 Windows 탐색기는 자연 정렬 순서라고 하는 별도의 정렬 방식이 있기 때문에 단순한 정렬 함수로는 차이가 나올 수 밖에 없다.

 

Windows 탐색기는 API 함수 StrCmpLogicalW()기능을 사용하여 정렬을 수행한다고 한다. (참고링크 1, 2)

WindowsExplorerSort 알고리즘은 크게 아래와 같은 규칙을 따른다고 한다.

 

  • 파일 이름은 부분적으로 비교된다. (숫자와 공백, 나머지가 따로따로 비교된다)
  • 파일 이름의 숫자는 비교 가능한 숫자로 간주한다. (string이 아닌 int로 비교한다.)
  • 숫자는 숫자형으로 비교되지만 같으면 더 긴 문자열이 우선된다. (filename00.txt, filename0.txt 순서임)
  • 만약 숫자와 숫자가 아닌 부분을 비교하게 되면 둘다 텍스트로 간주한다.
  • 텍스트는 대소문자를 구분하지 않고 비교된다.

이러한 규칙을 조금이라도 따라하기 위해선 localeCompare를 사용하면 된다. (참고링크 3)

 

단, 많은 수의 문자열을 정렬 할 때 성능을 높이기 위해서는 Intl.Collator객체 를 만들고 해당 compare속성에서 제공하는 함수 를 사용하는 것이 좋다고 한다. (참고링크 4)

var collator = new Intl.Collator('en', {numeric: true, sensitivity: 'base'});
var arr = ["filename 0.txt","filename 1.txt","filename 9.txt","filename 10.txt","filename 11.txt"];

console.log(arr.sort(collator.compare));

// 0: "filename 0.txt"
// 1: "filename 1.txt"
// 2: "filename 9.txt"
// 3: "filename 10.txt"
// 4: "filename 11.txt"

드디어!

좀 봐줄만하게 나왔다.

 

다만 WindowsExplorerSort 알고리즘 중 3번째는 적용이 되지 않는다.

현재 코드로는 짧은 문자열이 우선되고 있다.

var collator = new Intl.Collator('en', {numeric: true, sensitivity: 'base'});
var arr = ["filename 0.txt","filename 1.txt","filename 9.txt","filename 10.txt","filename 11.txt","filename00.txt","filename 00.txt","FILENAME 2.txt","새 텍스트.txt"];

console.log(arr.sort(collator.compare));

// 0: "filename 0.txt"
// 1: "filename 00.txt"
// 2: "filename 1.txt"
// 3: "FILENAME 2.txt"
// 4: "filename 9.txt"
// 5: "filename 10.txt"
// 6: "filename 11.txt"
// 7: "filename00.txt"
// 8: "새 텍스트.txt"

이건 좀 아쉬운 부분이다.

그래도 이것만 빼면 윈도우 탐색기와 제일 유사하게 나온다.

 

실제 WindowsExplorerSort는 아래와 같다

 

영어가 우선이고 한글이 그 다음에 오는데, 한글을 위로 올리고 싶으면 아래 코드를 사용하면 된다.

var collator = new Intl.Collator('kr', {numeric: true, sensitivity: 'base'}); // kr 명시
var arr = ["filename 0.txt","filename 1.txt","filename 9.txt","filename 10.txt","filename 11.txt","filename00.txt","filename 00.txt","FILENAME 2.txt","새 텍스트.txt"];

console.log(arr.sort(collator.compare));

// 0: "새 텍스트.txt"
// 1: "filename 0.txt"
// 2: "filename 00.txt"
// 3: "filename 1.txt"
// 4: "FILENAME 2.txt"
// 5: "filename 9.txt"
// 6: "filename 10.txt"
// 7: "filename 11.txt"
// 8: "filename00.txt"

 

 

여기서 실수할 수 있는데,

테스트 정렬이 잘 된다고 이 코드를 그냥 붙여넣으면 안 된다.

 

테스트와 동일하게 배열이 key 없이 value만 있으면 이 코드를 그대로 사용해도 무방하지만

실제 파일 리스트를 담고 있는 File 객체는 배열이 아닌 객체다!

 

따라서 File 객체에 담긴 실제 파일명을 비교해줘야 한다. (File.name)

혹은 파일의 전체 경로인 File.path도 괜찮다.

 

Electron에선 File 객체에 path 속성도 제공해주고 있어 난 path를 사용했다.

 

File.name 혹은 File.path를 정렬하는 코드는 아래 코드를 사용하면 된다.

// var files = 실제 파일 배열

var collator = new Intl.Collator('en', {numeric: true, sensitivity: 'base'});
files = files.sort((a, b) => collator.compare(a.name, b.name)) // 혹은 a.path, b.path

 

 

참고 링크.

1. https://stackoverflow.com/questions/23205020/java-sort-strings-like-windows-explorer

2. https://docs.microsoft.com/en-us/windows/win32/api/shlwapi/nf-shlwapi-strcmplogicalw

3. https://stackoverflow.com/questions/2802341/javascript-natural-sort-of-alphanumerical-strings

4. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare

5. https://codepen.io/TimPietrusky/pen/rKzoGN