1#![cfg(feature = "indexmap")]
2
3//! Conversions to and from [indexmap](https://docs.rs/indexmap/)’s
4//! `IndexMap`.
5//!
6//! [`indexmap::IndexMap`] is a hash table that is closely compatible with the standard [`std::collections::HashMap`],
7//! with the difference that it preserves the insertion order when iterating over keys. It was inspired
8//! by Python's 3.6+ dict implementation.
9//!
10//! Dictionary order is guaranteed to be insertion order in Python, hence IndexMap is a good candidate
11//! for maintaining an equivalent behaviour in Rust.
12//!
13//! # Setup
14//!
15//! To use this feature, add this to your **`Cargo.toml`**:
16//!
17//! ```toml
18//! [dependencies]
19//! # change * to the latest versions
20//! indexmap = "*"
21#![doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION"), "\", features = [\"indexmap\"] }")]
22//! ```
23//!
24//! Note that you must use compatible versions of indexmap and PyO3.
25//! The required indexmap version may vary based on the version of PyO3.
26//!
27//! # Examples
28//!
29//! Using [indexmap](https://docs.rs/indexmap) to return a dictionary with some statistics
30//! about a list of numbers. Because of the insertion order guarantees, the Python code will
31//! always print the same result, matching users' expectations about Python's dict.
32//! ```rust
33//! use indexmap::{indexmap, IndexMap};
34//! use pyo3::prelude::*;
35//!
36//! fn median(data: &Vec<i32>) -> f32 {
37//! let sorted_data = data.clone().sort();
38//! let mid = data.len() / 2;
39//! if data.len() % 2 == 0 {
40//! data[mid] as f32
41//! }
42//! else {
43//! (data[mid] + data[mid - 1]) as f32 / 2.0
44//! }
45//! }
46//!
47//! fn mean(data: &Vec<i32>) -> f32 {
48//! data.iter().sum::<i32>() as f32 / data.len() as f32
49//! }
50//! fn mode(data: &Vec<i32>) -> f32 {
51//! let mut frequency = IndexMap::new(); // we can use IndexMap as any hash table
52//!
53//! for &element in data {
54//! *frequency.entry(element).or_insert(0) += 1;
55//! }
56//!
57//! frequency
58//! .iter()
59//! .max_by(|a, b| a.1.cmp(&b.1))
60//! .map(|(k, _v)| *k)
61//! .unwrap() as f32
62//! }
63//!
64//! #[pyfunction]
65//! fn calculate_statistics(data: Vec<i32>) -> IndexMap<&'static str, f32> {
66//! indexmap! {
67//! "median" => median(&data),
68//! "mean" => mean(&data),
69//! "mode" => mode(&data),
70//! }
71//! }
72//!
73//! #[pymodule]
74//! fn my_module(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
75//! m.add_function(wrap_pyfunction!(calculate_statistics, m)?)?;
76//! Ok(())
77//! }
78//! ```
79//!
80//! Python code:
81//! ```python
82//! from my_module import calculate_statistics
83//!
84//! data = [1, 1, 1, 3, 4, 5]
85//! print(calculate_statistics(data))
86//! # always prints {"median": 2.0, "mean": 2.5, "mode": 1.0} in the same order
87//! # if another hash table was used, the order could be random
88//! ```
89
90use crate::types::*;
91use crate::{FromPyObject, IntoPy, PyErr, PyObject, Python, ToPyObject};
92use std::{cmp, hash};
93
94impl<K, V, H> ToPyObject for indexmap::IndexMap<K, V, H>
95where
96 K: hash::Hash + cmp::Eq + ToPyObject,
97 V: ToPyObject,
98 H: hash::BuildHasher,
99{
100 fn to_object(&self, py: Python<'_>) -> PyObject {
101 IntoPyDict::into_py_dict(self, py).into()
102 }
103}
104
105impl<K, V, H> IntoPy<PyObject> for indexmap::IndexMap<K, V, H>
106where
107 K: hash::Hash + cmp::Eq + IntoPy<PyObject>,
108 V: IntoPy<PyObject>,
109 H: hash::BuildHasher,
110{
111 fn into_py(self, py: Python<'_>) -> PyObject {
112 let iter: impl Iterator, …)> = self
113 .into_iter()
114 .map(|(k: K, v: V)| (k.into_py(py), v.into_py(py)));
115 IntoPyDict::into_py_dict(self:iter, py).into()
116 }
117}
118
119impl<'source, K, V, S> FromPyObject<'source> for indexmap::IndexMap<K, V, S>
120where
121 K: FromPyObject<'source> + cmp::Eq + hash::Hash,
122 V: FromPyObject<'source>,
123 S: hash::BuildHasher + Default,
124{
125 fn extract(ob: &'source PyAny) -> Result<Self, PyErr> {
126 let dict: &PyDict = ob.downcast()?;
127 let mut ret: IndexMap = indexmap::IndexMap::with_capacity_and_hasher(n:dict.len(), S::default());
128 for (k: &PyAny, v: &PyAny) in dict {
129 ret.insert(K::extract(k)?, V::extract(ob:v)?);
130 }
131 Ok(ret)
132 }
133}
134
135#[cfg(test)]
136mod test_indexmap {
137
138 use crate::types::*;
139 use crate::{IntoPy, PyObject, Python, ToPyObject};
140
141 #[test]
142 fn test_indexmap_indexmap_to_python() {
143 Python::with_gil(|py| {
144 let mut map = indexmap::IndexMap::<i32, i32>::new();
145 map.insert(1, 1);
146
147 let m = map.to_object(py);
148 let py_map: &PyDict = m.downcast(py).unwrap();
149
150 assert!(py_map.len() == 1);
151 assert!(
152 py_map
153 .get_item(1)
154 .unwrap()
155 .unwrap()
156 .extract::<i32>()
157 .unwrap()
158 == 1
159 );
160 assert_eq!(
161 map,
162 py_map.extract::<indexmap::IndexMap::<i32, i32>>().unwrap()
163 );
164 });
165 }
166
167 #[test]
168 fn test_indexmap_indexmap_into_python() {
169 Python::with_gil(|py| {
170 let mut map = indexmap::IndexMap::<i32, i32>::new();
171 map.insert(1, 1);
172
173 let m: PyObject = map.into_py(py);
174 let py_map: &PyDict = m.downcast(py).unwrap();
175
176 assert!(py_map.len() == 1);
177 assert!(
178 py_map
179 .get_item(1)
180 .unwrap()
181 .unwrap()
182 .extract::<i32>()
183 .unwrap()
184 == 1
185 );
186 });
187 }
188
189 #[test]
190 fn test_indexmap_indexmap_into_dict() {
191 Python::with_gil(|py| {
192 let mut map = indexmap::IndexMap::<i32, i32>::new();
193 map.insert(1, 1);
194
195 let py_map = map.into_py_dict(py);
196
197 assert_eq!(py_map.len(), 1);
198 assert_eq!(
199 py_map
200 .get_item(1)
201 .unwrap()
202 .unwrap()
203 .extract::<i32>()
204 .unwrap(),
205 1
206 );
207 });
208 }
209
210 #[test]
211 fn test_indexmap_indexmap_insertion_order_round_trip() {
212 Python::with_gil(|py| {
213 let n = 20;
214 let mut map = indexmap::IndexMap::<i32, i32>::new();
215
216 for i in 1..=n {
217 if i % 2 == 1 {
218 map.insert(i, i);
219 } else {
220 map.insert(n - i, i);
221 }
222 }
223
224 let py_map = map.clone().into_py_dict(py);
225
226 let trip_map = py_map.extract::<indexmap::IndexMap<i32, i32>>().unwrap();
227
228 for (((k1, v1), (k2, v2)), (k3, v3)) in
229 map.iter().zip(py_map.iter()).zip(trip_map.iter())
230 {
231 let k2 = k2.extract::<i32>().unwrap();
232 let v2 = v2.extract::<i32>().unwrap();
233 assert_eq!((k1, v1), (&k2, &v2));
234 assert_eq!((k1, v1), (k3, v3));
235 assert_eq!((&k2, &v2), (k3, v3));
236 }
237 });
238 }
239}
240