Skip to content

Commit f944588

Browse files
authored
Add reverse map iteration (#596)
* Add reverse map iteration
1 parent 63ca3fe commit f944588

File tree

7 files changed

+166
-25
lines changed

7 files changed

+166
-25
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ versions.
1212

1313
### Added
1414

15+
- Map iterators are now [DoubleEndedIterators](https://doc.rust-lang.org/std/iter/trait.DoubleEndedIterator.html)
16+
(#598), thus allowing being iterated in reverse using `.rev()`
17+
1518
### Fixed
1619

1720
### Changed

rustler/src/types/map.rs

Lines changed: 118 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -193,40 +193,140 @@ impl<'a> Term<'a> {
193193
}
194194
}
195195

196-
pub struct MapIterator<'a> {
197-
env: Env<'a>,
198-
iter: map::ErlNifMapIterator,
196+
struct SimpleMapIterator<'a> {
197+
map: Term<'a>,
198+
entry: map::MapIteratorEntry,
199+
iter: Option<map::ErlNifMapIterator>,
200+
last_key: Option<Term<'a>>,
201+
done: bool,
199202
}
200203

201-
impl<'a> MapIterator<'a> {
202-
pub fn new(map: Term<'a>) -> Option<MapIterator<'a>> {
203-
let env = map.get_env();
204-
unsafe { map::map_iterator_create(env.as_c_arg(), map.as_c_arg()) }
205-
.map(|iter| MapIterator { env, iter })
204+
impl<'a> SimpleMapIterator<'a> {
205+
fn next(&mut self) -> Option<(Term<'a>, Term<'a>)> {
206+
if self.done {
207+
return None;
208+
}
209+
210+
let iter = loop {
211+
match self.iter.as_mut() {
212+
None => {
213+
match unsafe {
214+
map::map_iterator_create(
215+
self.map.get_env().as_c_arg(),
216+
self.map.as_c_arg(),
217+
self.entry,
218+
)
219+
} {
220+
Some(iter) => {
221+
self.iter = Some(iter);
222+
continue;
223+
}
224+
None => {
225+
self.done = true;
226+
return None;
227+
}
228+
}
229+
}
230+
Some(iter) => {
231+
break iter;
232+
}
233+
}
234+
};
235+
236+
let env = self.map.get_env();
237+
238+
unsafe {
239+
match map::map_iterator_get_pair(env.as_c_arg(), iter) {
240+
Some((key, value)) => {
241+
match self.entry {
242+
map::MapIteratorEntry::First => {
243+
map::map_iterator_next(env.as_c_arg(), iter);
244+
}
245+
map::MapIteratorEntry::Last => {
246+
map::map_iterator_prev(env.as_c_arg(), iter);
247+
}
248+
}
249+
let key = Term::new(env, key);
250+
self.last_key = Some(key);
251+
Some((key, Term::new(env, value)))
252+
}
253+
None => {
254+
self.done = true;
255+
None
256+
}
257+
}
258+
}
206259
}
207260
}
208261

209-
impl<'a> Drop for MapIterator<'a> {
262+
impl<'a> Drop for SimpleMapIterator<'a> {
210263
fn drop(&mut self) {
211-
unsafe {
212-
map::map_iterator_destroy(self.env.as_c_arg(), &mut self.iter);
264+
if let Some(iter) = self.iter.as_mut() {
265+
unsafe {
266+
map::map_iterator_destroy(self.map.get_env().as_c_arg(), iter);
267+
}
213268
}
214269
}
215270
}
216271

217-
impl<'a> Iterator for MapIterator<'a> {
218-
type Item = (Term<'a>, Term<'a>);
272+
pub struct MapIterator<'a> {
273+
forward: SimpleMapIterator<'a>,
274+
reverse: SimpleMapIterator<'a>,
275+
}
219276

220-
fn next(&mut self) -> Option<(Term<'a>, Term<'a>)> {
221-
unsafe {
222-
map::map_iterator_get_pair(self.env.as_c_arg(), &mut self.iter).map(|(key, value)| {
223-
map::map_iterator_next(self.env.as_c_arg(), &mut self.iter);
224-
(Term::new(self.env, key), Term::new(self.env, value))
277+
impl<'a> MapIterator<'a> {
278+
pub fn new(map: Term<'a>) -> Option<MapIterator<'a>> {
279+
if map.is_map() {
280+
Some(MapIterator {
281+
forward: SimpleMapIterator {
282+
map,
283+
entry: map::MapIteratorEntry::First,
284+
iter: None,
285+
last_key: None,
286+
done: false,
287+
},
288+
reverse: SimpleMapIterator {
289+
map,
290+
entry: map::MapIteratorEntry::Last,
291+
iter: None,
292+
last_key: None,
293+
done: false,
294+
},
225295
})
296+
} else {
297+
None
226298
}
227299
}
228300
}
229301

302+
impl<'a> Iterator for MapIterator<'a> {
303+
type Item = (Term<'a>, Term<'a>);
304+
305+
fn next(&mut self) -> Option<Self::Item> {
306+
self.forward.next().and_then(|(key, value)| {
307+
if self.reverse.last_key == Some(key) {
308+
self.forward.done = true;
309+
self.reverse.done = true;
310+
return None;
311+
}
312+
Some((key, value))
313+
})
314+
}
315+
}
316+
317+
impl<'a> DoubleEndedIterator for MapIterator<'a> {
318+
fn next_back(&mut self) -> Option<Self::Item> {
319+
self.reverse.next().and_then(|(key, value)| {
320+
if self.forward.last_key == Some(key) {
321+
self.forward.done = true;
322+
self.reverse.done = true;
323+
return None;
324+
}
325+
Some((key, value))
326+
})
327+
}
328+
}
329+
230330
impl<'a> Decoder<'a> for MapIterator<'a> {
231331
fn decode(term: Term<'a>) -> NifResult<Self> {
232332
match MapIterator::new(term) {

rustler/src/wrapper/map.rs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,26 @@ pub unsafe fn map_update(
6666
Some(result.assume_init())
6767
}
6868

69-
pub unsafe fn map_iterator_create(env: NIF_ENV, map: NIF_TERM) -> Option<ErlNifMapIterator> {
69+
#[derive(Clone, Copy, Debug)]
70+
pub enum MapIteratorEntry {
71+
First,
72+
Last,
73+
}
74+
75+
pub unsafe fn map_iterator_create(
76+
env: NIF_ENV,
77+
map: NIF_TERM,
78+
entry: MapIteratorEntry,
79+
) -> Option<ErlNifMapIterator> {
7080
let mut iter = MaybeUninit::uninit();
7181
let success = rustler_sys::enif_map_iterator_create(
7282
env,
7383
map,
7484
iter.as_mut_ptr(),
75-
ErlNifMapIteratorEntry::ERL_NIF_MAP_ITERATOR_HEAD,
85+
match entry {
86+
MapIteratorEntry::First => ErlNifMapIteratorEntry::ERL_NIF_MAP_ITERATOR_HEAD,
87+
MapIteratorEntry::Last => ErlNifMapIteratorEntry::ERL_NIF_MAP_ITERATOR_TAIL,
88+
},
7689
);
7790
if success == 0 {
7891
None
@@ -103,6 +116,10 @@ pub unsafe fn map_iterator_next(env: NIF_ENV, iter: &mut ErlNifMapIterator) {
103116
rustler_sys::enif_map_iterator_next(env, iter);
104117
}
105118

119+
pub unsafe fn map_iterator_prev(env: NIF_ENV, iter: &mut ErlNifMapIterator) {
120+
rustler_sys::enif_map_iterator_prev(env, iter);
121+
}
122+
106123
pub unsafe fn make_map_from_arrays(
107124
env: NIF_ENV,
108125
keys: &[NIF_TERM],

rustler_tests/lib/rustler_test.ex

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ defmodule RustlerTest do
5050
def term_type(_term), do: err()
5151

5252
def sum_map_values(_), do: err()
53-
def map_entries_sorted(_), do: err()
53+
def map_entries(_), do: err()
54+
def map_entries_reversed(_), do: err()
5455
def map_from_arrays(_keys, _values), do: err()
5556
def map_from_pairs(_pairs), do: err()
5657
def map_generic(_), do: err()

rustler_tests/native/rustler_test/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ rustler::init!(
3232
test_term::term_phash2_hash,
3333
test_term::term_type,
3434
test_map::sum_map_values,
35-
test_map::map_entries_sorted,
35+
test_map::map_entries,
36+
test_map::map_entries_reversed,
3637
test_map::map_from_arrays,
3738
test_map::map_from_pairs,
3839
test_map::map_generic,

rustler_tests/native/rustler_test/src/test_map.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,28 @@ pub fn sum_map_values(iter: MapIterator) -> NifResult<i64> {
1111
}
1212

1313
#[rustler::nif]
14-
pub fn map_entries_sorted<'a>(env: Env<'a>, iter: MapIterator<'a>) -> NifResult<Vec<Term<'a>>> {
14+
pub fn map_entries<'a>(env: Env<'a>, iter: MapIterator<'a>) -> NifResult<Vec<Term<'a>>> {
1515
let mut vec = vec![];
1616
for (key, value) in iter {
1717
let key_string = key.decode::<String>()?;
1818
vec.push((key_string, value));
1919
}
2020

21-
vec.sort_by_key(|pair| pair.0.clone());
21+
let erlang_pairs: Vec<Term> = vec
22+
.into_iter()
23+
.map(|(key, value)| make_tuple(env, &[key.encode(env), value]))
24+
.collect();
25+
Ok(erlang_pairs)
26+
}
27+
28+
#[rustler::nif]
29+
pub fn map_entries_reversed<'a>(env: Env<'a>, iter: MapIterator<'a>) -> NifResult<Vec<Term<'a>>> {
30+
let mut vec = vec![];
31+
for (key, value) in iter.rev() {
32+
let key_string = key.decode::<String>()?;
33+
vec.push((key_string, value));
34+
}
35+
2236
let erlang_pairs: Vec<Term> = vec
2337
.into_iter()
2438
.map(|(key, value)| make_tuple(env, &[key.encode(env), value]))

rustler_tests/test/map_test.exs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,13 @@ defmodule RustlerTest.MapTest do
77
end
88

99
test "map iteration with keys" do
10+
entries = RustlerTest.map_entries(%{"d" => 0, "a" => 1, "b" => 7, "e" => 4, "c" => 6})
11+
1012
assert [{"a", 1}, {"b", 7}, {"c", 6}, {"d", 0}, {"e", 4}] ==
11-
RustlerTest.map_entries_sorted(%{"d" => 0, "a" => 1, "b" => 7, "e" => 4, "c" => 6})
13+
Enum.sort_by(entries, &elem(&1, 0))
14+
15+
assert Enum.reverse(entries) ==
16+
RustlerTest.map_entries_reversed(%{"d" => 0, "a" => 1, "b" => 7, "e" => 4, "c" => 6})
1217
end
1318

1419
test "map from arrays" do

0 commit comments

Comments
 (0)