/**
* @packageDocumentation
* @module array
*/
import { getRandomizer } from '../number/utils';
/**
* 지정된 avaliable 에 value 가 포함되는 경우 value 를, 그렇지 않은 경우 or 값을 반환
* @template T
* @param [available=[]] 유효한 값들
* @param value 검사할 값
* @param [or=null] 검사할 값이 유효한 값에 포함되지 않을 경우 대체 값
* @example
* console.log(availableOr(available1, 10, 10)); // 10
* console.log(availableOr(available1, 20, 10)); // 20
* console.log(availableOr(available1, 30, 10)); // 30
* console.log(availableOr(available1, 30, null)); // 30
* console.log(availableOr(available1, 111, 10)); // 10
* console.log(availableOr(available1, 222, 10)); // 10
* console.log(availableOr(available1, 333, 10)); // 10
* console.log(availableOr(available1, 444, null)); // null
*/
export function availableOr<T>(available: ArrayLike<T> = [], value: any, or = null): T {
if (!available.length) return or;
const find = (available as any[]).find((compare: any) => {
return compare === value;
});
return find || or;
}
/**
* 지정된 ref 배열의 index 에 value 를 삽입합니다.
* (원본 ref 가 직접 변경 됩니다.)
* @param ref 소스 배열
* @param [index=0] 삽입될 인덱스
* @param value 삽입될 값
* @example
* console.log(insert([1, 2, 3], 1, 99)); // [1, 99, 2, 3]
* console.log(insert([1, 2, 3], 1, ["A", "B"])); // [1, "A", "B", 2, 3];
* console.log(insert([1, 2, 3], 10, 99)); // [1, 2, 3, 99]);
* console.log(insert([1, 2, 3], 10, ["A", "B"])); // [1, 2, 3, "A", "B"];
* console.log(insert([1, 2, 3], null, "A")); // ["A", 1, 2, 3];
*/
export function insert(ref: any[], index: number = 0, value: any): any[] {
if (isNaN(index) || index < 0 || ref.length <= index) {
if (value instanceof Array) {
ref.push(...value);
} else {
ref.push(value);
}
} else {
if (value instanceof Array) {
ref.splice(index, 0, ...value);
} else {
ref.splice(index, 0, value);
}
}
return ref;
}
/**
* 지정된 ref 배열의 index ~ deleteCount 만큼 제거합니다.
* (원본 ref 가 직접 변경 됩니다.)
* @template T
* @param ref 소스 배열
* @param [index=0] 삭제 시작 index
* @param [deleteCount=1] 삭제 갯수
* @example
* console.log(extract([1, 2, 3, 4])); // [2, 3, 4];
* console.log(extract([1, 2, 3, 4], 1, 1)); // [1, 3, 4];
* console.log(extract([1, 2, 3, 4], 2, 1)); // [1, 2, 4];
* console.log(extract([1, 2, 3, 4], 3, 1)); // [1, 2, 3];
* console.log(extract([1, 2, 3, 4], 0, 2)); // [3, 4];
* console.log(extract([1, 2, 3, 4], 0, 10)); // [];
* console.log(extract([1, 2, 3, 4], 2, 10)); // [1, 2];
*/
export function extract<T>(ref: ArrayLike<T>, index: number = 0, deleteCount: number = 1): ArrayLike<T> {
if (isNaN(index) || index < 0 || ref.length <= index) {
return ref;
}
(ref as any[]).splice(index, deleteCount);
return ref;
}
/**
* 지정된 마지막 배열을 아이템을 반환 합니다.
* @template T
* @param array 소스 배열
* @param [shiftIndex=0] 아이템을 꺼낼 index. (뒤에서 앞으로)
* @param [overflowSafe=true] 계산된 index 가 0 보다 작은 경우는 맨 앞, length 보다 큰 경우는 마지막 아이템 index 로 찾을지 여부
* @example
* console.log(tail([1, 2, 3, 4])); // 4
* console.log(tail([1, 2, 3, 4],1)); // 3
* console.log(tail([1, 2, 3, 4],2)); // 2
* console.log(tail([1, 2, 3, 4],3)); // 1
* console.log(tail([1, 2, 3, 4],4)); // 1
* console.log(tail([1, 2, 3, 4],-1)); // 4
*/
export function tail<T>(array: ArrayLike<T>, shiftIndex: number = 0, overflowSafe: boolean = true): T {
const lastIndex = array.length - 1;
let index = lastIndex - shiftIndex;
if (overflowSafe) {
if (index < 0) {
index = 0;
} else if (lastIndex < index) {
index = lastIndex;
}
}
return array[index];
}
/**
* 중복된 값을 제외시킨 배열을 반환 합니다.
* @template T
* @param array 소스 배열
* @param [uniqueFn] 직접 filter 할 key 값을 반환할 수 있는 함수
* @example
*
* const arr1 = [1, 2, 3, 4, 5];
* const arr2 = [1, 2, 1, 3, 4, 4, 3, 5, 1];
* const arr3 = [
* { id: 1, name: "google" },
* { id: 2, name: "microsoft" },
* { id: 1, name: "google" },
* { id: 3, name: "amazone" }
* ];
* console.log(distinct(arr1)); // [1, 2, 3, 4, 5]
* console.log(distinct(arr2)); // [1, 2, 3, 4, 5]
* console.log(distinct(arr3, item => {
* return item.id;
* })); // [{ id: 1, name: "google" }, { id: 2, name: "microsoft" }, { id: 3, name: "amazone" }]
*/
export function distinct<T>(array: ReadonlyArray<T>, uniqueFn?: (item: T) => string | any): T[] {
if (!uniqueFn) {
return array.filter((item, index) => {
return array.indexOf(item) === index;
});
}
return array.filter(uniqueFilter(uniqueFn));
}
/**
* unique 필터를 만들 때 흔히 반복되는 구문을 구현해 놓은 함수
* @template T
* @param uniqueFn filter 할 key 값을 반환할 수 있는 함수
* @example
* const arr1 = [
* { id: 1, name: "google" },
* { id: 2, name: "microsoft" },
* { id: 1, name: "google" },
* { id: 3, name: "amazone" }
* ];
* const idFilter = item => {
* return item.id;
* };
* const nameFilter = item => {
* return item.name;
* };
* console.log(arr1.filter(uniqueFilter(idFilter))); // [{ id: 1, name: "google" },{ id: 2, name: "microsoft" },{ id: 3, name: "amazone" } ]
* console.log(arr1.filter(uniqueFilter(nameFilter))); // [{ id: 1, name: "google" },{ id: 2, name: "microsoft" },{ id: 3, name: "amazone" } ]
*/
export function uniqueFilter<T>(uniqueFn: (item: T) => string | any): (item: T) => boolean {
const hash: { [key: string]: boolean } = {};
return (item) => {
const key = uniqueFn(item);
if (hash[key]) return false;
hash[key] = true;
return true;
};
}
/**
* 배열을 섞습니다.
* @template T
* @param ref 소스 배열
* @param [seed] [getRandomizer]{@link getRandomizer}
* @example
* const arr = [1,2,3,4];
* shuffle(arr);
* console.log(arr); // [?,?,?,?]
*/
export function shuffle<T>(ref: T[], seed?: number): void {
const random = getRandomizer(seed);
for (let i = ref.length - 1; i > 0; i -= 1) {
const j = Math.floor(random() * (i + 1));
const temp = ref[i];
ref[i] = ref[j];
ref[j] = temp;
}
}
/**
* 1차원 행[]을 2차원 행열[][]로 바꿉니다. 원본 컬럼을 그대로 사용합니다.
* @template T 각 행의 타입
* @param {T[]} ref 변경 할 배열
* @returns {T[][]}
* @example
* const arr = [{id:1,name:'A'},{id:2,name:'B'},{id:3,name:'C'}];
* const rows = transposeRow(arr);
* console.log(rows); // [[{"id":1,"name":"A"},{"id":2,"name":"B"},{"id":3,"name":"C"}],[{"id":1,"name":"A"},{"id":2,"name":"B"},{"id":3,"name":"C"}],[{"id":1,"name":"A"},{"id":2,"name":"B"},{"id":3,"name":"C"}]]
*/
export function transposeRow<T>(ref: T[]): T[][] {
const refLen = ref.length;
const newRow: T[][] = Array(refLen);
for (let r = 0; r < refLen; r++) {
const newColumn: T[] = Array(refLen);
for (let c = 0; c < refLen; c++) {
newColumn[c] = ref[c];
}
newRow[r] = newColumn;
}
return newRow;
}
interface TransposeRowFilterParams<T> {
rowIndex: number;
columnIndex: number;
item: T;
}
/**
* 1차원 행[]을 2차원 행열[][]로 바꿉니다. 필터를 거친 요소로 변경합니다.
* @template T 각 행의 타입
* @template C 필터한 열의 타입
* @param {T[]} ref 변경 할 배열
* @param {Function} filter (params: TransposeRowFilterParams<T>) => C 각 요소를 정제할 함수.
* @returns {C[][]}
* @example
* const arr = [{id:1,name:'A'},{id:2,name:'B'},{id:3,name:'C'}];
* const rows = transposeRowFilter(arr, (params) => {
* const { rowIndex, columnIndex, item } = params;
* return {
* ...item,
* myValue1: item.name.toLowerCase(),
* rowIndex,
* columnIndex,
* };
* });
* console.log(rows); // [[{"id":1,"name":"A","myValue1":"a","rowIndex":0,"columnIndex":0},{"id":2,"name":"B","myValue1":"b","rowIndex":0,"columnIndex":1},{"id":3,"name":"C","myValue1":"c","rowIndex":0,"columnIndex":2}],[{"id":1,"name":"A","myValue1":"a","rowIndex":1,"columnIndex":0},{"id":2,"name":"B","myValue1":"b","rowIndex":1,"columnIndex":1},{"id":3,"name":"C","myValue1":"c","rowIndex":1,"columnIndex":2}],[{"id":1,"name":"A","myValue1":"a","rowIndex":2,"columnIndex":0},{"id":2,"name":"B","myValue1":"b","rowIndex":2,"columnIndex":1},{"id":3,"name":"C","myValue1":"c","rowIndex":2,"columnIndex":2}]];
*/
export function transposeRowFilter<T, C>(ref: T[], filter: (params: TransposeRowFilterParams<T>) => C): C[][] {
const refLen = ref.length;
const newRow: C[][] = Array(refLen);
for (let r = 0; r < refLen; r++) {
const newColumn: C[] = Array(refLen);
for (let c = 0; c < refLen; c++) {
newColumn[c] = filter({
rowIndex: r,
columnIndex: c,
item: ref[c],
});
}
newRow[r] = newColumn;
}
return newRow;
}