This repository has been archived by the owner on Apr 3, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 3
/
utils.ts
146 lines (138 loc) · 4.29 KB
/
utils.ts
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
/*
egjs-list-differ
Copyright (c) 2019-present NAVER Corp.
MIT license
*/
import { SUPPORT_MAP } from "./consts";
import HashMap from "./HashMap";
import PolyMap from "./PolyMap";
import Result from "./Result";
import {
MaintainedRecord,
CurrentRecord,
DiffResult,
MapInterface,
PrevRecord,
} from "./types";
/**
*
* @memberof eg.ListDiffer
* @static
* @function
* @param - Previous List <ko> 이전 목록 </ko>
* @param - List to Update <ko> 업데이트 할 목록 </ko>
* @param - This callback function returns the key of the item. <ko> 아이템의 키를 반환하는 콜백 함수입니다.</ko>
* @return - Returns the diff between `prevList` and `list` <ko> `prevList`와 `list`의 다른 점을 반환한다.</ko>
* @example
* import { diff } from "@egjs/list-differ";
* // script => eg.ListDiffer.diff
* const result = diff([0, 1, 2, 3, 4, 5], [7, 8, 0, 4, 3, 6, 2, 1], e => e);
* // List before update
* // [1, 2, 3, 4, 5]
* console.log(result.prevList);
* // Updated list
* // [4, 3, 6, 2, 1]
* console.log(result.list);
* // Index array of values added to `list`
* // [0, 1, 5]
* console.log(result.added);
* // Index array of values removed in `prevList`
* // [5]
* console.log(result.removed);
* // An array of index pairs of `prevList` and `list` with different indexes from `prevList` and `list`
* // [[0, 2], [4, 3], [3, 4], [2, 6], [1, 7]]
* console.log(result.changed);
* // An array of index pairs to be `ordered` that can synchronize `list` before adding data. (Formatted by: Array<[prevIndex, nextIndex]>)
* // [[4, 1], [4, 2], [4, 3]]
* console.log(result.ordered);
* // An array of index pairs of `prevList` and `list` that have not been added/removed so data is preserved
* // [[0, 2], [4, 3], [3, 4], [2, 6], [1, 7]]
* console.log(result.maintained);
*/
export function diff<T>(
prevList: T[],
list: T[],
findKeyCallback?: (e: T, i: number, arr: T[]) => unknown
): DiffResult<T> {
const mapClass: new () => MapInterface<unknown, number> = SUPPORT_MAP
? Map
: findKeyCallback
? HashMap
: PolyMap;
const callback = findKeyCallback || ((e: T) => e);
const added: CurrentRecord<T>[] = [];
const removed: PrevRecord<T>[] = [];
const maintained: MaintainedRecord<T>[] = [];
const changed: MaintainedRecord<T>[] = [];
const prevKeys = prevList.map(callback);
const keys = list.map(callback);
const prevKeyMap: MapInterface<unknown, number> = new mapClass();
const keyMap: MapInterface<unknown, number> = new mapClass();
const removedMap: Record<number, number> = {};
const orderPriority: number[] = [];
let addedCount = 0;
let removedCount = 0;
// Add prevKeys and keys to the hashmap.
prevKeys.forEach((key, prevIndex) => {
prevKeyMap.set(key, prevIndex);
});
keys.forEach((key, listIndex) => {
keyMap.set(key, listIndex);
});
// Compare `prevKeys` and `keys` and add them to `removed` if they are not in `keys`.
prevKeys.forEach((key, prevIndex) => {
const listIndex = keyMap.get(key);
// In prevList, but not in list, it is removed.
if (typeof listIndex === "undefined") {
++removedCount;
removed.push({
prevItem: prevList[prevIndex],
prevIndex,
});
} else {
removedMap[listIndex] = removedCount;
}
});
// Compare `prevKeys` and `keys` and add them to `added` if they are not in `prevKeys`.
keys.forEach((key, currentIndex) => {
const prevIndex = prevKeyMap.get(key);
const currentItem = list[currentIndex];
// In list, but not in prevList, it is added.
if (typeof prevIndex === "undefined") {
added.push({
currentItem,
currentIndex,
});
++addedCount;
} else {
const prevItem = prevList[prevIndex];
maintained.push({
prevItem,
currentItem,
prevIndex,
currentIndex,
});
removedCount = removedMap[currentIndex] || 0;
orderPriority[prevIndex - removedCount] = currentIndex - addedCount;
if (prevIndex !== currentIndex) {
changed.push({
prevItem,
currentItem,
prevIndex,
currentIndex,
});
}
}
});
// Sort by ascending order of 'to(list's index).
removed.reverse();
return new Result(
prevList,
list,
added,
removed,
changed,
maintained,
orderPriority
);
}