arraykit 1.8.0__tar.gz → 1.9.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. {arraykit-1.8.0/arraykit.egg-info → arraykit-1.9.0}/PKG-INFO +7 -1
  2. {arraykit-1.8.0 → arraykit-1.9.0}/README.rst +6 -0
  3. arraykit-1.9.0/VERSION +2 -0
  4. {arraykit-1.8.0 → arraykit-1.9.0/arraykit.egg-info}/PKG-INFO +7 -1
  5. {arraykit-1.8.0 → arraykit-1.9.0}/src/__init__.pyi +2 -0
  6. {arraykit-1.8.0 → arraykit-1.9.0}/src/auto_map.c +204 -0
  7. {arraykit-1.8.0 → arraykit-1.9.0}/src/tri_map.c +42 -0
  8. {arraykit-1.8.0 → arraykit-1.9.0}/test/test_auto_map.py +103 -0
  9. {arraykit-1.8.0 → arraykit-1.9.0}/test/test_tri_map.py +69 -0
  10. arraykit-1.8.0/VERSION +0 -2
  11. {arraykit-1.8.0 → arraykit-1.9.0}/LICENSE.txt +0 -0
  12. {arraykit-1.8.0 → arraykit-1.9.0}/MANIFEST.in +0 -0
  13. {arraykit-1.8.0 → arraykit-1.9.0}/arraykit.egg-info/SOURCES.txt +0 -0
  14. {arraykit-1.8.0 → arraykit-1.9.0}/arraykit.egg-info/dependency_links.txt +0 -0
  15. {arraykit-1.8.0 → arraykit-1.9.0}/arraykit.egg-info/requires.txt +0 -0
  16. {arraykit-1.8.0 → arraykit-1.9.0}/arraykit.egg-info/top_level.txt +0 -0
  17. {arraykit-1.8.0 → arraykit-1.9.0}/pyproject.toml +0 -0
  18. {arraykit-1.8.0 → arraykit-1.9.0}/setup.cfg +0 -0
  19. {arraykit-1.8.0 → arraykit-1.9.0}/setup.py +0 -0
  20. {arraykit-1.8.0 → arraykit-1.9.0}/src/__init__.py +0 -0
  21. {arraykit-1.8.0 → arraykit-1.9.0}/src/_arraykit.c +0 -0
  22. {arraykit-1.8.0 → arraykit-1.9.0}/src/array_go.c +0 -0
  23. {arraykit-1.8.0 → arraykit-1.9.0}/src/array_go.h +0 -0
  24. {arraykit-1.8.0 → arraykit-1.9.0}/src/array_to_tuple.c +0 -0
  25. {arraykit-1.8.0 → arraykit-1.9.0}/src/array_to_tuple.h +0 -0
  26. {arraykit-1.8.0 → arraykit-1.9.0}/src/auto_map.h +0 -0
  27. {arraykit-1.8.0 → arraykit-1.9.0}/src/block_index.c +0 -0
  28. {arraykit-1.8.0 → arraykit-1.9.0}/src/block_index.h +0 -0
  29. {arraykit-1.8.0 → arraykit-1.9.0}/src/delimited_to_arrays.c +0 -0
  30. {arraykit-1.8.0 → arraykit-1.9.0}/src/delimited_to_arrays.h +0 -0
  31. {arraykit-1.8.0 → arraykit-1.9.0}/src/methods.c +0 -0
  32. {arraykit-1.8.0 → arraykit-1.9.0}/src/methods.h +0 -0
  33. {arraykit-1.8.0 → arraykit-1.9.0}/src/py.typed +0 -0
  34. {arraykit-1.8.0 → arraykit-1.9.0}/src/tri_map.h +0 -0
  35. {arraykit-1.8.0 → arraykit-1.9.0}/src/utilities.h +0 -0
  36. {arraykit-1.8.0 → arraykit-1.9.0}/test/test_array_go.py +0 -0
  37. {arraykit-1.8.0 → arraykit-1.9.0}/test/test_astype_array.py +0 -0
  38. {arraykit-1.8.0 → arraykit-1.9.0}/test/test_auto_map_property.py +0 -0
  39. {arraykit-1.8.0 → arraykit-1.9.0}/test/test_block_index.py +0 -0
  40. {arraykit-1.8.0 → arraykit-1.9.0}/test/test_delimited_to_arrays.py +0 -0
  41. {arraykit-1.8.0 → arraykit-1.9.0}/test/test_delimited_to_arrays_integration.py +0 -0
  42. {arraykit-1.8.0 → arraykit-1.9.0}/test/test_delimited_to_arrays_property.py +0 -0
  43. {arraykit-1.8.0 → arraykit-1.9.0}/test/test_factorize.py +0 -0
  44. {arraykit-1.8.0 → arraykit-1.9.0}/test/test_fill_directional.py +0 -0
  45. {arraykit-1.8.0 → arraykit-1.9.0}/test/test_group_ordering.py +0 -0
  46. {arraykit-1.8.0 → arraykit-1.9.0}/test/test_nonzero_1d.py +0 -0
  47. {arraykit-1.8.0 → arraykit-1.9.0}/test/test_nonzero_1d_property.py +0 -0
  48. {arraykit-1.8.0 → arraykit-1.9.0}/test/test_objectable.py +0 -0
  49. {arraykit-1.8.0 → arraykit-1.9.0}/test/test_pyi.py +0 -0
  50. {arraykit-1.8.0 → arraykit-1.9.0}/test/test_split_after_count.py +0 -0
  51. {arraykit-1.8.0 → arraykit-1.9.0}/test/test_transition_slices_from_group.py +0 -0
  52. {arraykit-1.8.0 → arraykit-1.9.0}/test/test_type_discovery.py +0 -0
  53. {arraykit-1.8.0 → arraykit-1.9.0}/test/test_util.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: arraykit
3
- Version: 1.8.0
3
+ Version: 1.9.0
4
4
  Summary: Array utilities for StaticFrame
5
5
  Author: Christopher Ariza, Brandt Bucher, Charles Burkland
6
6
  License: MIT
@@ -64,6 +64,12 @@ ArrayKit requires the following:
64
64
  What is New in ArrayKit
65
65
  -------------------------
66
66
 
67
+ 1.9.0
68
+ ............
69
+
70
+ Added ``FrozenAutoMap.get_all_fill()`` and ``TriMap.register_many_from_one()``.
71
+
72
+
67
73
  1.8.0
68
74
  ............
69
75
 
@@ -35,6 +35,12 @@ ArrayKit requires the following:
35
35
  What is New in ArrayKit
36
36
  -------------------------
37
37
 
38
+ 1.9.0
39
+ ............
40
+
41
+ Added ``FrozenAutoMap.get_all_fill()`` and ``TriMap.register_many_from_one()``.
42
+
43
+
38
44
  1.8.0
39
45
  ............
40
46
 
arraykit-1.9.0/VERSION ADDED
@@ -0,0 +1,2 @@
1
+ 1.9.0
2
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: arraykit
3
- Version: 1.8.0
3
+ Version: 1.9.0
4
4
  Summary: Array utilities for StaticFrame
5
5
  Author: Christopher Ariza, Brandt Bucher, Charles Burkland
6
6
  License: MIT
@@ -64,6 +64,12 @@ ArrayKit requires the following:
64
64
  What is New in ArrayKit
65
65
  -------------------------
66
66
 
67
+ 1.9.0
68
+ ............
69
+
70
+ Added ``FrozenAutoMap.get_all_fill()`` and ``TriMap.register_many_from_one()``.
71
+
72
+
67
73
  1.8.0
68
74
  ............
69
75
 
@@ -53,6 +53,7 @@ class TriMap:
53
53
  def __init__(self, /, src_len: int, dst_len: int) -> None: ...
54
54
  def __repr__(self) -> str: ...
55
55
  def register_one(self, /, src_from: int, dst_from: int) -> None: ...
56
+ def register_many_from_one(self, __dst_pos: np.ndarray) -> None: ...
56
57
  def register_unmatched_dst(self) -> None: ...
57
58
  def register_many(self, /, src_from: int, dst_from: np.ndarray) -> None: ...
58
59
  def finalize(self) -> None: ...
@@ -142,6 +143,7 @@ class FrozenAutoMap:
142
143
  def items(self) -> tp.Iterator[tuple[_TLabel, int]]: ...
143
144
  def values(self) -> tp.Iterator[int]: ...
144
145
  def get_all(self, __key: list[_TLabel] | np.ndarray) -> np.ndarray: ...
146
+ def get_all_fill(self, __key: list[_TLabel] | np.ndarray) -> np.ndarray: ...
145
147
  def get_any(self, __key: list[_TLabel] | np.ndarray) -> list[int]: ...
146
148
  def __iter__(self) -> tp.Iterator[_TLabel]: ...
147
149
  def __getitem__(self, __key: tp.Any) -> int: ...
@@ -1919,6 +1919,209 @@ fam_get_all(FAMObject *self, PyObject *key) {
1919
1919
  # undef GET_ALL_FLEXIBLE
1920
1920
 
1921
1921
 
1922
+ // Fill variants of the GET_ALL_* macros: on a miss, write -1 into the output slot and
1923
+ // continue (instead of raising KeyError). Depend on self, key_size, key_array, i, b, array.
1924
+ # define GET_ALL_FILL_SCALARS(npy_type_src, npy_type_dst, kat, lookup_func, hash_func, post_deref) \
1925
+ { \
1926
+ npy_type_dst v; \
1927
+ Py_ssize_t table_pos; \
1928
+ for (; i < key_size; i++) { \
1929
+ v = post_deref(*(npy_type_src*)PyArray_GETPTR1(key_array, i)); \
1930
+ table_pos = lookup_func(self, v, hash_func(v), kat); \
1931
+ if (table_pos < 0 || (self->table[table_pos].hash == -1)) { \
1932
+ if (PyErr_Occurred()) { \
1933
+ Py_DECREF(array); \
1934
+ return NULL; \
1935
+ } \
1936
+ b[i] = -1; \
1937
+ continue; \
1938
+ } \
1939
+ b[i] = (npy_int64)self->table[table_pos].keys_pos; \
1940
+ } \
1941
+ } \
1942
+
1943
+ # define GET_ALL_FILL_DT64(npy_type_src, npy_type_dst, kat, lookup_func, hash_func) \
1944
+ { \
1945
+ npy_type_dst v; \
1946
+ Py_ssize_t table_pos; \
1947
+ for (; i < key_size; i++) { \
1948
+ v = *(npy_type_src*)PyArray_GETPTR1(key_array, i); \
1949
+ table_pos = lookup_func(self, v, hash_func(v), kat); \
1950
+ if (table_pos < 0 || (self->table[table_pos].hash == -1)) { \
1951
+ if (PyErr_Occurred()) { \
1952
+ Py_DECREF(array); \
1953
+ return NULL; \
1954
+ } \
1955
+ b[i] = -1; \
1956
+ continue; \
1957
+ } \
1958
+ b[i] = (npy_int64)self->table[table_pos].keys_pos; \
1959
+ } \
1960
+ } \
1961
+
1962
+ # define GET_ALL_FILL_FLEXIBLE(char_type, get_end_func, lookup_func, hash_func) \
1963
+ { \
1964
+ char_type* v; \
1965
+ Py_ssize_t dt_size = PyArray_ITEMSIZE(key_array) / sizeof(char_type); \
1966
+ Py_ssize_t k_size; \
1967
+ Py_ssize_t table_pos; \
1968
+ for (; i < key_size; i++) { \
1969
+ v = (char_type*)PyArray_GETPTR1(key_array, i); \
1970
+ k_size = get_end_func(v, dt_size) - v; \
1971
+ table_pos = lookup_func(self, v, k_size, hash_func(v, k_size)); \
1972
+ if (table_pos < 0 || (self->table[table_pos].hash == -1)) { \
1973
+ if (PyErr_Occurred()) { \
1974
+ Py_DECREF(array); \
1975
+ return NULL; \
1976
+ } \
1977
+ b[i] = -1; \
1978
+ continue; \
1979
+ } \
1980
+ b[i] = (npy_int64)self->table[table_pos].keys_pos; \
1981
+ } \
1982
+ } \
1983
+
1984
+ // Given a list or array of keys, return an input-aligned int64 array of looked-up
1985
+ // positions, with -1 for any key not found (never raises KeyError). This is the
1986
+ // "vectorized get with default -1" needed by left/outer joins. Immutable output.
1987
+ static PyObject *
1988
+ fam_get_all_fill(FAMObject *self, PyObject *key) {
1989
+ Py_ssize_t key_size = 0;
1990
+ Py_ssize_t keys_pos = -1;
1991
+ PyObject* k = NULL;
1992
+ PyObject *array = NULL;
1993
+ Py_ssize_t i = 0;
1994
+
1995
+ int key_is_list;
1996
+ if (PyList_CheckExact(key)) {
1997
+ key_is_list = 1;
1998
+ key_size = PyList_GET_SIZE(key);
1999
+ }
2000
+ else if (PyArray_Check(key)) {
2001
+ key_is_list = 0;
2002
+ key_size = PyArray_SIZE((PyArrayObject *)key);
2003
+ }
2004
+ else {
2005
+ PyErr_SetString(PyExc_TypeError, "Must provide a list or array.");
2006
+ return NULL;
2007
+ }
2008
+
2009
+ npy_intp dims[] = {key_size};
2010
+ array = PyArray_EMPTY(1, dims, NPY_INT64, 0);
2011
+ if (array == NULL) {
2012
+ return NULL;
2013
+ }
2014
+ npy_int64* b = (npy_int64*)PyArray_DATA((PyArrayObject*)array);
2015
+
2016
+ if (key_is_list) {
2017
+ for (; i < key_size; i++) {
2018
+ k = PyList_GET_ITEM(key, i); // borrow
2019
+ keys_pos = lookup(self, k);
2020
+ if (keys_pos < 0) {
2021
+ if (PyErr_Occurred()) {
2022
+ Py_DECREF(array);
2023
+ return NULL;
2024
+ }
2025
+ b[i] = -1;
2026
+ continue;
2027
+ }
2028
+ b[i] = (npy_int64)keys_pos;
2029
+ }
2030
+ }
2031
+ else { // key is an array
2032
+ PyArrayObject* key_array = (PyArrayObject *)key;
2033
+ int key_array_t = PyArray_TYPE(key_array);
2034
+ int use_typed = kat_is_kind(self->keys_array_type, PyArray_DESCR(key_array)->kind);
2035
+ if (use_typed && key_array_t == NPY_DATETIME) {
2036
+ NPY_DATETIMEUNIT key_unit = AK_dt_unit_from_array(key_array);
2037
+ if (!kat_is_datetime_unit(self->keys_array_type, key_unit)) {
2038
+ // mismatched units: use the coercing scalar path (below) rather than
2039
+ // raising, so equal instants still resolve (and true misses give -1)
2040
+ use_typed = 0;
2041
+ }
2042
+ }
2043
+ if (use_typed) {
2044
+ switch (key_array_t) {
2045
+ case NPY_INT64:
2046
+ GET_ALL_FILL_SCALARS(npy_int64, npy_int64, KAT_INT64, lookup_hash_int, int_to_hash,);
2047
+ break;
2048
+ case NPY_INT32:
2049
+ GET_ALL_FILL_SCALARS(npy_int32, npy_int64, KAT_INT32, lookup_hash_int, int_to_hash,);
2050
+ break;
2051
+ case NPY_INT16:
2052
+ GET_ALL_FILL_SCALARS(npy_int16, npy_int64, KAT_INT16, lookup_hash_int, int_to_hash,);
2053
+ break;
2054
+ case NPY_INT8:
2055
+ GET_ALL_FILL_SCALARS(npy_int8, npy_int64, KAT_INT8, lookup_hash_int, int_to_hash,);
2056
+ break;
2057
+ case NPY_UINT64:
2058
+ GET_ALL_FILL_SCALARS(npy_uint64, npy_uint64, KAT_UINT64, lookup_hash_uint, uint_to_hash,);
2059
+ break;
2060
+ case NPY_UINT32:
2061
+ GET_ALL_FILL_SCALARS(npy_uint32, npy_uint64, KAT_UINT32, lookup_hash_uint, uint_to_hash,);
2062
+ break;
2063
+ case NPY_UINT16:
2064
+ GET_ALL_FILL_SCALARS(npy_uint16, npy_uint64, KAT_UINT16, lookup_hash_uint, uint_to_hash,);
2065
+ break;
2066
+ case NPY_UINT8:
2067
+ GET_ALL_FILL_SCALARS(npy_uint8, npy_uint64, KAT_UINT8, lookup_hash_uint, uint_to_hash,);
2068
+ break;
2069
+ case NPY_FLOAT64:
2070
+ GET_ALL_FILL_SCALARS(npy_double, npy_double, KAT_FLOAT64, lookup_hash_double, double_to_hash,);
2071
+ break;
2072
+ case NPY_FLOAT32:
2073
+ GET_ALL_FILL_SCALARS(npy_float, npy_double, KAT_FLOAT32, lookup_hash_double, double_to_hash,);
2074
+ break;
2075
+ case NPY_FLOAT16:
2076
+ GET_ALL_FILL_SCALARS(npy_half, npy_double, KAT_FLOAT16, lookup_hash_double, double_to_hash, npy_half_to_double);
2077
+ break;
2078
+ case NPY_UNICODE:
2079
+ GET_ALL_FILL_FLEXIBLE(Py_UCS4, ucs4_get_end_p, lookup_hash_unicode, unicode_to_hash);
2080
+ break;
2081
+ case NPY_STRING:
2082
+ GET_ALL_FILL_FLEXIBLE(char, char_get_end_p, lookup_hash_string, string_to_hash);
2083
+ break;
2084
+ case NPY_DATETIME:
2085
+ GET_ALL_FILL_DT64(npy_int64, npy_int64, KAT_INT64, lookup_hash_int, int_to_hash);
2086
+ break;
2087
+ default:
2088
+ use_typed = 0; // unhandled kind: fall to the scalar path below
2089
+ break;
2090
+ }
2091
+ }
2092
+ if (!use_typed) {
2093
+ for (; i < key_size; i++) {
2094
+ k = PyArray_ToScalar(PyArray_GETPTR1(key_array, i), key_array);
2095
+ if (k == NULL) {
2096
+ Py_DECREF(array);
2097
+ return NULL;
2098
+ }
2099
+ keys_pos = lookup(self, k);
2100
+ if (keys_pos < 0) {
2101
+ if (PyErr_Occurred()) {
2102
+ Py_DECREF(k);
2103
+ Py_DECREF(array);
2104
+ return NULL;
2105
+ }
2106
+ Py_DECREF(k);
2107
+ b[i] = -1;
2108
+ continue;
2109
+ }
2110
+ Py_DECREF(k);
2111
+ b[i] = (npy_int64)keys_pos;
2112
+ }
2113
+ }
2114
+ }
2115
+
2116
+ PyArray_CLEARFLAGS((PyArrayObject *)array, NPY_ARRAY_WRITEABLE);
2117
+ return array;
2118
+ }
2119
+
2120
+ # undef GET_ALL_FILL_SCALARS
2121
+ # undef GET_ALL_FILL_DT64
2122
+ # undef GET_ALL_FILL_FLEXIBLE
2123
+
2124
+
1922
2125
  static inline int
1923
2126
  append_ssize_t(
1924
2127
  PyObject* list,
@@ -2974,6 +3177,7 @@ static PyMethodDef fam_methods[] = {
2974
3177
  {"keys", (PyCFunction) fam_keys, METH_NOARGS, NULL},
2975
3178
  {"values", (PyCFunction) fam_values, METH_NOARGS, NULL},
2976
3179
  {"get_all", (PyCFunction) fam_get_all, METH_O, NULL},
3180
+ {"get_all_fill", (PyCFunction) fam_get_all_fill, METH_O, NULL},
2977
3181
  {"get_any", (PyCFunction) fam_get_any, METH_O, NULL},
2978
3182
  {NULL},
2979
3183
  };
@@ -269,6 +269,47 @@ TriMap_register_one(TriMapObject *self, PyObject *args) {
269
269
  Py_RETURN_NONE;
270
270
  }
271
271
 
272
+ // Bulk one-to-one registration: given an int64 array `dst_pos` of length `src_len`,
273
+ // register src position i to dst position dst_pos[i] (or -1 for an unmatched src) in a
274
+ // single C loop -- equivalent to calling register_one(i, dst_pos[i]) for each i, but
275
+ // without per-element Python overhead. Used by the hash-join fast path.
276
+ PyObject *
277
+ TriMap_register_many_from_one(TriMapObject *self, PyObject *arg) {
278
+ if (self->finalized) {
279
+ PyErr_SetString(PyExc_RuntimeError, "Cannot register post finalization");
280
+ return NULL;
281
+ }
282
+ if (!PyArray_Check(arg)) {
283
+ PyErr_SetString(PyExc_TypeError, "Must provide an array");
284
+ return NULL;
285
+ }
286
+ PyArrayObject* a = (PyArrayObject*)arg;
287
+ if (PyArray_TYPE(a) != NPY_INT64) {
288
+ PyErr_SetString(PyExc_ValueError, "Array must be of type int64");
289
+ return NULL;
290
+ }
291
+ if (PyArray_NDIM(a) != 1) {
292
+ PyErr_SetString(PyExc_ValueError, "Array must be 1-dimensional");
293
+ return NULL;
294
+ }
295
+ if (!PyArray_IS_C_CONTIGUOUS(a)) {
296
+ PyErr_SetString(PyExc_ValueError, "Array must be contiguous");
297
+ return NULL;
298
+ }
299
+ npy_intp n = PyArray_SIZE(a);
300
+ if (n != self->src_len) {
301
+ PyErr_SetString(PyExc_ValueError, "Array length must equal src_len");
302
+ return NULL;
303
+ }
304
+ const npy_int64* d = (npy_int64*)PyArray_DATA(a);
305
+ for (npy_intp i = 0; i < n; i++) {
306
+ if (AK_TM_register_one(self, (Py_ssize_t)i, (Py_ssize_t)d[i])) {
307
+ return NULL;
308
+ }
309
+ }
310
+ Py_RETURN_NONE;
311
+ }
312
+
272
313
  PyObject *
273
314
  TriMap_register_unmatched_dst(TriMapObject *self) {
274
315
  if (self->finalized) {
@@ -1358,6 +1399,7 @@ TriMap_map_dst_fill(TriMapObject *self, PyObject *args) {
1358
1399
 
1359
1400
  static PyMethodDef TriMap_methods[] = {
1360
1401
  {"register_one", (PyCFunction)TriMap_register_one, METH_VARARGS, NULL},
1402
+ {"register_many_from_one", (PyCFunction)TriMap_register_many_from_one, METH_O, NULL},
1361
1403
  {"register_unmatched_dst", (PyCFunction)TriMap_register_unmatched_dst, METH_NOARGS, NULL},
1362
1404
  {"register_many", (PyCFunction)TriMap_register_many, METH_VARARGS, NULL},
1363
1405
  {"finalize", (PyCFunction)TriMap_finalize, METH_NOARGS, NULL},
@@ -916,6 +916,109 @@ def test_fam_array_get_all_m3():
916
916
  assert post2.tolist() == [2, 1]
917
917
 
918
918
 
919
+ # -------------------------------------------------------------------------------
920
+ # get_all_fill: input-aligned lookup, -1 for misses (never raises KeyError)
921
+
922
+
923
+ def test_fam_array_get_all_fill_int_a():
924
+ a1 = np.array((10, 20, 30, 40), dtype=np.int64)
925
+ a1.flags.writeable = False
926
+ fam = FrozenAutoMap(a1)
927
+ # all present -> identical to get_all
928
+ keys = np.array([30, 10, 40], dtype=np.int64)
929
+ keys.flags.writeable = False
930
+ assert fam.get_all_fill(keys).tolist() == [2, 0, 3]
931
+ assert fam.get_all_fill(keys).tolist() == fam.get_all(keys).tolist()
932
+
933
+
934
+ def test_fam_array_get_all_fill_int_missing():
935
+ a1 = np.array((10, 20, 30, 40), dtype=np.int64)
936
+ a1.flags.writeable = False
937
+ fam = FrozenAutoMap(a1)
938
+ keys = np.array([30, 99, 10, 77], dtype=np.int64)
939
+ keys.flags.writeable = False
940
+ assert fam.get_all_fill(keys).tolist() == [2, -1, 0, -1]
941
+
942
+
943
+ def test_fam_array_get_all_fill_immutable_and_dtype():
944
+ a1 = np.array((10, 20, 30), dtype=np.int64)
945
+ a1.flags.writeable = False
946
+ fam = FrozenAutoMap(a1)
947
+ keys = np.array([10], dtype=np.int64)
948
+ keys.flags.writeable = False
949
+ post = fam.get_all_fill(keys)
950
+ assert not post.flags.writeable
951
+ assert post.dtype == np.dtype(np.int64)
952
+
953
+
954
+ def test_fam_array_get_all_fill_empty():
955
+ a1 = np.array((10, 20), dtype=np.int64)
956
+ a1.flags.writeable = False
957
+ fam = FrozenAutoMap(a1)
958
+ keys = np.array([], dtype=np.int64)
959
+ keys.flags.writeable = False
960
+ assert fam.get_all_fill(keys).tolist() == []
961
+
962
+
963
+ def test_fam_array_get_all_fill_all_missing():
964
+ a1 = np.array((10, 20), dtype=np.int64)
965
+ a1.flags.writeable = False
966
+ fam = FrozenAutoMap(a1)
967
+ keys = np.array([1, 2, 3], dtype=np.int64)
968
+ keys.flags.writeable = False
969
+ assert fam.get_all_fill(keys).tolist() == [-1, -1, -1]
970
+
971
+
972
+ def test_fam_array_get_all_fill_kind_mismatch():
973
+ # int map queried with a float array -> scalar fallback, -1 for misses
974
+ a1 = np.array((10, 20, 30), dtype=np.int64)
975
+ a1.flags.writeable = False
976
+ fam = FrozenAutoMap(a1)
977
+ keys = np.array([10.0, 15.0, 30.0], dtype=np.float64)
978
+ keys.flags.writeable = False
979
+ assert fam.get_all_fill(keys).tolist() == [0, -1, 2]
980
+
981
+
982
+ def test_fam_array_get_all_fill_unicode():
983
+ a1 = np.array(['a', 'bb', 'c'])
984
+ a1.flags.writeable = False
985
+ fam = FrozenAutoMap(a1)
986
+ keys = np.array(['c', 'zz', 'a'])
987
+ keys.flags.writeable = False
988
+ assert fam.get_all_fill(keys).tolist() == [2, -1, 0]
989
+
990
+
991
+ def test_fam_array_get_all_fill_bytes():
992
+ a1 = np.array([b'a', b'bb', b'c'], dtype='S2')
993
+ a1.flags.writeable = False
994
+ fam = FrozenAutoMap(a1)
995
+ keys = np.array([b'c', b'zz', b'a'], dtype='S2')
996
+ keys.flags.writeable = False
997
+ assert fam.get_all_fill(keys).tolist() == [2, -1, 0]
998
+
999
+
1000
+ def test_fam_array_get_all_fill_datetime():
1001
+ a1 = np.array(['2020-01', '2021-06', '2022-03'], dtype='datetime64[M]')
1002
+ a1.flags.writeable = False
1003
+ fam = FrozenAutoMap(a1)
1004
+ keys = np.array(['2022-03', '1999-12', '2020-01'], dtype='datetime64[M]')
1005
+ keys.flags.writeable = False
1006
+ assert fam.get_all_fill(keys).tolist() == [2, -1, 0]
1007
+
1008
+
1009
+ def test_fam_array_get_all_fill_object_list():
1010
+ fam = FrozenAutoMap([('x',), ('y',)])
1011
+ assert fam.get_all_fill([('y',), ('z',), ('x',)]).tolist() == [1, -1, 0]
1012
+
1013
+
1014
+ def test_fam_array_get_all_fill_bad_type():
1015
+ a1 = np.array((10, 20), dtype=np.int64)
1016
+ a1.flags.writeable = False
1017
+ fam = FrozenAutoMap(a1)
1018
+ with pytest.raises(TypeError):
1019
+ fam.get_all_fill('a')
1020
+
1021
+
919
1022
  # -------------------------------------------------------------------------------
920
1023
 
921
1024
 
@@ -1439,3 +1439,72 @@ class TestUnit(unittest.TestCase):
1439
1439
  np.datetime64('2005-11'),
1440
1440
  ],
1441
1441
  )
1442
+
1443
+ # ------------------------------------------------------------------
1444
+ # register_many_from_one (bulk one-to-one)
1445
+
1446
+ def test_tri_map_register_many_from_one_a(self) -> None:
1447
+ tm = TriMap(3, 4)
1448
+ tm.register_many_from_one(np.array([2, 0, 3], dtype=np.int64))
1449
+ tm.finalize()
1450
+ self.assertFalse(tm.is_many())
1451
+ src = np.array([10, 20, 30])
1452
+ dst = np.array([100, 200, 300, 400])
1453
+ self.assertEqual(tm.map_src_no_fill(src).tolist(), [10, 20, 30])
1454
+ self.assertEqual(tm.map_dst_no_fill(dst).tolist(), [300, 100, 400])
1455
+
1456
+ def test_tri_map_register_many_from_one_unmatched(self) -> None:
1457
+ # -1 marks an unmatched src -> a src-only row (dst side gets a fill there)
1458
+ tm = TriMap(3, 4)
1459
+ tm.register_many_from_one(np.array([2, -1, 0], dtype=np.int64))
1460
+ tm.finalize()
1461
+ self.assertTrue(tm.src_no_fill()) # every output row has a src value
1462
+ self.assertFalse(tm.dst_no_fill()) # row 1 has no dst -> needs fill
1463
+ dst = np.array([100, 200, 300, 400])
1464
+ self.assertEqual(tm.map_dst_fill(dst, -9, np.dtype(np.int64)).tolist(), [300, -9, 100])
1465
+
1466
+ def test_tri_map_register_many_from_one_is_many(self) -> None:
1467
+ # two src rows matching the same dst -> is_many
1468
+ tm = TriMap(3, 3)
1469
+ tm.register_many_from_one(np.array([0, 0, 1], dtype=np.int64))
1470
+ tm.finalize()
1471
+ self.assertTrue(tm.is_many())
1472
+
1473
+ def test_tri_map_register_many_from_one_equivalence(self) -> None:
1474
+ rng = np.random.RandomState(0)
1475
+ for _ in range(50):
1476
+ src_len = int(rng.randint(1, 12))
1477
+ dst_len = int(rng.randint(1, 10))
1478
+ dst_pos = rng.randint(-1, dst_len, size=src_len).astype(np.int64)
1479
+ tb = TriMap(src_len, dst_len)
1480
+ tb.register_many_from_one(dst_pos)
1481
+ tb.finalize()
1482
+ tl = TriMap(src_len, dst_len)
1483
+ for i in range(src_len):
1484
+ tl.register_one(i, int(dst_pos[i]))
1485
+ tl.finalize()
1486
+ src = np.arange(100, 100 + src_len)
1487
+ dst = np.arange(200, 200 + dst_len)
1488
+ self.assertEqual(tb.is_many(), tl.is_many())
1489
+ self.assertEqual(
1490
+ tb.map_src_no_fill(src).tolist(), tl.map_src_no_fill(src).tolist()
1491
+ )
1492
+ self.assertEqual(
1493
+ tb.map_dst_fill(dst, -1, np.dtype(np.int64)).tolist(),
1494
+ tl.map_dst_fill(dst, -1, np.dtype(np.int64)).tolist(),
1495
+ )
1496
+
1497
+ def test_tri_map_register_many_from_one_errors(self) -> None:
1498
+ with self.assertRaises(ValueError): # wrong length
1499
+ TriMap(3, 3).register_many_from_one(np.array([0, 1], dtype=np.int64))
1500
+ with self.assertRaises(ValueError): # wrong dtype
1501
+ TriMap(3, 3).register_many_from_one(np.array([0, 1, 2], dtype=np.int32))
1502
+ with self.assertRaises(ValueError): # out of bounds dst
1503
+ TriMap(3, 3).register_many_from_one(np.array([0, 1, 9], dtype=np.int64))
1504
+ with self.assertRaises(TypeError): # not an array
1505
+ TriMap(3, 3).register_many_from_one([0, 1, 2])
1506
+ tm = TriMap(2, 2)
1507
+ tm.register_many_from_one(np.array([0, 1], dtype=np.int64))
1508
+ tm.finalize()
1509
+ with self.assertRaises(RuntimeError): # post-finalize
1510
+ tm.register_many_from_one(np.array([0, 1], dtype=np.int64))
arraykit-1.8.0/VERSION DELETED
@@ -1,2 +0,0 @@
1
- 1.8.0
2
-
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes