pythonのitertools.combinationsやitertools.permutationには無限長のイテレータを渡す事はできません。内部でイテレータをタプルに変換しようとするのでフリーズしてしまいます。
無限長にも対応する、アルゴリズム自体は難しくはありません。
そこで、python-ml-jpで、なぜitertools.combinationsは無限長に対応していないか質問したところ、
タプルの代わりにリストを利用することで、最初に各イテレータから全要素を取り出すことを
やめられるでしょうが、それだとreallocの回数分パフォーマンスが悪くなります。
実際に、無限長のイテレータに対応したバージョンを作ってみました。
>>> from itertools2 import combinations #無限長に対応したバージョン >>> from itertools import count, islice >>> it = combinations(count(), 2) #無限長のを渡してもフリーズしない >>> list(islice(it, 30)) [(0, 1), (0, 2), (1, 2), (0, 3), (1, 3), (2, 3), (0, 4), (1, 4), (2, 4), (3, 4), (0, 5), (1, 5), (2, 5), (3, 5), (4, 5), (0, 6), (1, 6), (2, 6), (3, 6), (4, 6), (5, 6), (0, 7), (1, 7), (2, 7), (3, 7), (4, 7), (5, 7), (6, 7), (0, 8), (1, 8)]
そして、時間測定
from __future__ import division, print_function, unicode_literals import itertools2 import itertools import timeit for mod in ["itertools", "itertools2"]: print(mod) for n, r in [(10, 2), (100, 2), (1000, 2)]: t = timeit.Timer( "for x in combinations(xrange({}), {}):pass".format(n, r), "from {} import combinations".format(mod) ) print(t.timeit(number=100)) print()
測定結果
itertools 0.000719923900943 0.0634376715476 6.90153106051 itertools2 0.000939225516092 0.083500556635 9.52643394621
やっぱり無限長に対応した物の方が遅いです。1.5倍ぐらいずつかかっています。itertoolsのような基礎的なモジュールで50%の速度差は致命的です。しかし、単に私のコードが下手だからという可能性はぬぐえません。
無限シーケンスにcombinationsを使う機会は、実際どれくらいあるものなんでしょう?
以下、ソース。初めて作ったC拡張を作りました。参照関係のデバッグは地獄です。
/* itertools2.c */ #include "Python.h" #include "structmember.h" /* combinations object ************************************************************/ typedef struct { PyObject_HEAD PyObject *it; PyObject *pool; /* input converted to a tuple */ PyObject *elem; Py_ssize_t *indices; /* one index per result element */ Py_ssize_t r; /* size of result tuple */ Py_ssize_t i; /* current axis of indices */ Py_ssize_t first; int stopped; /* set to 1 when the combinations iterator is exhausted */ } combinationsobject; static PyTypeObject combinations_type; int next_indices(Py_ssize_t r, Py_ssize_t n, Py_ssize_t *indices, Py_ssize_t *i) { if (r == 0 || n == 0 || *i < 0 || n <= *i || r < n) { return 0; } while(1) { indices[*i] += 1; if(*i == n - 1 && r <= indices[*i]) { return 0; } else if (*i < n - 1 && indices[*i + 1] <= indices[*i]) { *i += 1; } else if (0 < *i) { *i -= 1; indices[*i] = -1; } else { return 1; } } } static PyObject * combinations_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { combinationsobject *co; Py_ssize_t r; PyObject *iterable = NULL; Py_ssize_t *indices = NULL; PyObject *pool = NULL; static char *kwargs[] = {"iterable", "r", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kwds, "On:combinations", kwargs, &iterable, &r)) { return NULL; } if (r < 0) { PyErr_SetString(PyExc_ValueError, "r must be non-negative"); goto error; } if(r > 0) { indices = PyMem_Malloc((r - 1) * sizeof(Py_ssize_t)); if (indices == NULL) { PyErr_NoMemory(); goto error; } } pool = PyList_New(0); if(pool == NULL) { goto error; } /* create combinationsobject structure */ co = (combinationsobject *) type->tp_alloc(type, 0); if (co == NULL) { goto error; } co->pool = pool; co->indices = indices; co->it = PyObject_GetIter(iterable); co->first = 1; co->r = r; co->stopped = 0; return (PyObject *)co; error: if (indices != NULL) { PyMem_Free(indices); } Py_XDECREF(pool); return NULL; } static void combinations_dealloc(combinationsobject *co) { PyObject_GC_UnTrack(co); Py_XDECREF(co->pool); Py_XDECREF(co->it); Py_XDECREF(co->elem); if (co->indices != NULL) { PyMem_Free(co->indices); } Py_TYPE(co)->tp_free(co); } static int combinations_traverse(combinationsobject *co, visitproc visit, void *arg) { Py_VISIT(co->it); Py_VISIT(co->pool); Py_VISIT(co->elem); return 0; } static PyObject * combinations_next(combinationsobject *co) { Py_ssize_t j; PyObject *result; Py_ssize_t *indices = co->indices; Py_ssize_t *i = &co->i; PyObject *pool = co->pool; PyObject *it = co->it; Py_ssize_t r = co->r; if (co->stopped) { return NULL; } else if (r == 0) { co->first = 0; co->stopped = 1; result = PyTuple_New(0); if (result == NULL) { goto empty; } return result; } else if(r == 1) { PyObject *x = NULL; co->first = 0; x = PyIter_Next(it); if(x == NULL) { goto empty; } result = PyTuple_New(1); if (result == NULL) { goto empty; } PyTuple_SetItem(result, 0, x); return result; } else { if (co->first) { PyObject *x = NULL; co->first = 0; for(j=0; j < r - 1; ++j) { x = PyIter_Next(it); if(x == NULL) { goto empty; } if (PyList_Append(pool, x) == -1) { goto empty; } Py_DECREF(x); } x = PyIter_Next(it); if(x == NULL) { goto empty; } co->elem = x; for (j=0 ; j < r - 1 ; j++) { indices[j] = -1; } *i = r - 2; } while(1) { if(next_indices(PyList_Size(pool), r - 1, indices, i)) { PyObject *x; result = PyTuple_New(r); if (result == NULL) { goto empty; } for(j=0; j < r - 1; ++j) { x = PyList_GetItem(pool, indices[j]); if(x == NULL) { goto empty; } Py_INCREF(x); PyTuple_SetItem(result, j, x); } Py_INCREF(co->elem); PyTuple_SetItem(result, r - 1, co->elem); return result; } else { if (PyList_Append(pool, co->elem) == -1) { goto empty; } Py_DECREF(co->elem); co->elem = PyIter_Next(it); if(co->elem == NULL) { goto empty; } for (j=0 ; j < r - 1 ; j++) { indices[j] = -1; } *i = r - 2; } } } empty: //Py_XDECREF(result); co->stopped = 1; return NULL; } PyDoc_STRVAR(combinations_doc, "combinations(iterable, r) --> combinations object\n\ \n\ Return successive r-length combinations of elements in the iterable.\n\n\ combinations(range(4), 3) --> (0,1,2), (0,1,3), (0,2,3), (1,2,3)"); static PyTypeObject combinations_type = { PyVarObject_HEAD_INIT(NULL, 0) "itertools2.combinations", /* tp_name */ sizeof(combinationsobject), /* tp_basicsize */ 0, /* tp_itemsize */ /* methods */ (destructor)combinations_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ PyObject_GenericGetAttr, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE, /* tp_flags */ combinations_doc, /* tp_doc */ (traverseproc)combinations_traverse, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ PyObject_SelfIter, /* tp_iter */ (iternextfunc)combinations_next, /* tp_iternext */ 0, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ 0, /* tp_init */ 0, /* tp_alloc */ combinations_new, /* tp_new */ PyObject_GC_Del, /* tp_free */ }; /* module level code ********************************************************/ PyDoc_STRVAR(module_doc, ""); static PyMethodDef module_methods[] = { {NULL, NULL} /* sentinel */ }; PyMODINIT_FUNC inititertools2(void) { int i; PyObject *m; char *name; PyTypeObject *typelist[] = { &combinations_type, NULL }; m = Py_InitModule3("itertools2", module_methods, module_doc); if (m == NULL) return; for (i=0 ; typelist[i] != NULL ; i++) { if (PyType_Ready(typelist[i]) < 0) return; name = strchr(typelist[i]->tp_name, '.'); assert (name != NULL); Py_INCREF(typelist[i]); PyModule_AddObject(m, name+1, (PyObject *)typelist[i]); } }