Skip to content

Commit 4a3ce45

Browse files
KyrylRArvolear
andauthored
Add remove function to the PriorityQueue (#147)
* Added `remove` function to the PriorityQueue * Added `remove` function to the PriorityQueue * optimize remove() --------- Co-authored-by: Artem Chystiakov <artem.ch31@gmail.com>
1 parent 00a0d1e commit 4a3ce45

File tree

3 files changed

+228
-1
lines changed

3 files changed

+228
-1
lines changed

contracts/libs/data-structures/PriorityQueue.sol

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {TypeCaster} from "../utils/TypeCaster.sol";
88
*
99
* Courtesy of heap property,
1010
* add() and removeTop() operations are O(log(n)) complex
11+
* remove() operation is O(n) for search + O(log(n)) for removal
1112
* top(), topValue() operations are O(1)
1213
*
1314
* The library might be useful to implement priority withdrawals/purchases, reputation based systems, and similar logic.
@@ -60,6 +61,17 @@ library PriorityQueue {
6061
_removeTop(queue._queue);
6162
}
6263

64+
/**
65+
* @notice The function to remove a specific element from the uint256 queue. O(n) complex for search + O(log(n)) for removal
66+
* In case of duplicate elements, removes the one with the highest priority.
67+
* @param queue self
68+
* @param value_ the element value to remove
69+
* @return true if element was found and removed, false otherwise
70+
*/
71+
function remove(UintQueue storage queue, uint256 value_) internal returns (bool) {
72+
return _remove(queue._queue, bytes32(value_));
73+
}
74+
6375
/**
6476
* @notice The function to read the value of the element with the highest priority. O(1) complex
6577
* @param queue self
@@ -136,6 +148,14 @@ library PriorityQueue {
136148
_removeTop(queue._queue);
137149
}
138150

151+
/**
152+
* @notice The function to remove a specific element from the bytes32 queue. O(n) complex for search + O(log(n)) for removal
153+
* In case of duplicate elements, removes the one with the highest priority.
154+
*/
155+
function remove(Bytes32Queue storage queue, bytes32 value_) internal returns (bool) {
156+
return _remove(queue._queue, value_);
157+
}
158+
139159
/**
140160
* @notice The function to read the value of the element with the highest priority. O(1) complex
141161
*/
@@ -200,6 +220,14 @@ library PriorityQueue {
200220
_removeTop(queue._queue);
201221
}
202222

223+
/**
224+
* @notice The function to remove a specific element from the address queue. O(n) complex for search + O(log(n)) for removal
225+
* In case of duplicate elements, removes the one with the highest priority.
226+
*/
227+
function remove(AddressQueue storage queue, address value_) internal returns (bool) {
228+
return _remove(queue._queue, bytes32(uint256(uint160(value_))));
229+
}
230+
203231
/**
204232
* @notice The function to read the value of the element with the highest priority. O(1) complex
205233
*/
@@ -273,6 +301,44 @@ library PriorityQueue {
273301
_shiftDown(queue, 0);
274302
}
275303

304+
function _remove(Queue storage queue, bytes32 value_) private returns (bool) {
305+
uint256 length_ = _length(queue);
306+
307+
if (length_ == 0) {
308+
return false;
309+
}
310+
311+
uint256 indexToRemove_ = type(uint256).max;
312+
313+
for (uint256 i = 0; i < length_; ++i) {
314+
if (queue._values[i] == value_) {
315+
indexToRemove_ = i;
316+
break;
317+
}
318+
}
319+
320+
if (indexToRemove_ == type(uint256).max) {
321+
return false;
322+
}
323+
324+
if (indexToRemove_ == length_ - 1) {
325+
queue._values.pop();
326+
queue._priorities.pop();
327+
328+
return true;
329+
}
330+
331+
queue._values[indexToRemove_] = queue._values[length_ - 1];
332+
queue._priorities[indexToRemove_] = queue._priorities[length_ - 1];
333+
334+
queue._values.pop();
335+
queue._priorities.pop();
336+
337+
_shiftDown(queue, indexToRemove_);
338+
339+
return true;
340+
}
341+
276342
function _topValue(Queue storage queue) private view returns (bytes32) {
277343
_requireNotEmpty(queue);
278344

contracts/mock/libs/data-structures/PriorityQueueMock.sol

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ contract PriorityQueueMock {
2121
_uintQueue.removeTop();
2222
}
2323

24+
function removeUint(uint256 value_) external returns (bool) {
25+
return _uintQueue.remove(value_);
26+
}
27+
2428
function topValueUint() external view returns (uint256) {
2529
return _uintQueue.topValue();
2630
}
@@ -51,6 +55,10 @@ contract PriorityQueueMock {
5155
_bytes32Queue.removeTop();
5256
}
5357

58+
function removeBytes32(bytes32 value_) external returns (bool) {
59+
return _bytes32Queue.remove(value_);
60+
}
61+
5462
function topValueBytes32() external view returns (bytes32) {
5563
return _bytes32Queue.topValue();
5664
}
@@ -81,6 +89,10 @@ contract PriorityQueueMock {
8189
_addressQueue.removeTop();
8290
}
8391

92+
function removeAddress(address value_) external returns (bool) {
93+
return _addressQueue.remove(value_);
94+
}
95+
8496
function topValueAddress() external view returns (address) {
8597
return _addressQueue.topValue();
8698
}

test/libs/data-structures/PriorityQueue.test.ts

Lines changed: 150 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ describe("PriorityQueue", () => {
9696
});
9797
});
9898

99-
describe("remove()", async () => {
99+
describe("removeTop()", async () => {
100100
describe("uint", () => {
101101
it("should add and remove the top elements", async () => {
102102
await mock.addUint(1, 1);
@@ -190,4 +190,153 @@ describe("PriorityQueue", () => {
190190
});
191191
});
192192
});
193+
194+
describe("remove()", () => {
195+
describe("uint", () => {
196+
it("should remove a specific element from the middle", async () => {
197+
await mock.addUint(1, 1);
198+
await mock.addUint(2, 2);
199+
await mock.addUint(3, 3);
200+
await mock.addUint(4, 4);
201+
await mock.addUint(5, 5);
202+
203+
expect(await mock.lengthUint()).to.equal(5n);
204+
expect(await mock.topValueUint()).to.equal(5n);
205+
206+
// Remove element with value 3
207+
await mock.removeUint(3);
208+
209+
expect(await mock.lengthUint()).to.equal(4n);
210+
expect(await mock.topValueUint()).to.equal(5n);
211+
expect(await mock.valuesUint()).to.deep.equal([5n, 4n, 2n, 1n]);
212+
});
213+
214+
it("should remove the top element", async () => {
215+
await mock.addUint(1, 1);
216+
await mock.addUint(2, 2);
217+
await mock.addUint(3, 3);
218+
219+
// Remove the top element (value 3, priority 3)
220+
await mock.removeUint(3);
221+
222+
expect(await mock.lengthUint()).to.equal(2n);
223+
expect(await mock.topValueUint()).to.equal(2n);
224+
});
225+
226+
it("should remove the last element", async () => {
227+
await mock.addUint(1, 1);
228+
await mock.addUint(2, 2);
229+
await mock.addUint(3, 3);
230+
231+
// Remove element with value 1 (should be at the end)
232+
await mock.removeUint(1);
233+
234+
expect(await mock.lengthUint()).to.equal(2n);
235+
expect(await mock.topValueUint()).to.equal(3n);
236+
});
237+
238+
it("should remove the element with the same priority", async () => {
239+
await mock.addUint(1, ethers.MaxUint256);
240+
await mock.addUint(2, ethers.MaxUint256);
241+
await mock.addUint(3, ethers.MaxUint256);
242+
243+
await mock.removeUint(2);
244+
245+
expect(await mock.lengthUint()).to.equal(2n);
246+
expect(await mock.topValueUint()).to.equal(1n);
247+
248+
await mock.removeUint(3);
249+
250+
expect(await mock.lengthUint()).to.equal(1n);
251+
expect(await mock.topValueUint()).to.equal(1n);
252+
});
253+
254+
it("should return false when removing non-existent element", async () => {
255+
await mock.addUint(1, 1);
256+
await mock.addUint(2, 2);
257+
258+
// Check the return value using staticCall (view function call)
259+
expect(await mock.removeUint.staticCall(99)).to.equal(false);
260+
expect(await mock.lengthUint()).to.equal(2n);
261+
});
262+
263+
it("should return false when removing from empty queue", async () => {
264+
expect(await mock.removeUint.staticCall(1)).to.equal(false);
265+
expect(await mock.lengthUint()).to.equal(0n);
266+
});
267+
268+
it("should handle removing elements with same priority", async () => {
269+
await mock.addUint(1, 5);
270+
await mock.addUint(2, 5);
271+
await mock.addUint(3, 5);
272+
273+
await mock.removeUint(2);
274+
expect(await mock.lengthUint()).to.equal(2n);
275+
});
276+
277+
it("should handle removing all elements one by one", async () => {
278+
await mock.addUint(1, 1);
279+
await mock.addUint(2, 2);
280+
await mock.addUint(3, 3);
281+
282+
await mock.removeUint(2);
283+
expect(await mock.lengthUint()).to.equal(2n);
284+
285+
await mock.removeUint(1);
286+
expect(await mock.lengthUint()).to.equal(1n);
287+
288+
await mock.removeUint(3);
289+
expect(await mock.lengthUint()).to.equal(0n);
290+
});
291+
});
292+
293+
describe("bytes32", () => {
294+
it("should remove a specific bytes32 element", async () => {
295+
const val1 = ethers.keccak256("0x01");
296+
const val2 = ethers.keccak256("0x02");
297+
const val3 = ethers.keccak256("0x03");
298+
299+
await mock.addBytes32(val1, 1);
300+
await mock.addBytes32(val2, 2);
301+
await mock.addBytes32(val3, 3);
302+
303+
await mock.removeBytes32(val2);
304+
expect(await mock.lengthBytes32()).to.equal(2n);
305+
expect(await mock.topValueBytes32()).to.equal(val3);
306+
});
307+
308+
it("should return false when removing non-existent bytes32 element", async () => {
309+
const val1 = ethers.keccak256("0x01");
310+
const val2 = ethers.keccak256("0x02");
311+
312+
await mock.addBytes32(val1, 1);
313+
314+
expect(await mock.removeBytes32.staticCall(val2)).to.equal(false);
315+
expect(await mock.lengthBytes32()).to.equal(1n);
316+
});
317+
});
318+
319+
describe("address", () => {
320+
it("should remove a specific address element", async () => {
321+
const [FIRST, SECOND, THIRD] = await ethers.getSigners();
322+
323+
await mock.addAddress(FIRST.address, 1);
324+
await mock.addAddress(SECOND.address, 2);
325+
await mock.addAddress(THIRD.address, 3);
326+
327+
await mock.removeAddress(SECOND.address);
328+
expect(await mock.lengthAddress()).to.equal(2n);
329+
expect(await mock.topValueAddress()).to.equal(THIRD.address);
330+
});
331+
332+
it("should return false when removing non-existent address element", async () => {
333+
const [FIRST, SECOND] = await ethers.getSigners();
334+
335+
await mock.addAddress(FIRST.address, 1);
336+
337+
expect(await mock.removeAddress.staticCall(SECOND.address)).to.equal(false);
338+
expect(await mock.lengthAddress()).to.equal(1n);
339+
});
340+
});
341+
});
193342
});

0 commit comments

Comments
 (0)