Skip to content

Commit 300e053

Browse files
committed
py/objdeque: Expand implementation to be doubly-ended.
Add `pop()`, `appendleft()`, and `extend()` methods, support iteration and indexing, and initializing from an existing sequence. Added tests for checking new behavior.
1 parent de1f1dd commit 300e053

File tree

5 files changed

+299
-24
lines changed

5 files changed

+299
-24
lines changed

py/objdeque.c

Lines changed: 149 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@
3232

3333
#include "py/runtime.h"
3434

35+
STATIC mp_obj_t mp_obj_new_deque_iterator(mp_obj_t deque, size_t cur, mp_obj_iter_buf_t *iter_buf);
36+
STATIC mp_obj_t mp_obj_deque_append(mp_obj_t self_in, mp_obj_t arg);
37+
38+
3539
typedef struct _mp_obj_deque_t {
3640
mp_obj_base_t base;
3741
size_t alloc;
@@ -42,15 +46,18 @@ typedef struct _mp_obj_deque_t {
4246
#define FLAG_CHECK_OVERFLOW 1
4347
} mp_obj_deque_t;
4448

49+
STATIC mp_obj_t deque_extend_from_iter(mp_obj_t deque, mp_obj_t iterable) {
50+
mp_obj_t iter = mp_getiter(iterable, NULL);
51+
mp_obj_t item;
52+
while ((item = mp_iternext(iter)) != MP_OBJ_STOP_ITERATION) {
53+
mp_obj_deque_append(deque, item);
54+
}
55+
return deque;
56+
}
57+
4558
STATIC mp_obj_t deque_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) {
4659
mp_arg_check_num(n_args, n_kw, 2, 3, false);
4760

48-
/* Initialization from existing sequence is not supported, so an empty
49-
tuple must be passed as such. */
50-
if (args[0] != mp_const_empty_tuple) {
51-
mp_raise_ValueError(NULL);
52-
}
53-
5461
// Protect against -1 leading to zero-length allocation and bad array access
5562
mp_int_t maxlen = mp_obj_get_int(args[1]);
5663
if (maxlen < 0) {
@@ -66,21 +73,27 @@ STATIC mp_obj_t deque_make_new(const mp_obj_type_t *type, size_t n_args, size_t
6673
o->flags = mp_obj_get_int(args[2]);
6774
}
6875

76+
deque_extend_from_iter(MP_OBJ_FROM_PTR(o), args[0]);
77+
6978
return MP_OBJ_FROM_PTR(o);
7079
}
7180

81+
STATIC size_t deque_len(mp_obj_deque_t *self) {
82+
ssize_t len = self->i_put - self->i_get;
83+
if (len < 0) {
84+
len += self->alloc;
85+
}
86+
return len;
87+
}
88+
7289
STATIC mp_obj_t deque_unary_op(mp_unary_op_t op, mp_obj_t self_in) {
7390
mp_obj_deque_t *self = MP_OBJ_TO_PTR(self_in);
7491
switch (op) {
7592
case MP_UNARY_OP_BOOL:
7693
return mp_obj_new_bool(self->i_get != self->i_put);
77-
case MP_UNARY_OP_LEN: {
78-
ssize_t len = self->i_put - self->i_get;
79-
if (len < 0) {
80-
len += self->alloc;
81-
}
82-
return MP_OBJ_NEW_SMALL_INT(len);
83-
}
94+
case MP_UNARY_OP_LEN:
95+
return MP_OBJ_NEW_SMALL_INT(deque_len(self));
96+
8497
#if MICROPY_PY_SYS_GETSIZEOF
8598
case MP_UNARY_OP_SIZEOF: {
8699
size_t sz = sizeof(*self) + sizeof(mp_obj_t) * self->alloc;
@@ -117,6 +130,41 @@ STATIC mp_obj_t mp_obj_deque_append(mp_obj_t self_in, mp_obj_t arg) {
117130
}
118131
STATIC MP_DEFINE_CONST_FUN_OBJ_2(deque_append_obj, mp_obj_deque_append);
119132

133+
STATIC mp_obj_t mp_obj_deque_appendleft(mp_obj_t self_in, mp_obj_t arg) {
134+
mp_obj_deque_t *self = MP_OBJ_TO_PTR(self_in);
135+
136+
size_t new_i_get = self->i_get - 1;
137+
if (self->i_get == 0) {
138+
new_i_get = self->alloc - 1;
139+
}
140+
141+
if (self->flags & FLAG_CHECK_OVERFLOW && new_i_get == self->i_put) {
142+
mp_raise_msg(&mp_type_IndexError, MP_ERROR_TEXT("full"));
143+
}
144+
145+
self->i_get = new_i_get;
146+
self->items[self->i_get] = arg;
147+
148+
// overwriting first element in deque
149+
if (self->i_put == new_i_get) {
150+
if (self->i_put == 0) {
151+
self->i_put = self->alloc - 1;
152+
} else {
153+
self->i_put--;
154+
}
155+
}
156+
157+
return mp_const_none;
158+
}
159+
STATIC MP_DEFINE_CONST_FUN_OBJ_2(deque_appendleft_obj, mp_obj_deque_appendleft);
160+
161+
STATIC mp_obj_t deque_extend(mp_obj_t self_in, mp_obj_t arg_in) {
162+
deque_extend_from_iter(self_in, arg_in);
163+
return mp_const_none;
164+
}
165+
STATIC MP_DEFINE_CONST_FUN_OBJ_2(deque_extend_obj, deque_extend);
166+
167+
120168
STATIC mp_obj_t deque_popleft(mp_obj_t self_in) {
121169
mp_obj_deque_t *self = MP_OBJ_TO_PTR(self_in);
122170

@@ -135,6 +183,54 @@ STATIC mp_obj_t deque_popleft(mp_obj_t self_in) {
135183
}
136184
STATIC MP_DEFINE_CONST_FUN_OBJ_1(deque_popleft_obj, deque_popleft);
137185

186+
STATIC mp_obj_t deque_pop(mp_obj_t self_in) {
187+
mp_obj_deque_t *self = MP_OBJ_TO_PTR(self_in);
188+
189+
if (self->i_get == self->i_put) {
190+
mp_raise_msg(&mp_type_IndexError, MP_ERROR_TEXT("empty"));
191+
}
192+
193+
if (self->i_put == 0) {
194+
self->i_put = self->alloc - 1;
195+
} else {
196+
self->i_put--;
197+
}
198+
199+
mp_obj_t ret = self->items[self->i_put];
200+
self->items[self->i_put] = MP_OBJ_NULL;
201+
202+
return ret;
203+
}
204+
STATIC MP_DEFINE_CONST_FUN_OBJ_1(deque_pop_obj, deque_pop);
205+
206+
STATIC mp_obj_t deque_getiter(mp_obj_t o_in, mp_obj_iter_buf_t *iter_buf) {
207+
mp_obj_deque_t *deque = MP_OBJ_TO_PTR(o_in);
208+
return mp_obj_new_deque_iterator(o_in, deque->i_get, iter_buf);
209+
}
210+
211+
STATIC mp_obj_t deque_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) {
212+
if (value == MP_OBJ_NULL) {
213+
// delete not supported, fall back to mp_obj_subscr() error message
214+
return MP_OBJ_NULL;
215+
}
216+
mp_obj_deque_t *self = MP_OBJ_TO_PTR(self_in);
217+
218+
size_t offset = mp_get_index(self->base.type, deque_len(self), index, false);
219+
size_t index_val = self->i_get + offset;
220+
if (index_val > self->alloc) {
221+
index_val -= self->alloc;
222+
}
223+
224+
if (value == MP_OBJ_SENTINEL) {
225+
// load
226+
return self->items[index_val];
227+
} else {
228+
// store into deque
229+
self->items[index_val] = value;
230+
return mp_const_none;
231+
}
232+
}
233+
138234
#if 0
139235
STATIC mp_obj_t deque_clear(mp_obj_t self_in) {
140236
mp_obj_deque_t *self = MP_OBJ_TO_PTR(self_in);
@@ -147,9 +243,12 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(deque_clear_obj, deque_clear);
147243

148244
STATIC const mp_rom_map_elem_t deque_locals_dict_table[] = {
149245
{ MP_ROM_QSTR(MP_QSTR_append), MP_ROM_PTR(&deque_append_obj) },
246+
{ MP_ROM_QSTR(MP_QSTR_appendleft), MP_ROM_PTR(&deque_appendleft_obj) },
247+
{ MP_ROM_QSTR(MP_QSTR_extend), MP_ROM_PTR(&deque_extend_obj) },
150248
#if 0
151249
{ MP_ROM_QSTR(MP_QSTR_clear), MP_ROM_PTR(&deque_clear_obj) },
152250
#endif
251+
{ MP_ROM_QSTR(MP_QSTR_pop), MP_ROM_PTR(&deque_pop_obj) },
153252
{ MP_ROM_QSTR(MP_QSTR_popleft), MP_ROM_PTR(&deque_popleft_obj) },
154253
};
155254

@@ -158,10 +257,46 @@ STATIC MP_DEFINE_CONST_DICT(deque_locals_dict, deque_locals_dict_table);
158257
MP_DEFINE_CONST_OBJ_TYPE(
159258
mp_type_deque,
160259
MP_QSTR_deque,
161-
MP_TYPE_FLAG_NONE,
260+
MP_TYPE_FLAG_ITER_IS_GETITER,
162261
make_new, deque_make_new,
163262
unary_op, deque_unary_op,
263+
subscr, deque_subscr,
264+
iter, deque_getiter,
164265
locals_dict, &deque_locals_dict
165266
);
166267

268+
/******************************************************************************/
269+
/* deque iterator */
270+
typedef struct _mp_obj_deque_it_t {
271+
mp_obj_base_t base;
272+
mp_fun_1_t iternext;
273+
mp_obj_t deque;
274+
size_t cur;
275+
} mp_obj_deque_it_t;
276+
277+
STATIC mp_obj_t deque_it_iternext(mp_obj_t self_in) {
278+
mp_obj_deque_it_t *self = MP_OBJ_TO_PTR(self_in);
279+
mp_obj_deque_t *deque = MP_OBJ_TO_PTR(self->deque);
280+
if (self->cur != deque->i_put) {
281+
mp_obj_t o_out = deque->items[self->cur];
282+
283+
if (++self->cur == deque->alloc) {
284+
self->cur = 0;
285+
}
286+
return o_out;
287+
} else {
288+
return MP_OBJ_STOP_ITERATION;
289+
}
290+
}
291+
292+
mp_obj_t mp_obj_new_deque_iterator(mp_obj_t deque, size_t i_get, mp_obj_iter_buf_t *iter_buf) {
293+
assert(sizeof(mp_obj_deque_it_t) <= sizeof(mp_obj_iter_buf_t));
294+
mp_obj_deque_it_t *o = (mp_obj_deque_it_t *)iter_buf;
295+
o->base.type = &mp_type_polymorph_iter;
296+
o->iternext = deque_it_iternext;
297+
o->deque = deque;
298+
o->cur = i_get;
299+
return MP_OBJ_FROM_PTR(o);
300+
}
301+
167302
#endif // MICROPY_PY_COLLECTIONS_DEQUE

tests/basics/deque1.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,52 @@
6666
~d
6767
except TypeError:
6868
print("TypeError")
69+
70+
71+
# Same tests, but now with pop() and appendleft()
72+
73+
d = deque((), 2)
74+
print(len(d))
75+
print(bool(d))
76+
77+
try:
78+
d.popleft()
79+
except IndexError:
80+
print("IndexError")
81+
82+
print(d.append(1))
83+
print(len(d))
84+
print(bool(d))
85+
print(d.popleft())
86+
print(len(d))
87+
88+
d.append(2)
89+
print(d.popleft())
90+
91+
d.append(3)
92+
d.append(4)
93+
print(len(d))
94+
print(d.popleft(), d.popleft())
95+
try:
96+
d.popleft()
97+
except IndexError:
98+
print("IndexError")
99+
100+
d.append(5)
101+
d.append(6)
102+
d.append(7)
103+
print(len(d))
104+
print(d.popleft(), d.popleft())
105+
print(len(d))
106+
try:
107+
d.popleft()
108+
except IndexError:
109+
print("IndexError")
110+
111+
d = deque((), 2)
112+
d.appendleft(1)
113+
d.appendleft(2)
114+
d.appendleft(3)
115+
d.appendleft(4)
116+
d.appendleft(5)
117+
print(d.pop(), d.pop())

tests/basics/deque2.py

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,50 @@
99
print("SKIP")
1010
raise SystemExit
1111

12+
# Initial sequence is supported
13+
d = deque([1, 2, 3], 10)
14+
15+
# iteration over sequence
16+
for x in d:
17+
print(x)
18+
19+
# Iterables larger than maxlen have the beginnings removed, also tests
20+
# iteration through conversion to list
21+
d = deque([1, 2, 3, 4, 5], 3)
22+
print(list(d))
23+
24+
# Empty iterables are also supported
25+
deque([], 10)
26+
27+
# Extending deques with iterables
28+
d.extend([6, 7])
29+
print(list(d))
30+
31+
# Accessing queue elements via index
32+
d = deque((0, 1, 2, 3), 5)
33+
print(d[0], d[1], d[-1])
34+
35+
# Writing queue elements via index
36+
d[3] = 5
37+
print(d[3])
38+
39+
# Accessing indices out of bounds raises IndexError
40+
try:
41+
d[4]
42+
except IndexError:
43+
print("IndexError")
1244

13-
# Initial sequence is not supported
1445
try:
15-
deque([1, 2, 3], 10)
16-
except ValueError:
17-
print("ValueError")
46+
d[4] = 0
47+
except IndexError:
48+
print("IndexError")
1849

19-
# Not even empty list, only empty tuple
50+
# Removing elements with del is not supported, fall back on mp_obj_subscr() error message
2051
try:
21-
deque([], 10)
22-
except ValueError:
23-
print("ValueError")
52+
del d[0]
53+
except TypeError:
54+
print("TypeError")
55+
2456

2557
# Only fixed-size deques are supported, so length arg is mandatory
2658
try:
@@ -35,6 +67,11 @@
3567
except IndexError:
3668
print("IndexError")
3769

70+
try:
71+
d.pop()
72+
except IndexError:
73+
print("IndexError")
74+
3875
print(d.append(1))
3976
print(d.popleft())
4077

@@ -49,13 +86,24 @@
4986
except IndexError as e:
5087
print(repr(e))
5188

89+
try:
90+
d.pop()
91+
except IndexError as e:
92+
print(repr(e))
93+
5294
d.append(5)
5395
d.append(6)
5496
print(len(d))
5597
try:
5698
d.append(7)
5799
except IndexError as e:
58100
print(repr(e))
101+
102+
try:
103+
d.appendleft(8)
104+
except IndexError as e:
105+
print(repr(e))
106+
59107
print(len(d))
60108

61109
print(d.popleft(), d.popleft())

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy