Skip to content

Commit e277acc

Browse files
authored
Merge pull request RustPython#2902 from eldpswp99/dict-remain-order-after-delete
make dict remain order after delete
2 parents 0834ae3 + 564eb29 commit e277acc

File tree

4 files changed

+72
-40
lines changed

4 files changed

+72
-40
lines changed

Lib/test/test_collections.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1974,8 +1974,6 @@ def test_init(self):
19741974
self.assertRaises(TypeError, Counter, (), ())
19751975
self.assertRaises(TypeError, Counter.__init__)
19761976

1977-
# TODO: RUSTPYTHON
1978-
@unittest.expectedFailure
19791977
def test_order_preservation(self):
19801978
# Input order dictates items() order
19811979
self.assertEqual(list(Counter('abracadabra').items()),

Lib/test/test_dict.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -474,8 +474,6 @@ def test_mutating_iteration(self):
474474
for i in d:
475475
d[i+1] = 1
476476

477-
# TODO: RUSTPYTHON
478-
@unittest.expectedFailure
479477
def test_mutating_iteration_delete(self):
480478
# change dict content during iteration
481479
d = {}
@@ -485,8 +483,6 @@ def test_mutating_iteration_delete(self):
485483
del d[0]
486484
d[0] = 0
487485

488-
# TODO: RUSTPYTHON
489-
@unittest.expectedFailure
490486
def test_mutating_iteration_delete_over_values(self):
491487
# change dict content during iteration
492488
d = {}
@@ -496,8 +492,6 @@ def test_mutating_iteration_delete_over_values(self):
496492
del d[0]
497493
d[0] = 0
498494

499-
# TODO: RUSTPYTHON
500-
@unittest.expectedFailure
501495
def test_mutating_iteration_delete_over_items(self):
502496
# change dict content during iteration
503497
d = {}

Lib/test/test_ordered_dict.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -699,7 +699,6 @@ class CPythonBuiltinDictTests(unittest.TestCase):
699699
# TODO: RUSTPYTHON
700700
import functools
701701
setattr(CPythonBuiltinDictTests, "test_delitem", functools.partialmethod(OrderedDictTests.test_delitem))
702-
CPythonBuiltinDictTests.test_delitem = unittest.expectedFailure(CPythonBuiltinDictTests.test_delitem)
703702

704703
@unittest.skipUnless(c_coll, 'requires the C version of the collections module')
705704
class CPythonOrderedDictTests(OrderedDictTests, unittest.TestCase):

vm/src/dictdatatype.rs

Lines changed: 72 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ type EntryIndex = usize;
2525
pub struct Dict<T = PyObjectRef> {
2626
inner: PyRwLock<DictInner<T>>,
2727
}
28+
2829
impl<T> fmt::Debug for Dict<T> {
2930
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3031
f.debug_struct("Debug").finish()
@@ -37,10 +38,12 @@ enum IndexEntry {
3738
Free,
3839
Index(usize),
3940
}
41+
4042
impl IndexEntry {
4143
const FREE: i64 = -1;
4244
const DUMMY: i64 = -2;
4345
}
46+
4447
impl From<i64> for IndexEntry {
4548
fn from(idx: i64) -> Self {
4649
match idx {
@@ -50,6 +53,7 @@ impl From<i64> for IndexEntry {
5053
}
5154
}
5255
}
56+
5357
impl From<IndexEntry> for i64 {
5458
fn from(idx: IndexEntry) -> Self {
5559
match idx {
@@ -65,7 +69,9 @@ struct DictInner<T> {
6569
used: usize,
6670
filled: usize,
6771
indices: Vec<i64>,
68-
entries: Vec<DictEntry<T>>,
72+
entries: Vec<Option<DictEntry<T>>>,
73+
// index to new inserted element should be in entries
74+
next_new_entry_idx: usize,
6975
}
7076

7177
impl<T: Clone> Clone for Dict<T> {
@@ -84,6 +90,7 @@ impl<T> Default for Dict<T> {
8490
filled: 0,
8591
indices: vec![IndexEntry::FREE; 8],
8692
entries: Vec::new(),
93+
next_new_entry_idx: 0,
8794
}),
8895
}
8996
}
@@ -143,18 +150,23 @@ impl<T> DictInner<T> {
143150
self.indices = vec![IndexEntry::FREE; new_size];
144151
let mask = (new_size - 1) as i64;
145152
for (entry_idx, entry) in self.entries.iter_mut().enumerate() {
146-
let mut idxs = GenIndexes::new(entry.hash, mask);
147-
loop {
148-
let index_index = idxs.next();
149-
let idx = &mut self.indices[index_index];
150-
if *idx == IndexEntry::FREE {
151-
*idx = entry_idx as i64;
152-
entry.index = index_index;
153-
break;
153+
if let Some(entry) = entry {
154+
let mut idxs = GenIndexes::new(entry.hash, mask);
155+
loop {
156+
let index_index = idxs.next();
157+
let idx = &mut self.indices[index_index];
158+
if *idx == IndexEntry::FREE {
159+
*idx = entry_idx as i64;
160+
entry.index = index_index;
161+
break;
162+
}
154163
}
164+
} else {
165+
//removed entry
155166
}
156167
}
157168
self.filled = self.used;
169+
self.next_new_entry_idx = self.entries.len();
158170
}
159171

160172
fn unchecked_push(
@@ -171,10 +183,15 @@ impl<T> DictInner<T> {
171183
value,
172184
index,
173185
};
174-
let entry_index = self.entries.len();
175-
self.entries.push(entry);
186+
let entry_index = self.next_new_entry_idx;
187+
if self.entries.len() == entry_index {
188+
self.entries.push(Some(entry));
189+
} else {
190+
self.entries[entry_index] = Some(entry);
191+
}
176192
self.indices[index] = entry_index as i64;
177193
self.used += 1;
194+
self.next_new_entry_idx += 1;
178195
if let IndexEntry::Free = index_entry {
179196
self.filled += 1;
180197
if let Some(new_size) = self.should_resize() {
@@ -223,6 +240,9 @@ impl<T: Clone> Dict<T> {
223240
let mut inner = self.write();
224241
// Update existing key
225242
if let Some(entry) = inner.entries.get_mut(index) {
243+
let entry = entry
244+
.as_mut()
245+
.expect("The dict was changed since we did lookup.");
226246
if entry.index == index_index {
227247
let removed = std::mem::replace(&mut entry.value, value);
228248
// defer dec RC
@@ -266,6 +286,7 @@ impl<T: Clone> Dict<T> {
266286
if let IndexEntry::Index(index) = entry {
267287
let inner = self.read();
268288
if let Some(entry) = inner.entries.get(index) {
289+
let entry = extract_dict_entry(entry);
269290
if entry.index == index_index {
270291
break Some(entry.value.clone());
271292
} else {
@@ -303,6 +324,7 @@ impl<T: Clone> Dict<T> {
303324
inner.indices.resize(8, IndexEntry::FREE);
304325
inner.used = 0;
305326
inner.filled = 0;
327+
inner.next_new_entry_idx = 0;
306328
// defer dec rc
307329
std::mem::take(&mut inner.entries)
308330
};
@@ -377,6 +399,7 @@ impl<T: Clone> Dict<T> {
377399
if let IndexEntry::Index(index) = entry {
378400
let inner = self.read();
379401
if let Some(entry) = inner.entries.get(index) {
402+
let entry = extract_dict_entry(entry);
380403
if entry.index == index_index {
381404
break entry.value.clone();
382405
} else {
@@ -419,6 +442,7 @@ impl<T: Clone> Dict<T> {
419442
if let IndexEntry::Index(index) = entry {
420443
let inner = self.read();
421444
if let Some(entry) = inner.entries.get(index) {
445+
let entry = extract_dict_entry(entry);
422446
if entry.index == index_index {
423447
break (entry.key.clone(), entry.value.clone());
424448
} else {
@@ -453,10 +477,14 @@ impl<T: Clone> Dict<T> {
453477
}
454478

455479
pub fn next_entry(&self, position: &mut EntryIndex) -> Option<(PyObjectRef, T)> {
456-
self.read().entries.get(*position).map(|entry| {
480+
let inner = self.read();
481+
loop {
482+
let entry = inner.entries.get(*position)?;
457483
*position += 1;
458-
(entry.key.clone(), entry.value.clone())
459-
})
484+
if let Some(entry) = entry {
485+
break Some((entry.key.clone(), entry.value.clone()));
486+
}
487+
}
460488
}
461489

462490
pub fn len_from_entry_index(&self, position: EntryIndex) -> usize {
@@ -469,7 +497,11 @@ impl<T: Clone> Dict<T> {
469497
}
470498

471499
pub fn keys(&self) -> Vec<PyObjectRef> {
472-
self.read().entries.iter().map(|v| v.key.clone()).collect()
500+
self.read()
501+
.entries
502+
.iter()
503+
.filter_map(|v| v.as_ref().map(|v| v.key.clone()))
504+
.collect()
473505
}
474506

475507
/// Lookup the index for the given key.
@@ -505,7 +537,7 @@ impl<T: Clone> Dict<T> {
505537
return Ok(idxs);
506538
}
507539
IndexEntry::Index(i) => {
508-
let entry = &inner.entries[i];
540+
let entry = &inner.entries[i].as_ref().unwrap();
509541
let ret = (IndexEntry::Index(i), index_index);
510542
if key.key_is(&entry.key) {
511543
break 'outer ret;
@@ -540,23 +572,17 @@ impl<T: Clone> Dict<T> {
540572
return Ok(None);
541573
};
542574
let mut inner = self.write();
543-
if matches!(inner.entries.get(entry_index), Some(entry) if entry.index == index_index) {
575+
if matches!(inner.entries.get(entry_index), Some(Some(entry)) if entry.index == index_index)
576+
{
544577
// all good
545578
} else {
546579
// The dict was changed since we did lookup. Let's try again.
547580
return Err(());
548581
};
549582
inner.indices[index_index] = IndexEntry::DUMMY;
550583
inner.used -= 1;
551-
let removed = if entry_index == inner.used {
552-
inner.entries.pop().unwrap()
553-
} else {
554-
let last_index = inner.entries.last().unwrap().index;
555-
let removed = inner.entries.swap_remove(entry_index);
556-
inner.indices[last_index] = entry_index as i64;
557-
removed
558-
};
559-
Ok(Some(removed))
584+
let removed = std::mem::take(&mut inner.entries[entry_index]);
585+
Ok(removed)
560586
}
561587

562588
/// Retrieve and delete a key
@@ -575,11 +601,19 @@ impl<T: Clone> Dict<T> {
575601

576602
pub fn pop_back(&self) -> Option<(PyObjectRef, T)> {
577603
let mut inner = self.write();
578-
inner.entries.pop().map(|entry| {
579-
inner.used -= 1;
580-
inner.indices[entry.index] = IndexEntry::DUMMY;
581-
(entry.key, entry.value)
582-
})
604+
let mut idx = inner.next_new_entry_idx.checked_sub(1)?;
605+
while inner.entries.get(idx).unwrap().is_none() {
606+
idx = idx.checked_sub(1)?;
607+
}
608+
609+
let entry = std::mem::take(&mut inner.entries[idx]).unwrap();
610+
let removed_key = entry.key;
611+
let removed_item = entry.value;
612+
613+
inner.used -= 1;
614+
inner.indices[entry.index] = IndexEntry::DUMMY;
615+
inner.next_new_entry_idx = idx;
616+
Some((removed_key, removed_item))
583617
}
584618

585619
pub fn sizeof(&self) -> usize {
@@ -638,6 +672,7 @@ impl DictKey for PyStrRef {
638672
}
639673
}
640674
}
675+
641676
impl DictKey for PyRefExact<PyStr> {
642677
fn key_hash(&self, vm: &VirtualMachine) -> PyResult<HashValue> {
643678
(**self).key_hash(vm)
@@ -699,6 +734,12 @@ fn str_exact<'a>(obj: &'a PyObjectRef, vm: &VirtualMachine) -> Option<&'a PyStr>
699734
}
700735
}
701736

737+
fn extract_dict_entry<T>(option_entry: &Option<DictEntry<T>>) -> &DictEntry<T> {
738+
option_entry
739+
.as_ref()
740+
.expect("The dict was changed since we did lookup.")
741+
}
742+
702743
#[cfg(test)]
703744
mod tests {
704745
use super::{Dict, DictKey};

0 commit comments

Comments
 (0)