From 46db0366b85b61ac103d3db0f18abb55ae8313aa Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Fri, 4 Aug 2023 09:25:52 -0500 Subject: [PATCH] OrderedDict.move_to_end: alternate implementation of #8234 this implementation is hoped to be smaller. (feather_m4_express/fr fits unlike the other PR; approximate savings ~600 bytes) Minor difference to standard Python: A `dict` object has a `move_to_end` method. However, calling this method always results in TypeError. Implementing it this way means that the method table can still be shared between OrderedDict and builtin dict. Closes #4408. --- py/objdict.c | 50 ++++++++++++++++++++++++++++++++++++ tests/basics/ordereddict2.py | 44 +++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 tests/basics/ordereddict2.py diff --git a/py/objdict.c b/py/objdict.c index b63ea89137..abe72e7839 100644 --- a/py/objdict.c +++ b/py/objdict.c @@ -421,6 +421,53 @@ STATIC mp_obj_t dict_update(size_t n_args, const mp_obj_t *args, mp_map_t *kwarg } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(dict_update_obj, 1, dict_update); +#if MICROPY_PY_COLLECTIONS_ORDEREDDICT +STATIC mp_obj_t dict_move_to_end(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + mp_obj_dict_t *self = MP_OBJ_TO_PTR(pos_args[0]); + mp_arg_validate_type(self, &mp_type_ordereddict, MP_QSTR_self); + + // parse args + enum { ARG_key, ARG_last }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_key, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_last, MP_ARG_BOOL, {.u_bool = true } } + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + mp_obj_t *key = args[ARG_key].u_obj; + bool last = args[ARG_last].u_bool; + + mp_map_elem_t *elem = mp_map_lookup(&self->map, key, MP_MAP_LOOKUP); + if (!elem) { + mp_raise_type_arg(&mp_type_KeyError, key); + } + + mp_map_elem_t tmp = *elem; + mp_map_elem_t *table = self->map.table; + mp_map_elem_t *dest, *move_begin, *move_dest; + size_t move_count; + + if (last) { + mp_map_elem_t *top = &table[self->map.used]; + dest = top - 1; + move_begin = elem + 1; + move_dest = elem; + move_count = top - move_begin; + } else { + dest = &table[0]; + move_begin = table; + move_dest = table + 1; + move_count = elem - table; + } + memmove(move_dest, move_begin, move_count * sizeof(*elem)); + *dest = tmp; + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(dict_move_to_end_obj, 1, dict_move_to_end); +#endif + /******************************************************************************/ /* dict views */ @@ -590,6 +637,9 @@ STATIC const mp_rom_map_elem_t dict_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_get), MP_ROM_PTR(&dict_get_obj) }, { MP_ROM_QSTR(MP_QSTR_items), MP_ROM_PTR(&dict_items_obj) }, { MP_ROM_QSTR(MP_QSTR_keys), MP_ROM_PTR(&dict_keys_obj) }, + #if MICROPY_PY_COLLECTIONS_ORDEREDDICT + { MP_ROM_QSTR(MP_QSTR_move_to_end), MP_ROM_PTR(&dict_move_to_end_obj) }, + #endif { MP_ROM_QSTR(MP_QSTR_pop), MP_ROM_PTR(&dict_pop_obj) }, { MP_ROM_QSTR(MP_QSTR_popitem), MP_ROM_PTR(&dict_popitem_obj) }, { MP_ROM_QSTR(MP_QSTR_setdefault), MP_ROM_PTR(&dict_setdefault_obj) }, diff --git a/tests/basics/ordereddict2.py b/tests/basics/ordereddict2.py new file mode 100644 index 0000000000..f85b08fea5 --- /dev/null +++ b/tests/basics/ordereddict2.py @@ -0,0 +1,44 @@ +try: + from collections import OrderedDict +except ImportError: + print("SKIP") + raise SystemExit + +try: + {'a': None}.move_to_end('a') +except (TypeError, AttributeError): + print("Exception") + +d = OrderedDict(a=1, b=2, c=3) +d.move_to_end('a') +print(list(d.items())) + +d = OrderedDict(a=1, b=2, c=3) +d.move_to_end('b') +print(list(d.items())) + +d = OrderedDict(a=1, b=2, c=3) +d.move_to_end('c') +print(list(d.items())) + +try: + d.move_to_end('x') +except KeyError: + print("KeyError") + +d = OrderedDict(a=1, b=2, c=3) +d.move_to_end('a', last=False) +print(list(d.items())) + +d = OrderedDict(a=1, b=2, c=3) +d.move_to_end('b', last=False) +print(list(d.items())) + +d = OrderedDict(a=1, b=2, c=3) +d.move_to_end('c', last=False) +print(list(d.items())) + +try: + d.move_to_end('x', last=False) +except KeyError: + print("KeyError")