Skip to content

Commit 9975948

Browse files
committed
Implement count() method
1 parent 9ed5048 commit 9975948

File tree

4 files changed

+87
-0
lines changed

4 files changed

+87
-0
lines changed

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,17 @@ Alternate string representation to the built-in `str` type.
88
* `len` returns size in _bytes_ (not including terminating zero-byte).
99
* Random access (to _bytes_, *not* Unicode code points) is supported with indices and slices.
1010

11+
## Methods
12+
13+
### count(substring [,start [,end]])
14+
15+
See: https://docs.python.org/3/library/stdtypes.html#str.count
16+
17+
Notes:
18+
19+
* `substring` may be a `cstring` or Python `str` object.
20+
* `start` and `end`, if provided, are _byte_ indexes.
21+
1122
## TODO
1223

1324
* Write docs (see `str` type docs)
@@ -17,5 +28,6 @@ Alternate string representation to the built-in `str` type.
1728
* Read `__cstring__` "dunder" on objects, if available?
1829
* Implement iter (iterate over Unicode code points, "runes")
1930
* Implement str methods
31+
* Include start/end indexes as byte indexes? Calculate code points? Or just don't support?
2032
* Implement buffer interface?
2133
* Decide subclassing protocol

src/cstring.c

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,58 @@ static PyObject *cstring_subscript(PyObject *self, PyObject *key) {
179179
return NULL;
180180
}
181181

182+
static const char *_obj_to_utf8(PyObject *o, Py_ssize_t *len_p) {
183+
if(PyUnicode_Check(o))
184+
return PyUnicode_AsUTF8AndSize(o, len_p);
185+
if(Py_TYPE(o) == &cstring_type) {
186+
*len_p = cstring_len(o);
187+
return CSTRING_VALUE(o);
188+
}
189+
PyErr_Format(
190+
PyExc_TypeError, "Object cannot be type %s.", Py_TYPE(o)->tp_name);
191+
return NULL;
192+
}
193+
194+
static Py_ssize_t _fix_index(Py_ssize_t i, Py_ssize_t len) {
195+
Py_ssize_t result = i;
196+
if(result < 0)
197+
result += len;
198+
if(result < 0)
199+
result = 0;
200+
if(result > len)
201+
result = len;
202+
return result;
203+
}
204+
205+
PyDoc_STRVAR(count__doc__, "");
206+
static PyObject *cstring_count(PyObject *self, PyObject *args) {
207+
PyObject *substr_obj;
208+
Py_ssize_t start = 0;
209+
Py_ssize_t end = PY_SSIZE_T_MAX;
210+
211+
if(!PyArg_ParseTuple(args, "O|nn", &substr_obj, &start, &end))
212+
return NULL;
213+
214+
Py_ssize_t substr_len;
215+
const char *substr = _obj_to_utf8(substr_obj, &substr_len);
216+
if(!substr)
217+
return NULL;
218+
219+
start = _fix_index(start, cstring_len(self));
220+
end = _fix_index(end, cstring_len(self));
221+
222+
char *p = &CSTRING_VALUE(self)[start];
223+
long result = 0;
224+
while((p = strstr(p, substr)) != NULL) {
225+
++result;
226+
p += substr_len;
227+
if(p >= &CSTRING_VALUE(self)[end])
228+
break;
229+
}
230+
231+
return PyLong_FromLong(result);
232+
}
233+
182234
static PySequenceMethods cstring_as_sequence = {
183235
.sq_length = cstring_len,
184236
.sq_concat = cstring_concat,
@@ -193,6 +245,7 @@ static PyMappingMethods cstring_as_mapping = {
193245
};
194246

195247
static PyMethodDef cstring_methods[] = {
248+
{"count", cstring_count, METH_VARARGS, count__doc__},
196249
{0},
197250
};
198251

test/test_methods.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from cstring import cstring
2+
3+
4+
def test_count():
5+
target = cstring('hello, world')
6+
assert target.count(cstring('l')) == 3
7+
8+
9+
def test_count_start():
10+
target = cstring('hello, world')
11+
assert target.count(cstring('l'), 10) == 1
12+
13+
14+
def test_count_end():
15+
target = cstring('hello, world')
16+
assert target.count(cstring('l'), 0, 4) == 2
17+
18+
19+
def test_count_str_unicode():
20+
target = cstring('🙂 🙃 🙂 🙂 🙃 🙂 🙂')
21+
assert target.count('🙂') == 5
22+
File renamed without changes.

0 commit comments

Comments
 (0)