Skip to content

Commit 42e466e

Browse files
committed
select adjacent edge cases #3
1 parent 98a05df commit 42e466e

File tree

7 files changed

+393
-65
lines changed

7 files changed

+393
-65
lines changed

src/arrayUtils.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { head } from "ramda";
2+
3+
export function findNextPivot(
4+
sortedArray: string[],
5+
subarray: string[],
6+
previousPivot: string
7+
): string {
8+
const previousIndex = sortedArray.indexOf(previousPivot);
9+
let nextIndex = previousIndex + 1;
10+
11+
while (nextIndex <= sortedArray.length - 1) {
12+
const nextItem = sortedArray[nextIndex];
13+
if (subarray.includes(nextItem)){
14+
return nextItem;
15+
};
16+
nextIndex++;
17+
}
18+
19+
nextIndex = previousIndex - 1;
20+
21+
while (nextIndex >= 0) {
22+
const prevItem = sortedArray[nextIndex];
23+
if (subarray.includes(prevItem)) {
24+
return prevItem;
25+
}
26+
nextIndex--;
27+
}
28+
29+
return head(sortedArray)!
30+
}
31+
32+
export function findAdjacentToPivotInSortedArray(
33+
sortedArray: string[],
34+
subarray: string[],
35+
item: string): string[] {
36+
const indexOfItem = sortedArray.indexOf(item);
37+
let leftIndex = indexOfItem;
38+
let rightIndex = indexOfItem;
39+
let hasLeftAdjacent = leftIndex > 0;
40+
let hasRightAdjacent = rightIndex < sortedArray.length - 1;
41+
while ((hasLeftAdjacent || hasRightAdjacent)) {
42+
if (leftIndex > 0) {
43+
const nextLeft = sortedArray[leftIndex - 1];
44+
hasLeftAdjacent = subarray.includes(nextLeft);
45+
leftIndex = hasLeftAdjacent ? leftIndex - 1 : leftIndex;
46+
} else {
47+
hasLeftAdjacent = false
48+
}
49+
50+
if (rightIndex < sortedArray.length - 1) {
51+
const nextRight = sortedArray[rightIndex + 1];
52+
hasRightAdjacent = subarray.includes(nextRight);
53+
rightIndex = hasRightAdjacent ? rightIndex + 1 : rightIndex;
54+
} else {
55+
hasRightAdjacent = false
56+
}
57+
}
58+
59+
return sortedArray.slice(leftIndex, rightIndex + 1);
60+
}

src/index.ts

Lines changed: 32 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
1-
import { head, take } from "ramda";
1+
import { difference, head, take, union, without } from "ramda";
2+
import { findAdjacentToPivotInSortedArray, findNextPivot } from "./arrayUtils";
23

34
export type Context = {
4-
list: string[],
5-
selected: string[],
6-
adjacentPivot: undefined,
7-
lastSelected: undefined,
8-
} | {
95
list: string[],
106
selected: string[],
117
adjacentPivotstring,
12-
lastSelected: string,
138
}
149

1510
type Command =
@@ -23,6 +18,7 @@ function listIncludesAndIsNotEmpty(list: string[], item: string) {
2318
}
2419

2520
export function multiselect(context: Context, command: Command): Context {
21+
2622
if (
2723
command.type === "SELECT ONE" &&
2824
listIncludesAndIsNotEmpty(context.list, command.id)
@@ -31,7 +27,6 @@ export function multiselect(context: Context, command: Command): Context {
3127
...context,
3228
selected: [command.id],
3329
adjacentPivot: command.id,
34-
lastSelected: command.id,
3530
};
3631
} else if (
3732
command.type === 'TOGGLE SELECTION' &&
@@ -43,23 +38,24 @@ export function multiselect(context: Context, command: Command): Context {
4338
return {
4439
...context,
4540
selected: [],
46-
adjacentPivot: undefined,
47-
lastSelected: undefined
41+
adjacentPivot: head(context.list)!,
4842
};
4943
} else if (
5044
command.type === 'TOGGLE SELECTION' &&
5145
listIncludesAndIsNotEmpty(context.list, command.id) &&
5246
context.selected.includes(command.id)
5347
) {
54-
const index = context.selected.indexOf(command.id);
55-
const adjacentPivot= (index < (context.selected.length - 1))
56-
? context.selected[index + 1]
57-
: context.selected[index - 1];
48+
const selected = context.selected.filter(x => x !== command.id);
49+
const adjacentPivot = findNextPivot(
50+
context.list,
51+
selected,
52+
context.adjacentPivot
53+
);
54+
5855
return {
5956
...context,
60-
selected: context.selected.filter(x => x !== command.id),
57+
selected,
6158
adjacentPivot,
62-
lastSelected: adjacentPivot
6359
};
6460
} else if (
6561
command.type === 'TOGGLE SELECTION' &&
@@ -69,14 +65,12 @@ export function multiselect(context: Context, command: Command): Context {
6965
...context,
7066
selected: context.selected.concat([command.id]),
7167
adjacentPivot: command.id,
72-
lastSelected: command.id
7368
};
7469
} else if (command.type === "DESELECT ALL") {
7570
return {
7671
...context,
7772
selected: [],
78-
adjacentPivot: undefined,
79-
lastSelected: undefined
73+
adjacentPivot: head(context.list)!,
8074
}
8175
} else if (
8276
command.type === "SELECT ADJACENT" &&
@@ -87,20 +81,31 @@ export function multiselect(context: Context, command: Command): Context {
8781
return {
8882
...context,
8983
selected: take(n, context.list),
90-
adjacentPivot: head(context.list)!,
91-
lastSelected: command.id,
9284
}
9385
} else if (
9486
command.type === "SELECT ADJACENT" &&
95-
listIncludesAndIsNotEmpty(context.list, command.id) &&
96-
context.adjacentPivot
87+
listIncludesAndIsNotEmpty(context.list, command.id)
9788
) {
98-
const start = context.list.indexOf(context.adjacentPivot);
99-
const n = context.list.indexOf(command.id);
89+
90+
const pivotIndex = context.list.indexOf(context.adjacentPivot);
91+
const selectionIndex = context.list.indexOf(command.id);
92+
93+
const adjacent = findAdjacentToPivotInSortedArray(
94+
context.list,
95+
context.selected,
96+
context.adjacentPivot
97+
);
98+
99+
const nextSelection = context.list.slice(
100+
Math.min(pivotIndex, selectionIndex),
101+
Math.max(pivotIndex, selectionIndex) + 1
102+
);
103+
104+
const toRemove = difference(adjacent, nextSelection);
105+
100106
return {
101107
...context,
102-
selected: context.list.slice(Math.min(start, n), Math.max(start, n) + 1),
103-
lastSelected: command.id
108+
selected: union(without(toRemove, context.selected), nextSelection)
104109
}
105110
} else {
106111
return context;

src/spec/arrayUtils.spec.ts

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
import fc from 'fast-check';
2+
import { head } from 'ramda';
3+
import { findAdjacentToPivotInSortedArray, findNextPivot } from "../arrayUtils";
4+
5+
describe('find adjacent to pivot', () => {
6+
test('should find adjacent elements', () => {
7+
fc.assert(
8+
fc.property(
9+
fc.set(
10+
fc.string(),
11+
{ minLength: 1 }
12+
)
13+
.chain(sortedArray =>
14+
fc
15+
.nat(sortedArray.length -1) // pivot
16+
.chain(pivot =>
17+
fc.tuple(
18+
fc.nat(pivot), // adjacent left bound
19+
fc.integer(pivot, sortedArray.length - 1) // adjacent right bound
20+
)
21+
.chain(([adjacentLeftBound, adjacentRightBound]) =>
22+
fc.tuple(
23+
fc.nat(
24+
adjacentLeftBound > 1
25+
? adjacentLeftBound - 2
26+
: 0
27+
) // left space after adjacent
28+
.chain(leftSpace =>
29+
fc.shuffledSubarray(sortedArray.slice(0, leftSpace))
30+
),
31+
fc.integer(
32+
(adjacentRightBound + 2 < sortedArray.length - 1)
33+
? adjacentRightBound + 2
34+
: sortedArray.length - 1,
35+
sortedArray.length - 1
36+
) // right space after adjacent
37+
.chain(rightSpace =>
38+
fc.shuffledSubarray(sortedArray.slice(rightSpace, sortedArray.length - 1))
39+
)
40+
, // right space after adjacent
41+
)
42+
.map(([left, right]) => {
43+
const adjacent = sortedArray.slice(adjacentLeftBound, adjacentRightBound + 1);
44+
return {
45+
sortedArray: sortedArray,
46+
pivot: sortedArray[pivot],
47+
subarray: [
48+
...left,
49+
...adjacent,
50+
...right,
51+
],
52+
adjacent,
53+
}
54+
})
55+
)
56+
)
57+
)
58+
,
59+
({
60+
pivot,
61+
sortedArray,
62+
subarray,
63+
adjacent
64+
}) => {
65+
expect(
66+
findAdjacentToPivotInSortedArray(
67+
sortedArray,
68+
subarray,
69+
pivot
70+
)
71+
)
72+
.toEqual(
73+
adjacent
74+
)
75+
}
76+
),
77+
{
78+
verbose: true
79+
}
80+
)
81+
});
82+
});
83+
84+
describe('find next pivot', () => {
85+
test('should return 0 if theres nothing else selected', () => {
86+
fc.assert(
87+
fc.property(
88+
fc.set(
89+
fc.string(),
90+
{ minLength: 1 }
91+
)
92+
.chain(sortedArray =>
93+
fc.record({
94+
sortedArray: fc.constant(sortedArray),
95+
previousPivot: fc
96+
.nat(sortedArray.length - 1)
97+
.map(index => sortedArray[index])
98+
})
99+
)
100+
,
101+
({
102+
previousPivot,
103+
sortedArray
104+
}) => {
105+
expect(
106+
findNextPivot(
107+
sortedArray,
108+
[],
109+
previousPivot
110+
)
111+
).toEqual(head(sortedArray)!)
112+
}
113+
)
114+
)
115+
});
116+
117+
test('should return pivot if exits on left side', () => {
118+
fc.assert(
119+
fc.property(
120+
fc.set(
121+
fc.string(),
122+
{ minLength: 2 }
123+
)
124+
.chain(sortedArray =>
125+
fc
126+
.integer(1, sortedArray.length - 1)
127+
.chain(index =>
128+
fc
129+
.nat(index - 1)
130+
.chain(nextPivot =>
131+
fc
132+
.shuffledSubarray(sortedArray.slice(0, nextPivot))
133+
.map(selection => ({
134+
previousPivot: sortedArray[index],
135+
sortedArray: sortedArray,
136+
nextPivot: sortedArray[nextPivot],
137+
selection: selection.concat([sortedArray[nextPivot]])
138+
}))
139+
)
140+
),
141+
)
142+
,
143+
({
144+
previousPivot,
145+
sortedArray,
146+
selection,
147+
nextPivot
148+
}) => {
149+
expect(
150+
findNextPivot(
151+
sortedArray,
152+
selection,
153+
previousPivot
154+
)
155+
).toEqual(nextPivot)
156+
}
157+
)
158+
)
159+
})
160+
161+
test('should return pivot if exits on right side', () => {
162+
fc.assert(
163+
fc.property(
164+
fc.set(
165+
fc.string(),
166+
{ minLength: 2 }
167+
)
168+
.chain(sortedArray =>
169+
fc
170+
.nat(sortedArray.length - 2)
171+
.chain(index =>
172+
fc
173+
.integer(index + 1, sortedArray.length -1)
174+
.chain(nextPivot =>
175+
fc.
176+
tuple(
177+
fc.shuffledSubarray(sortedArray.slice(0, index)),
178+
fc.shuffledSubarray(sortedArray.slice(nextPivot +1, sortedArray.length - 1))
179+
)
180+
.map(([left, right]) => ({
181+
previousPivot: sortedArray[index],
182+
sortedArray: sortedArray,
183+
nextPivot: sortedArray[nextPivot],
184+
selection: left.concat([sortedArray[nextPivot]]).concat(right)
185+
}))
186+
)
187+
),
188+
)
189+
,
190+
({
191+
previousPivot,
192+
sortedArray,
193+
selection,
194+
nextPivot
195+
}) => {
196+
expect(
197+
findNextPivot(
198+
sortedArray,
199+
selection,
200+
previousPivot
201+
)
202+
).toEqual(nextPivot)
203+
}
204+
)
205+
)
206+
})
207+
})

0 commit comments

Comments
 (0)