Skip to content

Commit fe3df2a

Browse files
committed
Add tests for arrays of strings
1 parent 71a5c45 commit fe3df2a

4 files changed

Lines changed: 201 additions & 36 deletions

File tree

bench/ndarray/stringops_bench.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@
1818
# nparr = nparr.view('S40').astype('U10')
1919

2020
N = int(1e5)
21-
nparr_ = np.array(['josé', 'pepe', 'francisco'])
22-
nparr = np.repeat(nparr_, N)
21+
nparr = np.repeat(np.array(['josé', 'pepe', 'francisco']), N)
2322
cparams = blosc2.cparams_dflts
23+
cparams["filters"][-1] = blosc2.Filter.SHUFFLE
24+
cparams["filters_meta"][-1] = 0 # use default (typesize)
2425
arr1 = blosc2.asarray(nparr, cparams=cparams)
2526
print(f"cratio without filter: {arr1.cratio}")
26-
cparams["filters"][-1] = blosc2.Filter.SHUFFLE
2727
cparams["filters_meta"][-1] = 4
2828
arr1 = blosc2.asarray(nparr, cparams=cparams)
2929
print(f"cratio with filter: {arr1.cratio}")

tests/ndarray/test_full.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,17 @@
3838
"full.b2nd",
3939
True,
4040
),
41+
(
42+
(23, 34),
43+
(20, 20),
44+
(10, 10),
45+
"josé",
46+
None,
47+
blosc2.CParams(codec=blosc2.Codec.LZ4HC, clevel=8, use_dict=False, nthreads=2),
48+
{"nthreads": 2},
49+
"full.b2nd",
50+
True,
51+
),
4152
(
4253
(80, 51, 60),
4354
(20, 10, 33),
@@ -95,6 +106,7 @@ def test_full(shape, chunks, blocks, fill_value, cparams, dparams, dtype, urlpat
95106
[
96107
((100, 1230), b"0123", None),
97108
((23, 34), b"sun", None),
109+
((23, 34), "josé", None),
98110
((80, 51, 60), 3.14, "f8"),
99111
((13, 13), 123456789, None),
100112
],
@@ -161,6 +173,7 @@ def test_complex_datatype():
161173
("f_019", "<f4", (27, 363)),
162174
("f_020", "S1000"),
163175
("f_021", "S1000"),
176+
("f_022", "<U1000"),
164177
]
165178
)
166179
a = np.zeros((256,), dtype=dtype)

tests/ndarray/test_lazyexpr.py

Lines changed: 14 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -540,41 +540,22 @@ def test_stringops(values):
540540
if value1 == "NDArray":
541541
a1 = np.array(["abc", "def", "aterr", "oot", "zu", "ab c"])
542542
a1_blosc = blosc2.asarray(a1)
543-
if value2 == "str": # ("NDArray", "str")
544-
value2 = "a"
545-
expr_lazy = blosc2.startswith(a1_blosc, value2)
546-
assert expr_lazy.shape == a1_blosc.shape
547-
res_numexpr = np.char.startswith(a1, value2)
548-
np.testing.assert_array_equal(expr_lazy[:], res_numexpr)
549-
value2 = "c"
550-
expr_lazy = blosc2.endswith(a1_blosc, value2)
551-
res_numexpr = np.char.endswith(a1, value2)
552-
np.testing.assert_array_equal(expr_lazy[:], res_numexpr)
553-
else: # ("NDArray", "NDArray")
554-
a2 = np.array(["ba", "ef", "rr", "o ", "\tz", "c h"])
555-
a2_blosc = blosc2.asarray(a2)
556-
expr_lazy = blosc2.startswith(a1_blosc, a2_blosc)
557-
assert expr_lazy.shape == a1_blosc.shape
558-
res_numexpr = np.char.startswith(a1, a2)
559-
np.testing.assert_array_equal(expr_lazy[:], res_numexpr)
560-
a2 = np.array(["ab", "d", " ath", "oo", "\tabc", " c"])
561-
a2_blosc = blosc2.asarray(a2)
562-
expr_lazy = blosc2.endswith(a1, a2_blosc)
563-
res_numexpr = np.char.endswith(a1, a2)
564-
np.testing.assert_array_equal(expr_lazy[:], res_numexpr)
565-
else: # ("str", "NDArray")
566-
value1 = "abc"
567-
a2 = np.array(["ab", "def", "a", "oot", "zu", "ab "])
543+
else: # ("str", _)
544+
a1 = a1_blosc = "abc"
545+
546+
if value2 == "str": # (_, "str")
547+
a2 = a2_blosc = "a"
548+
else: # (_, "NDArray")
549+
a2 = np.array(["ba", "ef", "rr", "o ", "\tz", "c h"])
568550
a2_blosc = blosc2.asarray(a2)
569-
expr_lazy = blosc2.startswith(value1, a2_blosc)
570-
assert expr_lazy.shape == a2_blosc.shape
571-
assert expr_lazy.dtype == blosc2.bool_
572-
res_numexpr = np.char.startswith(value1, a2)
573-
np.testing.assert_array_equal(expr_lazy[:], res_numexpr)
574551

575-
value1 = "b"
576-
expr_lazy = blosc2.endswith(value1, a2_blosc)
577-
res_numexpr = np.char.endswith(value1, a2)
552+
for func, npfunc in zip(
553+
(blosc2.startswith, blosc2.endswith), (np.char.startswith, np.char.endswith), strict=True
554+
):
555+
expr_lazy = func(a1_blosc, a2_blosc)
556+
res_numexpr = npfunc(a1, a2)
557+
assert expr_lazy.shape == res_numexpr.shape
558+
assert expr_lazy.dtype == blosc2.bool_
578559
np.testing.assert_array_equal(expr_lazy[:], res_numexpr)
579560

580561

tests/ndarray/test_stringarrays.py

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
import numpy as np
2+
import pytest
3+
4+
import blosc2
5+
6+
# ----------------------------------
7+
# Helpers
8+
# ----------------------------------
9+
10+
UNICODE_VALUES = [
11+
"café",
12+
"β",
13+
"こんにちは",
14+
"mañana",
15+
"добрый",
16+
"数据",
17+
]
18+
19+
20+
def make_unicode_array(shape, maxlen=16):
21+
"""
22+
Create a NumPy Unicode array with non-ASCII content.
23+
dtype='U' uses fixed-width UTF-32 internally.
24+
"""
25+
total = np.prod(shape)
26+
data = [UNICODE_VALUES[i % len(UNICODE_VALUES)] for i in range(total)]
27+
return np.array(data, dtype=f"U{maxlen}").reshape(shape)
28+
29+
30+
# ----------------------------------
31+
# Parameter grids
32+
# ----------------------------------
33+
34+
SHAPES = [
35+
(12,),
36+
(12, 6),
37+
(3, 4, 5),
38+
]
39+
40+
CHUNKS = [
41+
None,
42+
(4,),
43+
(7, 4),
44+
(2, 2, 5),
45+
]
46+
47+
BLOCKS = [
48+
None,
49+
(3,),
50+
(3, 3),
51+
(1, 2, 5),
52+
]
53+
54+
55+
# ----------------------------------
56+
# In-memory tests
57+
# ----------------------------------
58+
59+
60+
@pytest.mark.parametrize("shape", SHAPES)
61+
@pytest.mark.parametrize("chunks", CHUNKS)
62+
@pytest.mark.parametrize("blocks", BLOCKS)
63+
def test_unicode_roundtrip_in_memory(shape, chunks, blocks):
64+
arr = make_unicode_array(shape)
65+
66+
b2 = blosc2.asarray(
67+
arr,
68+
chunks=chunks if chunks and len(chunks) == arr.ndim else None,
69+
blocks=blocks if blocks and len(blocks) == arr.ndim else None,
70+
)
71+
72+
assert b2.dtype == arr.dtype
73+
assert np.array_equal(b2, arr)
74+
75+
76+
def test_unicode_indexing_and_slicing():
77+
arr = make_unicode_array((10,))
78+
b2 = blosc2.asarray(arr, chunks=(6,), blocks=(4,))
79+
80+
assert b2[0] == arr[0]
81+
assert b2[5] == arr[5]
82+
assert np.array_equal(b2[2:8], arr[2:8])
83+
assert np.array_equal(b2[::2], arr[::2])
84+
85+
86+
def test_unicode_multidimensional_slice():
87+
arr = make_unicode_array((6, 8))
88+
b2 = blosc2.asarray(arr, chunks=(3, 4), blocks=(1, 4))
89+
90+
assert np.array_equal(
91+
b2[1:5, 2:7],
92+
arr[1:5, 2:7],
93+
)
94+
95+
96+
def test_unicode_partial_assignment():
97+
arr = make_unicode_array((10,))
98+
b2 = blosc2.asarray(arr)
99+
100+
new_vals = np.array(["Ω", "λ", "plo"], dtype=arr.dtype)
101+
b2[3:6] = new_vals
102+
arr[3:6] = new_vals
103+
104+
assert np.array_equal(b2, arr)
105+
106+
107+
# ----------------------------------
108+
# On-disk tests
109+
# ----------------------------------
110+
111+
112+
@pytest.mark.parametrize("shape", SHAPES)
113+
def test_unicode_roundtrip_on_disk(tmp_path, shape):
114+
arr = make_unicode_array(shape)
115+
116+
path = tmp_path / "unicode_array.b2nd"
117+
118+
b2 = blosc2.asarray(
119+
arr,
120+
urlpath=path,
121+
mode="w",
122+
chunks=tuple(max(1, s // 2) for s in shape),
123+
blocks=tuple(1 for _ in shape),
124+
)
125+
126+
# Re-open from disk
127+
out = blosc2.open(path)
128+
129+
assert out.dtype == arr.dtype
130+
assert np.array_equal(out, arr)
131+
132+
133+
def test_unicode_on_disk_partial_io(tmp_path):
134+
arr = make_unicode_array((20,))
135+
path = tmp_path / "partial_unicode.b2nd"
136+
137+
b2 = blosc2.asarray(
138+
arr,
139+
urlpath=path,
140+
mode="w",
141+
chunks=(5,),
142+
blocks=(2,),
143+
)
144+
145+
# Partial read
146+
assert np.array_equal(b2[4:12], arr[4:12])
147+
148+
# Partial write
149+
replacement = np.array(
150+
["python", "is", "good", "!"],
151+
dtype=arr.dtype,
152+
)
153+
b2[6:10] = replacement
154+
arr[6:10] = replacement
155+
156+
reopened = blosc2.open(path)
157+
assert np.array_equal(reopened, arr)
158+
159+
160+
def test_unicode_on_disk_persistence(tmp_path):
161+
path = tmp_path / "persistent_unicode.b2nd"
162+
163+
arr1 = make_unicode_array((8,))
164+
blosc2.asarray(arr1, urlpath=path, mode="w")
165+
166+
arr2 = make_unicode_array((8,))
167+
b2 = blosc2.open(path, mode="a")
168+
b2[:] = arr2
169+
170+
reopened = blosc2.open(path)
171+
assert np.array_equal(reopened, arr2)

0 commit comments

Comments
 (0)