-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
198 lines (177 loc) · 6.7 KB
/
index.js
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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
/**
* Lexographic List Sorting Functions
*
*/
const lettersToBase26 = {
a: '0',
b: '1',
c: '2',
d: '3',
e: '4',
f: '5',
g: '6',
h: '7',
i: '8',
j: '9',
k: 'a',
l: 'b',
m: 'c',
n: 'd',
o: 'e',
p: 'f',
q: 'g',
r: 'h',
s: 'i',
t: 'j',
u: 'k',
v: 'l',
w: 'm',
x: 'n',
y: 'o',
z: 'p',
};
// Reverse the object so that we can convert base26 to letters.
const lettersBase26ToWord = {};
Object.keys(lettersToBase26).forEach((key) => {
const val = lettersToBase26[key];
lettersBase26ToWord[val] = key;
});
// Add a letter to a word.
export const addToWord = (word, valToAdd) => {
const split = word.split('');
const b26 = split.map(l => lettersToBase26[l]).join('');
const b10 = parseInt(b26, 26);
const newB10 = b10 + valToAdd;
const newB26 = newB10.toString(26);
const split2 = newB26.split('');
const newWord = split2.map(l => lettersBase26ToWord[l]).join('');
return newWord;
};
// Get the last letter of a word, and also return the word without the last letter and the length of the word.
export const getLastLetter = (word) => {
const split = word.split('');
const wordLength = split.length;
const last = split.pop();
return {
lastLetter: last,
allBeforeLast: split.join(''),
wordLength,
};
};
// Constants.
export const FIRST_LIST_ITEM = 'mmmmmm-m';
const UPPER_LIST_LIMIT = 'aaaaaa';
const LOWER_LIST_LIMIT = 'zzzzzz';
const LAST_LETTER = 'z';
const FIRST_LETTER = 'b';
const ADDITIONAL_LETTER = 'm';
/**
* Insert an item between two items.
* This function will compute a string that can be lexicographically sorted between two items.
* beforeItem and afterItem are optional. If both are omitted, the new item will be the first item in the list.
* If beforeItem is omitted, the new item will be the first item in the list.
* If afterItem is omitted, the new item will be the last item in the list.
*
* @param {string} [beforeItem] - the item that will come before.
* @param {string} [afterItem] - the item that will come after.
* @returns
*/
export const insertBetween = (beforeItem, afterItem) => () => {
let newItem;
const beforeItemSplit = beforeItem ? beforeItem.split('-') : null;
const afterItemSplit = afterItem ? afterItem.split('-') : null;
if (!beforeItem && !afterItem) {
newItem = FIRST_LIST_ITEM;
}
// Start of list. Subtract one from first 'word'.
if (!beforeItemSplit && afterItemSplit) {
// If we have reached all a's in the first portion of the key,
// add to second part of key rather than the first, using the same procedure
// you'd normally use with the second portion.
if (afterItemSplit[0] === UPPER_LIST_LIMIT) {
// Do not subtract from first word anymore - aaaaa is as far as we go.
// And it is amazing that we got there. Will probably never happen in practice.
// Subtract one from the second word.
const { lastLetter, allBeforeLast } = getLastLetter(afterItemSplit[1]);
const p2 = addToWord(lastLetter, -1);
// If the last letter is 'b', also add 'm'
if (p2 === FIRST_LETTER) {
newItem = `${afterItemSplit[0]}-${allBeforeLast}${p2}${ADDITIONAL_LETTER}`;
} else {
newItem = `${afterItemSplit[0]}-${allBeforeLast}${p2}`;
}
} else {
// Subtract from the first word.
const p1 = addToWord(afterItemSplit[0], -1);
newItem = `${p1}-${ADDITIONAL_LETTER}`;
}
}
// End of list. Add one to first 'word', and end last word with m.
if (beforeItemSplit && !afterItemSplit) {
if (beforeItemSplit[0] === LOWER_LIST_LIMIT) {
// Do not add to first word anymore - zzzzz is as far as we go.
// Add one to the second word.
const { lastLetter, allBeforeLast } = getLastLetter(beforeItemSplit[1]);
const p2 = addToWord(lastLetter, 1);
// If the last letter is 'z', also add 'm'
if (p2 === LAST_LETTER) {
newItem = `${beforeItemSplit[0]}-${allBeforeLast}${p2}${ADDITIONAL_LETTER}`;
} else {
newItem = `${beforeItemSplit[0]}-${allBeforeLast}${p2}`;
}
} else {
const p1 = addToWord(beforeItemSplit[0], 1);
newItem = `${p1}-${ADDITIONAL_LETTER}`;
}
}
// Between two items.
if (beforeItemSplit && afterItemSplit) {
// First check if you can add one to the first word.
const { lastLetter, allBeforeLast, wordLength } = getLastLetter(beforeItemSplit[1]);
const potentialLastLetter = addToWord(lastLetter, 1);
const { lastLetter: afterLastLetter, allBeforeLast: afterAllBeforeLast, wordLength: afterWordLength } = getLastLetter(afterItemSplit[1]);
const beforeLastLetterIndex = wordLength - 1;
const afterLastLetterIndex = afterWordLength - 1;
const letterInAfterAtSameIndexAsLastLetterInBefore = afterItemSplit[1][beforeLastLetterIndex];
// Can we add one letter to the 'before' word?
if (lastLetter === LAST_LETTER || letterInAfterAtSameIndexAsLastLetterInBefore === lastLetter || letterInAfterAtSameIndexAsLastLetterInBefore === potentialLastLetter) {
// In this case, we cannot add one to before letter.
// Can we subtract one from afterWord?
const potentialLastLetterInAfter = addToWord(afterLastLetter, -1);
const letterInBeforeAtSameIndexAsLastLetterInAfter = beforeItemSplit[1][afterLastLetterIndex];
if (afterLastLetter === FIRST_LETTER || letterInBeforeAtSameIndexAsLastLetterInAfter === lastLetter || letterInBeforeAtSameIndexAsLastLetterInAfter === potentialLastLetterInAfter) {
// We can't subtract from this either. Need to add an 'm' to beforeWord.
newItem = `${beforeItemSplit[0]}-${beforeItemSplit[1]}${ADDITIONAL_LETTER}`;
} else {
// Subtract one letter from the 'after' word.
// If last letter is b, always add 'm' as well.
newItem = `${beforeItemSplit[0]}-${afterAllBeforeLast}${potentialLastLetterInAfter}${potentialLastLetterInAfter === FIRST_LETTER ? ADDITIONAL_LETTER : ''}`;
}
} else {
// Add one letter to the 'before' word.
// If last letter is z, always add 'm' as well.
newItem = `${beforeItemSplit[0]}-${allBeforeLast}${potentialLastLetter}${potentialLastLetter === 'z' ? ADDITIONAL_LETTER : ''}`;
}
}
return newItem;
};
/**
* Returns an array with sorted strings for the entire list length.
*
* Only provide startWithWord if you want to start from a specific word.
*
* @param {number} listLength - length of a list you want to generate.
* @param {string} [startWithWord]
* @returns
*/
export const resortEntireList = (listLength, startWithWord) => () => {
let startingWord = 'mmmmmm';
if (lastWordOfPreviousList) {
startingWord = lastWordOfPreviousList;
}
for (let i = 0; i < listLength; i++) {
const newWord = addToWord(startingWord, (i + 1));
return `${newWord}-m`;
}
return newOrder;
};