diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..96ba6eb5a --- /dev/null +++ b/.travis.yml @@ -0,0 +1,7 @@ +dist: xenial +language: python +python: +- "2.7" +- "3.5" +install: pip install -e . +script: py.test diff --git a/README.rst b/README.rst index b98efea73..810ea9d42 100644 --- a/README.rst +++ b/README.rst @@ -2,10 +2,13 @@ bitarray: efficient arrays of booleans ====================================== +.. image:: https://travis-ci.org/diamondman/bitarray.svg?branch=master + :target: https://travis-ci.org/diamondman/bitarray + This module provides an object type which efficiently represents an array of booleans. Bitarrays are sequence types and behave very much like usual lists. Eight bits are represented by one byte in a contiguous block of -memory. The user can select between two representations; little-endian +memory. The user can select between two representations: little-endian and big-endian. All of the functionality is implemented in C. Methods for accessing the machine representation are provided. This can be useful when bit level access to binary files is required, @@ -68,7 +71,7 @@ Once you have installed the package, you may want to test it:: ........................................... ---------------------------------------------------------------------- Ran 134 tests in 1.396s - + OK You can always import the function test, @@ -156,8 +159,8 @@ Bit endianness -------------- Since a bitarray allows addressing of individual bits, where the machine -represents 8 bits in one byte, there two obvious choices for this mapping; -little- and big-endian. +represents 8 bits in one byte, there are two obvious choices for this +mapping: little- and big-endian. When creating a new bitarray object, the endianness can always be specified explicitly: @@ -305,26 +308,26 @@ Reference the optional initial, and endianness. If no object is provided, the bitarray is initialized to have length zero. The initial object may be of the following types: - + int, long Create bitarray of length given by the integer. The initial values in the array are random, because only the memory allocated. - + string Create bitarray from a string of '0's and '1's. - + list, tuple, iterable Create bitarray from a sequence, each element in the sequence is converted to a bit using truth value value. - + bitarray Create bitarray from another bitarray. This is done by copying the memory holding the bitarray data, and is hence very fast. - + The optional keyword arguments 'endian' specifies the bit endianness of the created bitarray object. Allowed values are 'big' and 'little' (default is 'big'). - + Note that setting the bit endianness only has an effect when accessing the machine representation of the bitarray, i.e. when using the methods: tofile, fromfile, tobytes, frombytes. @@ -462,17 +465,25 @@ Reference Reverse the order of bits in the array (in-place). -``search(bitarray, [limit])`` -> list +``search(bitarray, [limit], [pos])`` -> list Searches for the given a bitarray in self, and returns the start positions where bitarray matches self as a list. - The optional argument limits the number of search results to the integer - specified. By default, all search results are returned. + The optional `limit` argument limits the number of search results to the + integer specified. By default, all search results are returned. + The optional `pos` argument begins the search at the position specified. + By default, search begins at position 0. If no match is found until the end, + the search will wrap around until reaching the start position again. ``setall(value)`` Set all bits in the bitarray to bool(value). +``setlist(list, val) -> int`` + Sets the bitarray to the given value for each position given in the list, + and returns the number of bits set. + + ``sort(reverse=False)`` Sort the bits in the array (in-place). diff --git a/bitarray/_bitarray.c b/bitarray/_bitarray.c index d2c19cb9f..c603343c5 100644 --- a/bitarray/_bitarray.c +++ b/bitarray/_bitarray.c @@ -4,6 +4,11 @@ Author: Ilan Schnell */ +#define HAS_VECTORS (defined(__GNUC__) || defined(__clang__)) + +#if HAS_VECTORS +typedef char vec __attribute__((vector_size(16))); +#endif #define PY_SSIZE_T_CLEAN #include "Python.h" @@ -87,13 +92,26 @@ static PyTypeObject Bitarraytype; #define BYTES(bits) (((bits) == 0) ? 0 : (((bits) - 1) / 8 + 1)) -#define BITMASK(endian, i) (((char) 1) << ((endian) ? (7 - (i)%8) : (i)%8)) +#define BITMASK(endian, i) (((unsigned char) 1) << ((endian) ? (7 - (i)%8) : (i)%8)) /* ------------ low level access to bits in bitarrayobject ------------- */ #define GETBIT(self, i) \ ((self)->ob_item[(i) / 8] & BITMASK((self)->endian, i) ? 1 : 0) +#if HAS_VECTORS +/* + * Perform bitwise operation OP on 16 bytes of memory at a time. + */ +#define vector_op(A, B, OP) do { \ + vec __a, __b, __r; \ + memcpy(&__a, A, sizeof(vec)); \ + memcpy(&__b, B, sizeof(vec)); \ + __r = __a OP __b; \ + memcpy(A, &__r, sizeof(vec)); \ +} while(0); +#endif + static void setbit(bitarrayobject *self, idx_t i, int bit) { @@ -327,60 +345,21 @@ repeat(bitarrayobject *self, idx_t n) return 0; } - -enum op_type { - OP_and, - OP_or, - OP_xor, -}; - -/* perform bitwise operation */ -static int -bitwise(bitarrayobject *self, PyObject *arg, enum op_type oper) -{ - bitarrayobject *other; - Py_ssize_t i; - - if (!bitarray_Check(arg)) { - PyErr_SetString(PyExc_TypeError, - "bitarray object expected for bitwise operation"); - return -1; - } - other = (bitarrayobject *) arg; - if (self->nbits != other->nbits) { - PyErr_SetString(PyExc_ValueError, - "bitarrays of equal length expected for bitwise operation"); - return -1; - } - setunused(self); - setunused(other); - switch (oper) { - case OP_and: - for (i = 0; i < Py_SIZE(self); i++) - self->ob_item[i] &= other->ob_item[i]; - break; - case OP_or: - for (i = 0; i < Py_SIZE(self); i++) - self->ob_item[i] |= other->ob_item[i]; - break; - case OP_xor: - for (i = 0; i < Py_SIZE(self); i++) - self->ob_item[i] ^= other->ob_item[i]; - break; - } - return 0; -} - /* set the bits from start to stop (excluding) in self to val */ -static void +idx_t setrange(bitarrayobject *self, idx_t start, idx_t stop, int val) { idx_t i; + idx_t ret = 0; assert(0 <= start && start <= self->nbits); - assert(0 <= stop && stop <= self->nbits); + assert(0 <= stop && stop <= self->nbits); for (i = start; i < stop; i++) + { + ret++; setbit(self, i, val); + } + return ret; } static void @@ -524,7 +503,7 @@ search(bitarrayobject *self, bitarrayobject *xa, idx_t p) static int set_item(bitarrayobject *self, idx_t i, PyObject *v) { - long vi; + int vi; assert(0 <= i && i < self->nbits); vi = PyObject_IsTrue(v); @@ -543,7 +522,7 @@ append_item(bitarrayobject *self, PyObject *item) } static PyObject * -unpack(bitarrayobject *self, char zero, char one) +unpack(bitarrayobject *self, unsigned char zero, unsigned char one) { PyObject *res; Py_ssize_t i; @@ -780,11 +759,11 @@ IntBool_AsInt(PyObject *v) long x; if (PyBool_Check(v)) - return PyObject_IsTrue(v); + return v == Py_True; #ifndef IS_PY3K if (PyInt_Check(v)) { - x = PyInt_AsLong(v); + x = PyInt_AS_LONG(v); } else #endif @@ -898,8 +877,18 @@ slice_GetIndicesEx(PySliceObject *r, idx_t length, Implementation of API methods **************************************************************************/ -static PyObject * +static Py_ssize_t bitarray_length(bitarrayobject *self) +{ + if (self->nbits > PY_SSIZE_T_MAX) { + PyErr_Format(PyExc_OverflowError, "bitarray is too large"); + return -1; + } + return self->nbits; +} + +static PyObject * +bitarray_py_length(bitarrayobject *self) { return PyLong_FromLongLong(self->nbits); } @@ -913,14 +902,6 @@ since __len__ will fail for a bitarray object with 2^31 or more elements\n\ on a 32bit machine, whereas this method will return the correct value,\n\ on 32bit and 64bit machines."); -PyDoc_STRVAR(len_doc, -"__len__() -> int\n\ -\n\ -Return the length, i.e. number of bits stored in the bitarray.\n\ -This method will fail for a bitarray object with 2^31 or more elements\n\ -on a 32bit machine. Use bitarray.length() instead."); - - static PyObject * bitarray_copy(bitarrayobject *self) { @@ -964,7 +945,7 @@ bitarray_index(bitarrayobject *self, PyObject *args) { PyObject *x; idx_t i, start = 0, stop = -1; - long vi; + int vi; if (!PyArg_ParseTuple(args, "O|LL:index", &x, &start, &stop)) return NULL; @@ -1004,17 +985,17 @@ to this method are the same iterable objects which can given to a bitarray\n\ object upon initialization."); -static PyObject * +static int bitarray_contains(bitarrayobject *self, PyObject *x) { - long res; + int res; if (IS_INT_OR_BOOL(x)) { int vi; vi = IntBool_AsInt(x); if (vi < 0) - return NULL; + return -1; res = findfirst(self, vi, 0, -1) >= 0; } else if (bitarray_Check(x)) { @@ -1022,16 +1003,53 @@ bitarray_contains(bitarrayobject *self, PyObject *x) } else { PyErr_SetString(PyExc_TypeError, "bitarray or bool expected"); + return -1; + } + return res; +} + +static PyObject * +bitarray_setlist(bitarrayobject *self, PyObject *args) +{ + PyObject *p = NULL; /* positions to set (evals to true/false) */ + PyObject *v = NULL; /* value to set (evals to true/false) */ + int vi = 0; /* int val to set */ + Py_ssize_t i = 0; /* iteration index */ + Py_ssize_t npos = 0; /* loop size */ + + if (!PyArg_ParseTuple(args, "O!O:_setlist", &PyList_Type, &p, &v)) return NULL; + + vi = PyObject_IsTrue(v); + + npos = PyList_Size(p); + if (npos < 0) + return NULL; /* Not a list */ + + for (i = 0; i < npos; i++) { + PyObject* elem = PyList_GetItem(p, i); + idx_t pos = PyLong_AsLongLong(elem); + if (pos == -1 && PyErr_Occurred()) + return NULL; + if (pos < 0 || pos >= self->nbits) + return PyErr_Format(PyExc_IndexError, + "bitarray index out of range"); + setbit(self, pos, vi); } - return PyBool_FromLong(res); + +#ifdef IS_PY3K + return PyLong_FromSsize_t(npos); +#else + return PyInt_FromSsize_t(npos); +#endif } -PyDoc_STRVAR(contains_doc, -"__contains__(x) -> bool\n\ +PyDoc_STRVAR(setlist_doc, +"setlist(bitarray, list, val) -> int\n\ \n\ -Return True if bitarray contains x, False otherwise.\n\ -The value x may be a boolean (or integer between 0 and 1), or a bitarray."); +Sets the bitarray to the given value for each position given in the list,\n\ +and returns the number of bits set.\n\ +"); static PyObject * @@ -1040,10 +1058,11 @@ bitarray_search(bitarrayobject *self, PyObject *args) PyObject *list = NULL; /* list of matching positions to be returned */ PyObject *x, *item = NULL; Py_ssize_t limit = -1; + idx_t pos = 0; bitarrayobject *xa; - idx_t p; - if (!PyArg_ParseTuple(args, "O|" PY_SSIZE_T_FMT ":_search", &x, &limit)) + if (!PyArg_ParseTuple(args, "O|" PY_SSIZE_T_FMT "L:_search", &x, &limit, + &pos)) return NULL; if (!bitarray_Check(x)) { @@ -1058,19 +1077,20 @@ bitarray_search(bitarrayobject *self, PyObject *args) list = PyList_New(0); if (list == NULL) return NULL; - if (xa->nbits > self->nbits || limit == 0) + if (xa->nbits > self->nbits || limit == 0 || pos > self->nbits) return list; - - p = 0; + if (pos < 0) + pos = 0; + while (1) { - p = search(self, xa, p); - if (p < 0) + pos = search(self, xa, pos); + if (pos < 0) break; - item = PyLong_FromLongLong(p); - p++; + item = PyLong_FromLongLong(pos); + pos++; if (item == NULL || PyList_Append(list, item) < 0) { Py_XDECREF(item); - Py_XDECREF(list); + Py_DECREF(list); return NULL; } Py_DECREF(item); @@ -1081,12 +1101,16 @@ bitarray_search(bitarrayobject *self, PyObject *args) } PyDoc_STRVAR(search_doc, -"search(bitarray, [limit]) -> list\n\ +"search(bitarray, [limit], [pos]) -> list\n\ \n\ Searches for the given a bitarray in self, and returns the start positions\n\ where bitarray matches self as a list.\n\ -The optional argument limits the number of search results to the integer\n\ -specified. By default, all search results are returned."); +The optional 'limit' argument limits the number of search results to the \n\ +integer specified. By default, all search results are returned.\n\ +The optional 'pos' argument begins the search at the position specified.\n\ +By default, search begins at position 0. If no match is found until the end,\n\ +the search will wrap around until reaching the start position again.\n\ +"); static PyObject * @@ -1658,7 +1682,7 @@ use the extend method."); static PyObject * bitarray_unpack(bitarrayobject *self, PyObject *args, PyObject *kwds) { - char zero = 0x00, one = 0xff; + unsigned char zero = 0x00, one = 0xff; static char* kwlist[] = {"zero", "one", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kwds, "|cc:unpack", kwlist, @@ -1797,7 +1821,7 @@ static PyObject * bitarray_remove(bitarrayobject *self, PyObject *v) { idx_t i; - long vi; + int vi; vi = PyObject_IsTrue(v); if (vi < 0) @@ -1823,7 +1847,17 @@ Raises ValueError if item is not present."); /* --------- special methods ----------- */ static PyObject * -bitarray_getitem(bitarrayobject *self, PyObject *a) +bitarray_item(bitarrayobject *self, Py_ssize_t i) +{ + if (i < 0 || i >= self->nbits) { + PyErr_SetString(PyExc_IndexError, "bitarray index out of range"); + return NULL; + } + return PyBool_FromLong(GETBIT(self, i)); +} + +static PyObject * +bitarray_subscript(bitarrayobject *self, PyObject *a) { PyObject *res; idx_t start, stop, step, slicelength, j, i = 0; @@ -1857,6 +1891,18 @@ bitarray_getitem(bitarrayobject *self, PyObject *a) return NULL; } +static int +bitarray_ass_item(bitarrayobject *self, Py_ssize_t i, PyObject *v) +{ + if (i < 0 || i >= self->nbits) { + PyErr_SetString(PyExc_IndexError, "bitarray index out of range"); + return -1; + } + if (v != NULL) + return set_item(self, i, v); + return delete_n(self, i, 1); +} + /* Sets the elements, specified by slice, in self to the value(s) given by v which is either a bitarray or a boolean. */ @@ -1913,62 +1959,36 @@ setslice(bitarrayobject *self, PySliceObject *slice, PyObject *v) return -1; } -static PyObject * -bitarray_setitem(bitarrayobject *self, PyObject *args) +static int +bitarray_ass_subscript(bitarrayobject *self, PyObject *a, PyObject *v) { - PyObject *a, *v; idx_t i = 0; - if (!PyArg_ParseTuple(args, "OO:__setitem__", &a, &v)) - return NULL; - if (IS_INDEX(a)) { if (getIndex(a, &i) < 0) - return NULL; + return -1; if (i < 0) i += self->nbits; if (i < 0 || i >= self->nbits) { PyErr_SetString(PyExc_IndexError, "bitarray index out of range"); - return NULL; + return -1; } - if (set_item(self, i, v) < 0) - return NULL; - Py_RETURN_NONE; + if (v != NULL) + return set_item(self, i, v); + return delete_n(self, i, 1); } if (PySlice_Check(a)) { - if (setslice(self, (PySliceObject *) a, v) < 0) - return NULL; - Py_RETURN_NONE; - } - PyErr_SetString(PyExc_TypeError, "index or slice expected"); - return NULL; -} + idx_t start, stop, step, slicelength, j; -static PyObject * -bitarray_delitem(bitarrayobject *self, PyObject *a) -{ - idx_t start, stop, step, slicelength, j, i = 0; + if (v != NULL) + return setslice(self, (PySliceObject *) a, v); - if (IS_INDEX(a)) { - if (getIndex(a, &i) < 0) - return NULL; - if (i < 0) - i += self->nbits; - if (i < 0 || i >= self->nbits) { - PyErr_SetString(PyExc_IndexError, "bitarray index out of range"); - return NULL; - } - if (delete_n(self, i, 1) < 0) - return NULL; - Py_RETURN_NONE; - } - if (PySlice_Check(a)) { if (slice_GetIndicesEx((PySliceObject *) a, self->nbits, &start, &stop, &step, &slicelength) < 0) { - return NULL; + return -1; } if (slicelength == 0) - Py_RETURN_NONE; + return 0; if (step < 0) { stop = start + 1; @@ -1977,9 +1997,7 @@ bitarray_delitem(bitarrayobject *self, PyObject *a) } if (step == 1) { assert(stop - start == slicelength); - if (delete_n(self, start, slicelength) < 0) - return NULL; - Py_RETURN_NONE; + return delete_n(self, start, slicelength); } /* this is the only complicated part when step > 1 */ for (i = j = start; i < self->nbits; i++) @@ -1987,22 +2005,16 @@ bitarray_delitem(bitarrayobject *self, PyObject *a) setbit(self, j, GETBIT(self, i)); j++; } - if (resize(self, self->nbits - slicelength) < 0) - return NULL; - Py_RETURN_NONE; + return resize(self, self->nbits - slicelength); } PyErr_SetString(PyExc_TypeError, "index or slice expected"); - return NULL; + return -1; } -/* ---------- number methods ---------- */ - static PyObject * -bitarray_add(bitarrayobject *self, PyObject *other) +bitarray_concat(bitarrayobject *self, PyObject *other) { - PyObject *res; - - res = bitarray_copy(self); + PyObject *res = bitarray_copy(self); if (extend_dispatch((bitarrayobject *) res, other) < 0) { Py_DECREF(res); return NULL; @@ -2011,7 +2023,7 @@ bitarray_add(bitarrayobject *self, PyObject *other) } static PyObject * -bitarray_iadd(bitarrayobject *self, PyObject *other) +bitarray_inplace_concat(bitarrayobject *self, PyObject *other) { if (extend_dispatch(self, other) < 0) return NULL; @@ -2020,20 +2032,10 @@ bitarray_iadd(bitarrayobject *self, PyObject *other) } static PyObject * -bitarray_mul(bitarrayobject *self, PyObject *v) +bitarray_repeat(bitarrayobject *self, Py_ssize_t n) { - PyObject *res; - idx_t vi = 0; - - if (!IS_INDEX(v)) { - PyErr_SetString(PyExc_TypeError, - "integer value expected for bitarray repetition"); - return NULL; - } - if (getIndex(v, &vi) < 0) - return NULL; - res = bitarray_copy(self); - if (repeat((bitarrayobject *) res, vi) < 0) { + PyObject *res = bitarray_copy(self); + if (repeat((bitarrayobject *) res, n) < 0) { Py_DECREF(res); return NULL; } @@ -2041,23 +2043,35 @@ bitarray_mul(bitarrayobject *self, PyObject *v) } static PyObject * -bitarray_imul(bitarrayobject *self, PyObject *v) +bitarray_inplace_repeat(bitarrayobject *self, Py_ssize_t n) { - idx_t vi = 0; - - if (!IS_INDEX(v)) { - PyErr_SetString(PyExc_TypeError, - "integer value expected for in-place bitarray repetition"); - return NULL; - } - if (getIndex(v, &vi) < 0) - return NULL; - if (repeat(self, vi) < 0) + if (repeat(self, n) < 0) return NULL; Py_INCREF(self); return (PyObject *) self; } +static PySequenceMethods bitarray_as_sequence = { + (lenfunc)bitarray_length, /* sq_length */ + (binaryfunc)bitarray_concat, /* sq_concat */ + (ssizeargfunc)bitarray_repeat, /* sq_repeat */ + (ssizeargfunc)bitarray_item, /* sq_item */ + 0, /* sq_slice */ + (ssizeobjargproc)bitarray_ass_item, /* sq_ass_item */ + 0, /* sq_ass_slice */ + (objobjproc)bitarray_contains, /* sq_contains */ + (binaryfunc)bitarray_inplace_concat, /* sq_inplace_concat */ + (ssizeargfunc)bitarray_inplace_repeat /* sq_inplace_repeat */ +}; + +static PyMappingMethods bitarray_as_mapping = { + (lenfunc)bitarray_length, + (binaryfunc)bitarray_subscript, + (objobjargproc)bitarray_ass_subscript +}; + +/* ---------- number methods ---------- */ + static PyObject * bitarray_cpinvert(bitarrayobject *self) { @@ -2068,38 +2082,101 @@ bitarray_cpinvert(bitarrayobject *self) return res; } -#define BITWISE_FUNC(oper) \ +#if HAS_VECTORS +#define BITWISE_FUNC_INTERNAL(SELF, OTHER, OP, OPEQ) do { \ + Py_ssize_t i = 0; \ + const Py_ssize_t size = Py_SIZE(SELF); \ + char* self_ob_item = (SELF)->ob_item; \ + const char* other_ob_item = (OTHER)->ob_item; \ + \ + for (; i + sizeof(vec) < size; i += sizeof(vec)) { \ + vector_op(self_ob_item + i, other_ob_item + i, OP); \ + } \ + \ + for (; i < size; ++i) { \ + self_ob_item[i] OPEQ other_ob_item[i]; \ + } \ +} while(0); +#else +#define BITWISE_FUNC_INTERNAL(SELF, OTHER, OP, OPEQ) do { \ + Py_ssize_t i; \ + const Py_ssize_t size = Py_SIZE(SELF); \ + \ + for (i = 0; i < size; ++i) { \ + (SELF)->ob_item[i] OPEQ (OTHER)->ob_item[i]; \ + } \ +} while(0); +#endif + +/* + * Generate function that performs bitwise operations. + **/ +#define BITWISE_FUNC(OPNAME, OP, OPEQ) \ +static int bitwise_ ## OPNAME (bitarrayobject *self, PyObject *arg) \ +{ \ + bitarrayobject *other; \ + \ + if (!bitarray_Check(arg)) { \ + PyErr_SetString(PyExc_TypeError, \ + "bitarray object expected for bitwise operation"); \ + return -1; \ + } \ + \ + other = (bitarrayobject *) arg; \ + \ + if (self->nbits != other->nbits) { \ + PyErr_SetString(PyExc_ValueError, \ + "bitarrays of equal length expected for bitwise operation"); \ + return -1; \ + } \ + \ + setunused(self); \ + setunused(other); \ + \ + BITWISE_FUNC_INTERNAL(self, other, OP, OPEQ); \ + \ + return 0; \ +} + +BITWISE_FUNC(xor, ^, ^=) +BITWISE_FUNC(and, &, &=) +BITWISE_FUNC(or, |, |=) + +#define BITARRAY_FUNC(oper) \ static PyObject * \ bitarray_ ## oper (bitarrayobject *self, PyObject *other) \ { \ PyObject *res; \ \ res = bitarray_copy(self); \ - if (bitwise((bitarrayobject *) res, other, OP_ ## oper) < 0) { \ + \ + if (bitwise_ ## oper((bitarrayobject *) res, other) < 0) { \ Py_DECREF(res); \ return NULL; \ } \ + \ return res; \ } -BITWISE_FUNC(and) -BITWISE_FUNC(or) -BITWISE_FUNC(xor) +BITARRAY_FUNC(and) +BITARRAY_FUNC(or) +BITARRAY_FUNC(xor) -#define BITWISE_IFUNC(oper) \ +#define BITARRAY_IFUNC(oper) \ static PyObject * \ bitarray_i ## oper (bitarrayobject *self, PyObject *other) \ { \ - if (bitwise(self, other, OP_ ## oper) < 0) \ + if (bitwise_ ## oper(self, other) < 0) \ return NULL; \ + \ Py_INCREF(self); \ return (PyObject *) self; \ } -BITWISE_IFUNC(and) -BITWISE_IFUNC(or) -BITWISE_IFUNC(xor) +BITARRAY_IFUNC(and) +BITARRAY_IFUNC(or) +BITARRAY_IFUNC(xor) /******************* variable length encoding and decoding ***************/ @@ -2461,7 +2538,7 @@ bitarray_methods[] = { insert_doc}, {"invert", (PyCFunction) bitarray_invert, METH_NOARGS, invert_doc}, - {"length", (PyCFunction) bitarray_length, METH_NOARGS, + {"length", (PyCFunction) bitarray_py_length, METH_NOARGS, length_doc}, {"pack", (PyCFunction) bitarray_pack, METH_O, pack_doc}, @@ -2473,6 +2550,8 @@ bitarray_methods[] = { reverse_doc}, {"setall", (PyCFunction) bitarray_setall, METH_O, setall_doc}, + {"setlist", (PyCFunction) bitarray_setlist, METH_VARARGS, + setlist_doc}, {"search", (PyCFunction) bitarray_search, METH_VARARGS, search_doc}, {"itersearch", (PyCFunction) bitarray_itersearch, METH_O, @@ -2497,24 +2576,10 @@ bitarray_methods[] = { copy_doc}, {"__deepcopy__", (PyCFunction) bitarray_copy, METH_O, copy_doc}, - {"__len__", (PyCFunction) bitarray_length, METH_NOARGS, - len_doc}, - {"__contains__", (PyCFunction) bitarray_contains, METH_O, - contains_doc}, {"__reduce__", (PyCFunction) bitarray_reduce, METH_NOARGS, reduce_doc}, - /* slice methods */ - {"__delitem__", (PyCFunction) bitarray_delitem, METH_O, 0}, - {"__getitem__", (PyCFunction) bitarray_getitem, METH_O, 0}, - {"__setitem__", (PyCFunction) bitarray_setitem, METH_VARARGS, 0}, - /* number methods */ - {"__add__", (PyCFunction) bitarray_add, METH_O, 0}, - {"__iadd__", (PyCFunction) bitarray_iadd, METH_O, 0}, - {"__mul__", (PyCFunction) bitarray_mul, METH_O, 0}, - {"__rmul__", (PyCFunction) bitarray_mul, METH_O, 0}, - {"__imul__", (PyCFunction) bitarray_imul, METH_O, 0}, {"__and__", (PyCFunction) bitarray_and, METH_O, 0}, {"__or__", (PyCFunction) bitarray_or, METH_O, 0}, {"__xor__", (PyCFunction) bitarray_xor, METH_O, 0}, @@ -2885,8 +2950,8 @@ static PyTypeObject Bitarraytype = { 0, /* tp_compare */ (reprfunc) bitarray_repr, /* tp_repr */ 0, /* tp_as_number*/ - 0, /* tp_as_sequence */ - 0, /* tp_as_mapping */ + &bitarray_as_sequence, /* tp_as_sequence */ + &bitarray_as_mapping, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 000000000..47176bfb0 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,5 @@ +[bdist_wheel] +universal=1 + +[tool:pytest] +testpaths = test \ No newline at end of file diff --git a/setup.py b/setup.py old mode 100644 new mode 100755 index 5eae52743..0fd22a09e --- a/setup.py +++ b/setup.py @@ -1,22 +1,44 @@ +#-*- coding: utf-8 -*- + +""" + ProteusISC + ~~~~~ + + Setup + ````` + + $ pip install . # or python setup.py install +""" + +import codecs +import os import re -from os.path import join from distutils.core import setup, Extension +here = os.path.abspath(os.path.dirname(__file__)) -kwds = {} -kwds['long_description'] = open('README.rst').read() +def read(*parts): + """Taken from pypa pip setup.py: + intentionally *not* adding an encoding option to open, See: + https://github.com/pypa/virtualenv/issues/201#issuecomment-3145690 + """ + return codecs.open(os.path.join(here, *parts), 'r').read() -# Read version from bitarray/__init__.py -pat = re.compile(r'__version__\s*=\s*(\S+)', re.M) -data = open(join('bitarray', '__init__.py')).read() -kwds['version'] = eval(pat.search(data).group(1)) +def find_version(*file_paths): + version_file = read(*file_paths) + version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", + version_file, re.M) + if version_match: + return version_match.group(1) + raise RuntimeError("Unable to find version string.") setup( name = "bitarray", + version=find_version("bitarray", "__init__.py"), author = "Ilan Schnell", author_email = "ilanschnell@gmail.com", - url = "https://github.com/ilanschnell/bitarray", + url = "https://github.com/diamondman/bitarray", license = "PSF", classifiers = [ "License :: OSI Approved :: Python Software Foundation License", @@ -34,11 +56,13 @@ "Programming Language :: Python :: 3.2", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3.5", "Topic :: Utilities", ], description = "efficient arrays of booleans -- C extension", + long_description=open(os.path.join(os.path.dirname(__file__), + 'README.rst')).read(), packages = ["bitarray"], ext_modules = [Extension(name = "bitarray._bitarray", sources = ["bitarray/_bitarray.c"])], - **kwds ) diff --git a/test/bench_bitarray.py b/test/bench_bitarray.py new file mode 100644 index 000000000..c878b62ef --- /dev/null +++ b/test/bench_bitarray.py @@ -0,0 +1,31 @@ +import timeit + +def bench_sequence(): + print('Benchmarking sequence methods') + baseline = [] + for name, setup in [('list', 's = list(range(20));' + + 's1 = [1];' + + 's2 = list(range(1000000))'), + ('bitarray', 'from bitarray import bitarray;' + + 's = bitarray([0, 1]) * 10;' + + 's1 = bitarray([1]);' + + 's2 = bitarray(1000000)')]: + print('=== Testing ' + name) + for i, op in enumerate(['len(s)', '1 in s', + 's[0]', 's[0] = 1', 'del s2[-1]', + 's[1:-1]', 's[-2:-1] = s1', 'del s2[-1:]', + 's + s', 's * 2', 's += s1']): + t = min(timeit.repeat(op, setup)) + if i < len(baseline): + b = t / baseline[i] + else: + b = '' + baseline.append(t) + print('%-24s %.8f\t%s' % (op + ' took:', t, b)) + print('') + +def run(): + bench_sequence() + +if __name__ == '__main__': + run() diff --git a/bitarray/test_bitarray.py b/test/test_bitarray.py similarity index 96% rename from bitarray/test_bitarray.py rename to test/test_bitarray.py index 83aa3487c..9623aeb32 100644 --- a/bitarray/test_bitarray.py +++ b/test/test_bitarray.py @@ -21,8 +21,6 @@ from bitarray import bitarray, bitdiff, bits2bytes, __version__ -tests = [] - if sys.version_info[:2] < (2, 6): def next(x): return x.next() @@ -169,7 +167,6 @@ def test_bits2bytes(self): self.assertEqual(bits2bytes(n), m) -tests.append(TestsModuleFunctions) # --------------------------------------------------------------------------- @@ -324,8 +321,6 @@ def test_WrongArgs(self): self.assertRaises(ValueError, bitarray.__new__, bitarray, 0, 'foo') -tests.append(CreateObjectTests) - # --------------------------------------------------------------------------- class ToObjectsTests(unittest.TestCase, Util): @@ -353,8 +348,6 @@ def test_tuple(self): self.assertEqual(tuple(a), tuple(a.tolist())) -tests.append(ToObjectsTests) - # --------------------------------------------------------------------------- class MetaDataTests(unittest.TestCase): @@ -408,8 +401,6 @@ def test_length(self): self.assertEqual(a.length(), n) -tests.append(MetaDataTests) - # --------------------------------------------------------------------------- class SliceTests(unittest.TestCase, Util): @@ -560,6 +551,21 @@ def test_setslice_to_int(self): slice(None, 2, None), -1) + def test_setlist_to_bool(self): + a = bitarray('11111111') + a.setlist([0, 2, 4, 6], False) + self.assertEqual(a, bitarray('01010101')) + a.setlist([0, 4], True) + self.assertEqual(a, bitarray('11011101')) + + def test_setlist_to_int(self): + a = bitarray('11111111') + a.setlist([0, 2, 4, 6], 0) + self.assertEqual(a, bitarray('01010101')) + a.setlist([0, 4], 1) + self.assertEqual(a, bitarray('11011101')) + + def test_delitem1(self): a = bitarray('100110') del a[1] @@ -589,8 +595,6 @@ def test_delitem2(self): self.assertEQUAL(c, bitarray(cc, endian=c.endian())) -tests.append(SliceTests) - # --------------------------------------------------------------------------- class MiscTests(unittest.TestCase, Util): @@ -750,8 +754,6 @@ def test_overflow(self): self.assertRaises(OverflowError, a.__imul__, 17180) -tests.append(MiscTests) - # --------------------------------------------------------------------------- class SpecialMethodTests(unittest.TestCase, Util): @@ -846,8 +848,6 @@ def test_not_equality(self): self.assertReallyNotEqual(bitarray(''), bitarray('0')) self.assertReallyNotEqual(bitarray('0'), bitarray('1')) -tests.append(SpecialMethodTests) - # --------------------------------------------------------------------------- class NumberTests(unittest.TestCase, Util): @@ -941,8 +941,6 @@ def test_imul(self): self.assertRaises(TypeError, a.__imul__, None) -tests.append(NumberTests) - # --------------------------------------------------------------------------- class BitwiseTests(unittest.TestCase, Util): @@ -1028,8 +1026,6 @@ def test_invert(self): self.check_obj(b) -tests.append(BitwiseTests) - # --------------------------------------------------------------------------- class SequenceTests(unittest.TestCase, Util): @@ -1089,8 +1085,6 @@ def test_contains4(self): self.assertEqual(bitarray(s) in a, r) -tests.append(SequenceTests) - # --------------------------------------------------------------------------- class ExtendTests(unittest.TestCase, Util): @@ -1212,8 +1206,6 @@ def test_string01(self): self.check_obj(c) -tests.append(ExtendTests) - # --------------------------------------------------------------------------- class MethodTests(unittest.TestCase, Util): @@ -1388,6 +1380,19 @@ def test_search3(self): self.assertEqual(list(a.itersearch(b)), res) self.assertEqual([p for p in a.itersearch(b)], res) + def test_search4(self): + ba = bitarray('0011001100110011') + for inc in [1, 2, 3, 4, 5, 6]: + pos = -1 + res = list() + while True: + match = ba.search(bitarray('11'), inc, pos) + if not match: + break + res += match + pos = match[-1] + 1 + self.assertEqual([2, 6, 10, 14], res), inc + self.assertEqual([], ba.search(bitarray('11'), 1, len(ba)+1)) def test_fill(self): a = bitarray('') @@ -1578,8 +1583,6 @@ def test_bytereverse(self): self.check_obj(b) -tests.append(MethodTests) - # --------------------------------------------------------------------------- class StringTests(unittest.TestCase, Util): @@ -1691,8 +1694,6 @@ def test_pack(self): self.assertRaises(TypeError, a.pack, bitarray()) -tests.append(StringTests) - # --------------------------------------------------------------------------- class FileTests(unittest.TestCase, Util): @@ -1899,8 +1900,6 @@ def test_tofile(self): self.assertEqual(c, b) -tests.append(FileTests) - # --------------------------------------------------------------------------- class PrefixCodeTests(unittest.TestCase, Util): @@ -2103,59 +2102,35 @@ def test_real_example(self): self.assertEqual(''.join(a.iterdecode(code)), message) -tests.append(PrefixCodeTests) - # -------------- Buffer Interface (Python 2.7 only for now) ---------------- - -class BufferInterfaceTests(unittest.TestCase): - - def test_read1(self): - a = bitarray('01000001' '01000010' '01000011', endian='big') - v = memoryview(a) - self.assertEqual(len(v), 3) - self.assertEqual(v[0], 'A') - self.assertEqual(v[:].tobytes(), 'ABC') - a[13] = 1 - self.assertEqual(v[:].tobytes(), 'AFC') - - def test_read2(self): - a = bitarray([randint(0, 1) for d in range(8000)]) - v = memoryview(a) - self.assertEqual(len(v), 1000) - b = a[345 * 8 : 657 * 8] - self.assertEqual(v[345:657].tobytes(), b.tobytes()) - self.assertEqual(v[:].tobytes(), a.tobytes()) - - def test_write(self): - a = bitarray(800000) - a.setall(0) - v = memoryview(a) - self.assertFalse(v.readonly) - v[50000] = '\xff' - self.assertEqual(a[399999:400009], bitarray('0111111110')) - a[400003] = 0 - self.assertEqual(a[399999:400009], bitarray('0111011110')) - v[30001:30004] = 'ABC' - self.assertEqual(a[240000:240040].tobytes(), '\x00ABC\x00') - if sys.version_info[:2] == (2, 7): - tests.append(BufferInterfaceTests) - -# --------------------------------------------------------------------------- - -def run(verbosity=1, repeat=1): - print('bitarray is installed in: ' + os.path.dirname(__file__)) - print('bitarray version: ' + __version__) - print(sys.version) - - suite = unittest.TestSuite() - for cls in tests: - for _ in range(repeat): - suite.addTest(unittest.makeSuite(cls)) - - runner = unittest.TextTestRunner(verbosity=verbosity) - return runner.run(suite) - - -if __name__ == '__main__': - run() + class BufferInterfaceTests(unittest.TestCase): + + def test_read1(self): + a = bitarray('01000001' '01000010' '01000011', endian='big') + v = memoryview(a) + self.assertEqual(len(v), 3) + self.assertEqual(v[0], 'A') + self.assertEqual(v[:].tobytes(), 'ABC') + a[13] = 1 + self.assertEqual(v[:].tobytes(), 'AFC') + + def test_read2(self): + a = bitarray([randint(0, 1) for d in range(8000)]) + v = memoryview(a) + self.assertEqual(len(v), 1000) + b = a[345 * 8 : 657 * 8] + self.assertEqual(v[345:657].tobytes(), b.tobytes()) + self.assertEqual(v[:].tobytes(), a.tobytes()) + + def test_write(self): + a = bitarray(800000) + a.setall(0) + v = memoryview(a) + self.assertFalse(v.readonly) + v[50000] = '\xff' + self.assertEqual(a[399999:400009], bitarray('0111111110')) + a[400003] = 0 + self.assertEqual(a[399999:400009], bitarray('0111011110')) + v[30001:30004] = 'ABC' + self.assertEqual(a[240000:240040].tobytes(), '\x00ABC\x00') diff --git a/update_readme.py b/update_readme.py old mode 100644 new mode 100755