【JS】【TS】配列・リスト操作 ~ ソート編 ~

■ はじめに

https://dk521123.hatenablog.com/entry/2021/02/10/225119

の続き。

ソートに関して、記事が長くなったので、分冊。
結構、落とし穴もあるので、その辺も追記。

目次

例1:クラスのソート
例2:同一の値が存在する場合の対応
■ 使用上の注意:大文字・小文字に関して
 対応策1)toLowerCase()で小文字(もしくは大文字)に統一してからソート
 対応策2)localeCompare() を使ってソート

例1:クラスのソート

function sortList(
  targetList: any[],
  sortType: string,
  sortItem: string
) {
  targetList.sort((source, target) => {
    if (sortType === 'ASC') {
      // 昇順
      if (source[sortItem] < target[sortItem]) {
        return -1;
      } if (source[sortItem] > target[sortItem]) {
        return 1;
      }
    } else {
      // 降順
      if (source[sortItem] > target[sortItem]) {
        return -1;
      } if (source[sortItem] < target[sortItem]) {
        return 1;
      }
    }
    return 0;
  });
}

const people = [
  {id: 1, name: 'Mike', birthDate: '1922-02-20'},
  {id: 5, name: 'Sam', birthDate: '2011-12-09'},
  {id: 2, name: 'Kevin', birthDate: '1983-07-10'},
  {id: 3, name: 'Tom', birthDate: '1978-01-04'},
  {id: 4, name: 'Alex', birthDate: '2012-11-23'},
];

console.log('* id ******************');
sortList(people, 'ASC', 'id');
console.log(people);
console.log('*******************');
sortList(people, 'DESC', 'id');
console.log(people);
console.log('* name ******************');
sortList(people, 'ASC', 'name');
console.log(people);
console.log('*******************');
sortList(people, 'DESC', 'name');
console.log(people);
console.log('* birthDate ******************');
sortList(people, 'ASC', 'birthDate');
console.log(people);
console.log('*******************');
sortList(people, 'DESC', 'birthDate');
console.log(people);
console.log('*******************');

console.log('Done');

出力結果

* id ******************
[
  { id: 1, name: 'Mike', birthDate: '1922-02-20' }, 
  { id: 2, name: 'Kevin', birthDate: '1983-07-10' },
  { id: 3, name: 'Tom', birthDate: '1978-01-04' },  
  { id: 4, name: 'Alex', birthDate: '2012-11-23' }, 
  { id: 5, name: 'Sam', birthDate: '2011-12-09' }   
]
*******************
[
  { id: 5, name: 'Sam', birthDate: '2011-12-09' },  
  { id: 4, name: 'Alex', birthDate: '2012-11-23' },
  { id: 3, name: 'Tom', birthDate: '1978-01-04' },
  { id: 2, name: 'Kevin', birthDate: '1983-07-10' },
  { id: 1, name: 'Mike', birthDate: '1922-02-20' }
]
* name ******************
[
  { id: 4, name: 'Alex', birthDate: '2012-11-23' },
  { id: 2, name: 'Kevin', birthDate: '1983-07-10' },
  { id: 1, name: 'Mike', birthDate: '1922-02-20' },
  { id: 5, name: 'Sam', birthDate: '2011-12-09' },
  { id: 3, name: 'Tom', birthDate: '1978-01-04' }
]
*******************
[
  { id: 3, name: 'Tom', birthDate: '1978-01-04' },
  { id: 5, name: 'Sam', birthDate: '2011-12-09' },
  { id: 1, name: 'Mike', birthDate: '1922-02-20' },
  { id: 2, name: 'Kevin', birthDate: '1983-07-10' },
  { id: 4, name: 'Alex', birthDate: '2012-11-23' }
]
* birthDate ******************
[
  { id: 1, name: 'Mike', birthDate: '1922-02-20' },
  { id: 3, name: 'Tom', birthDate: '1978-01-04' },
  { id: 2, name: 'Kevin', birthDate: '1983-07-10' },
  { id: 5, name: 'Sam', birthDate: '2011-12-09' },
  { id: 4, name: 'Alex', birthDate: '2012-11-23' }
]
*******************
[
  { id: 4, name: 'Alex', birthDate: '2012-11-23' },
  { id: 5, name: 'Sam', birthDate: '2011-12-09' },
  { id: 2, name: 'Kevin', birthDate: '1983-07-10' },
  { id: 3, name: 'Tom', birthDate: '1978-01-04' },
  { id: 1, name: 'Mike', birthDate: '1922-02-20' }
]
*******************
Done

例2:同一の値が存在する場合の対応

function compare(sortKey: string, sortType: string, source: any, target: any) {
  // console.debug(`sortKey = ${sortKey}, sortType = ${sortType}`);
  // console.debug(`source = ${source[sortKey]}, target = ${target[sortKey]}`);
  let result = 0;
  if (sortType === 'ASC') {
    // Ascending
    if (source[sortKey] < target[sortKey]) {
      result = -1;
    } if (source[sortKey] > target[sortKey]) {
      result = 1;
    }
  } else {
    // Descending
    if (source[sortKey] > target[sortKey]) {
      result = -1;
    } if (source[sortKey] < target[sortKey]) {
      result = 1;
    }
  }
  // console.debug(`result = ${result}`);
  return result;
}
function sortList(
  targetList: any[],
  sortType: string,
) {
  targetList.sort((source, target) => {
    let result = compare('birthDate', sortType, source, target);
    if (result === 0) {
      result = compare('name', sortType, source, target);
      if (result === 0) {
        result = compare('id', sortType, source, target);
      }
    }
    return result
  });
}

const people = [
  {id: 7, name: 'Mike', birthDate: '1922-02-20'},
  {id: 5, name: 'Sam', birthDate: '2011-12-09'},
  {id: 2, name: 'Kevin', birthDate: '1983-07-10'},
  {id: 3, name: 'Tom', birthDate: '1978-01-04'},
  {id: 9, name: 'Tom', birthDate: '2011-12-09'},
  {id: 8, name: 'Sam', birthDate: '2011-12-09'},
  {id: 10, name: 'Kevin', birthDate: '2011-12-09'},
  {id: 1, name: 'Mike', birthDate: '1922-02-20'},
  {id: 4, name: 'Alex', birthDate: '2012-11-23'},
  {id: 6, name: 'Mike', birthDate: '1922-02-20'},
];

console.log('* ASC(昇順) birthDate->name->id ******');
sortList(people, 'ASC');
console.log(people);
console.log('*******************');
console.log('* DESC(降順) birthDate->name->id *****');
sortList(people, 'DESC');
console.log(people);
console.log('*******************');

console.log('Done');

出力結果

* ASC(昇順) birthDate->name->id ******
[
  { id: 1, name: 'Mike', birthDate: '1922-02-20' },
  { id: 6, name: 'Mike', birthDate: '1922-02-20' },
  { id: 7, name: 'Mike', birthDate: '1922-02-20' },
  { id: 3, name: 'Tom', birthDate: '1978-01-04' },
  { id: 2, name: 'Kevin', birthDate: '1983-07-10' },
  { id: 10, name: 'Kevin', birthDate: '2011-12-09' },
  { id: 5, name: 'Sam', birthDate: '2011-12-09' },
  { id: 8, name: 'Sam', birthDate: '2011-12-09' },
  { id: 9, name: 'Tom', birthDate: '2011-12-09' },
  { id: 4, name: 'Alex', birthDate: '2012-11-23' }
]
*******************
* DESC(降順) birthDate->name->id *****
[
  { id: 4, name: 'Alex', birthDate: '2012-11-23' },
  { id: 9, name: 'Tom', birthDate: '2011-12-09' },
  { id: 8, name: 'Sam', birthDate: '2011-12-09' },
  { id: 5, name: 'Sam', birthDate: '2011-12-09' },
  { id: 10, name: 'Kevin', birthDate: '2011-12-09' },
  { id: 2, name: 'Kevin', birthDate: '1983-07-10' },
  { id: 3, name: 'Tom', birthDate: '1978-01-04' },
  { id: 7, name: 'Mike', birthDate: '1922-02-20' },
  { id: 6, name: 'Mike', birthDate: '1922-02-20' },
  { id: 1, name: 'Mike', birthDate: '1922-02-20' }
]
*******************
Done

■ 使用上の注意:大文字・小文字に関して

大文字・小文字が混じっている場合、例1をそのまま使うと
以下「考慮がされていない場合」のようになる。
 => 昇順の場合、大文字⇒小文字でソートされている

対応案
https://stackoverflow.com/questions/8996963/how-to-perform-case-insensitive-sorting-in-javascript

に記載されている方法は、
1)toLowerCase()で小文字(もしくは大文字)に統一してからソート
2)localeCompare() を使ってソート

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare

考慮がされていない場合

// 例1を一部修正

const people = [
  {id: 1, name: 'Mike', birthDate: '1922-02-20'},
  {id: 5, name: 'mike', birthDate: '2011-12-09'},
  {id: 2, name: 'alex', birthDate: '1983-07-10'},
  {id: 3, name: 'Tom', birthDate: '1978-01-04'},
  {id: 4, name: 'Alex', birthDate: '2012-11-23'},
];

console.log('* name ******************');
sortList(people, 'ASC', 'name');
console.log(people);
console.log('*******************');
sortList(people, 'DESC', 'name');
console.log(people);

console.log('Done');

出力結果

* name ******************
[
  { id: 4, name: 'Alex', birthDate: '2012-11-23' },
  { id: 1, name: 'Mike', birthDate: '1922-02-20' },
  { id: 3, name: 'Tom', birthDate: '1978-01-04' }, 
  { id: 2, name: 'alex', birthDate: '1983-07-10' },
  { id: 5, name: 'mike', birthDate: '2011-12-09' } 
]
*******************
[
  { id: 5, name: 'mike', birthDate: '2011-12-09' },
  { id: 2, name: 'alex', birthDate: '1983-07-10' },
  { id: 3, name: 'Tom', birthDate: '1978-01-04' },
  { id: 1, name: 'Mike', birthDate: '1922-02-20' },
  { id: 4, name: 'Alex', birthDate: '2012-11-23' }
]
Done

1)toLowerCase()で小文字(もしくは大文字)に統一してからソート

function sortList(
  targetList: any[],
  sortType: string,
  sortItem: string
) {
  targetList.sort((source, target) => {
    // 小文字で統一する
    const sourceValue = source[sortItem].toLowerCase();
    const targetValue = target[sortItem].toLowerCase();
    if (sortType === 'ASC') {
      // 昇順
      if (sourceValue < targetValue) {
        return -1;
      } if (sourceValue > targetValue) {
        return 1;
      }
    } else {
      // 降順
      if (sourceValue > targetValue) {
        return -1;
      } if (sourceValue < targetValue) {
        return 1;
      }
    }
    return 0;
  });
}

// ... 略 ...

出力結果

* name ******************
[
  { id: 2, name: 'alex', birthDate: '1983-07-10' },
  { id: 4, name: 'Alex', birthDate: '2012-11-23' },
  { id: 1, name: 'Mike', birthDate: '1922-02-20' },
  { id: 5, name: 'mike', birthDate: '2011-12-09' },
  { id: 3, name: 'Tom', birthDate: '1978-01-04' }
]
*******************
[
  { id: 3, name: 'Tom', birthDate: '1978-01-04' },
  { id: 1, name: 'Mike', birthDate: '1922-02-20' },
  { id: 5, name: 'mike', birthDate: '2011-12-09' },
  { id: 2, name: 'alex', birthDate: '1983-07-10' },
  { id: 4, name: 'Alex', birthDate: '2012-11-23' }
]
Done

2)localeCompare() を使ってソート

function sortList(
  targetList: any[],
  sortType: string,
  sortItem: string
) {
  targetList.sort((source, target) => {
    const sourceValue = source[sortItem];
    const targetValue = target[sortItem];
    let result = sourceValue.localeCompare(targetValue, undefined, { sensitivity: 'base' })
    if (sortType === 'ASC') {
      // 昇順
      return result;
    } else {
      return result * (-1);
    }
  });
}

// ... 略 ...

出力結果

* name ******************
[
  { id: 2, name: 'alex', birthDate: '1983-07-10' },
  { id: 4, name: 'Alex', birthDate: '2012-11-23' },
  { id: 1, name: 'Mike', birthDate: '1922-02-20' },
  { id: 5, name: 'mike', birthDate: '2011-12-09' },
  { id: 3, name: 'Tom', birthDate: '1978-01-04' }
]
*******************
[
  { id: 3, name: 'Tom', birthDate: '1978-01-04' },
  { id: 1, name: 'Mike', birthDate: '1922-02-20' },
  { id: 5, name: 'mike', birthDate: '2011-12-09' },
  { id: 2, name: 'alex', birthDate: '1983-07-10' },
  { id: 4, name: 'Alex', birthDate: '2012-11-23' }
]

関連記事

TypeScript ~ 入門編 ~
https://dk521123.hatenablog.com/entry/2020/12/21/180904
ループ操作 ~ map etc ~
https://dk521123.hatenablog.com/entry/2021/01/03/000000
配列・リスト操作
https://dk521123.hatenablog.com/entry/2021/02/10/225119
配列・リスト操作 ~ スプレッド構文 / Three-dots ~
https://dk521123.hatenablog.com/entry/2021/03/09/000000