diff -r cca2ed4e8b41 Lib/collections/__init__.py --- a/Lib/collections/__init__.py Thu Dec 17 10:35:05 2015 +0000 +++ b/Lib/collections/__init__.py Fri Dec 18 02:31:23 2015 +0100 @@ -43,6 +43,11 @@ except ImportError: pass +try: + from _collections import has_subsequence +except ImportError: + pass + ################################################################################ ### OrderedDict diff -r cca2ed4e8b41 Lib/test/test_collections.py --- a/Lib/test/test_collections.py Thu Dec 17 10:35:05 2015 +0000 +++ b/Lib/test/test_collections.py Fri Dec 18 02:31:23 2015 +0100 @@ -19,6 +19,7 @@ from collections import UserDict, UserString, UserList from collections import ChainMap from collections import deque +from collections import has_subsequence from collections.abc import Awaitable, Coroutine, AsyncIterator, AsyncIterable from collections.abc import Hashable, Iterable, Iterator, Generator from collections.abc import Sized, Container, Callable @@ -1622,6 +1623,54 @@ self.assertEqual(dict(c), {'a': 5, 'b': 2, 'c': 1, 'd': 1, 'r':2 }) +class BuggyInt(int): + def __eq__(self, other): + raise ValueError('spam') + + +class TestHasSubsequence(unittest.TestCase): + def test_numbers(self): + self.assertTrue(has_subsequence([1, 2, 3, 4, 5], [1, 2, 3])) + self.assertFalse(has_subsequence([1, 2, 3, 4, 5], [1, 2, 4])) + self.assertTrue(has_subsequence([1, 2, 4, 1, 2, 5, 1, 2, 6], [2, 6])) + self.assertTrue(has_subsequence([1, 2, 3, 4, 5], [3])) + self.assertTrue(has_subsequence([1, 2, 3, 4, 5], [])) + self.assertTrue(has_subsequence([], [])) + self.assertFalse(has_subsequence([], [1, 2, 3])) + self.assertTrue(has_subsequence([1, 2, 3], [1, 2, 3])) + self.assertFalse(has_subsequence([1, 2, 3], [3, 2, 1])) + self.assertFalse(has_subsequence([1, 2, 3, 4, 5], [1, 2, 3, 4, 5, 6])) + + def test_strings(self): + self.assertFalse(has_subsequence(['xx', 'y', 'zz'], ['y', 'z'])) + self.assertTrue(has_subsequence(['x', 'y', 'z'], ['y', 'z'])) + + def test_tuples(self): + self.assertTrue(has_subsequence((1, 2, 3), (3,))) + self.assertTrue(has_subsequence((1, 2, 3), [3])) + self.assertFalse(has_subsequence((1, 2, 3), (4,))) + self.assertTrue(has_subsequence((1, 2, 3), ())) + self.assertFalse(has_subsequence((1, 2, 3), (2, 3, 4))) + + def test_inherited(self): + self.assertTrue(has_subsequence(UserList([1, 2, 3]), [1])) + self.assertTrue(has_subsequence([1, 2, 3], UserList([1]))) + self.assertTrue(has_subsequence(UserList([1, 2, 3]), UserList([1]))) + + def test_exceptions(self): + with self.assertRaises(TypeError): + has_subsequence() + has_subsequence([1, 2, 3]) + has_subsequence([1, 2, 3], 3) + has_subsequence((1, 2, 3), 3) + has_subsequence({1, 2, 3}, [3]) + has_subsequence([1, 2, 3], {3}) + has_subsequence([1, 2, 3], {3: 'spam'}) + with self.assertRaises(ValueError): + # Raised by BuggyInt.__eq__() + has_subsequence([1, 2, 3], [BuggyInt(3)]) + + ################################################################################ ### Run tests ################################################################################ @@ -1630,7 +1679,7 @@ NamedTupleDocs = doctest.DocTestSuite(module=collections) test_classes = [TestNamedTuple, NamedTupleDocs, TestOneTrickPonyABCs, TestCollectionABCs, TestCounter, TestChainMap, - TestUserObjects, + TestUserObjects, TestHasSubsequence, ] support.run_unittest(*test_classes) support.run_doctest(collections, verbose) diff -r cca2ed4e8b41 Modules/_collectionsmodule.c --- a/Modules/_collectionsmodule.c Thu Dec 17 10:35:05 2015 +0000 +++ b/Modules/_collectionsmodule.c Fri Dec 18 02:31:23 2015 +0100 @@ -2317,6 +2317,76 @@ Py_RETURN_NONE; } + +PyDoc_STRVAR(has_subsequence_doc, +"has_subsequence(seq, subseq) -> bool\n\ +\n\ +Check whether subseq is a subsequence of seq"); + +static PyObject * +has_subsequence(PyObject *self, PyObject *args) +{ + Py_ssize_t i, j, k, stop, seqlen, sublen; + PyObject *seq, *subseq, *fast_seq, *fast_subseq, *result; + PyObject **seq_items, **subseq_items; + int equal; + + if (!PyArg_UnpackTuple(args, "has_subsequence", 2, 2, &seq, &subseq)) + return NULL; + + if (!PySequence_Check(seq) || !PySequence_Check(subseq)) { + PyErr_SetString(PyExc_TypeError, "arguments must be sequences"); + return NULL; + } + fast_seq = PySequence_Fast(seq, "seq must be iterable"); + if (fast_seq == NULL) + return NULL; + + fast_subseq = PySequence_Fast(subseq, "subseq must be iterable"); + if (fast_subseq == NULL) { + Py_DECREF(fast_seq); + return NULL; + } + seqlen = PySequence_Fast_GET_SIZE(fast_seq); + sublen = PySequence_Fast_GET_SIZE(fast_subseq); + stop = seqlen - sublen + 1; + + if (sublen == 0) { + result = Py_True; + goto done; + } + if (sublen > seqlen) { + result = Py_False; + goto done; + } + seq_items = PySequence_Fast_ITEMS(fast_seq); + subseq_items = PySequence_Fast_ITEMS(fast_subseq); + + for (i = 0; i < stop; i++) { + for (j = 0, k = i; j < sublen; j++, k++) { + equal = PyObject_RichCompareBool(subseq_items[j], + seq_items[k], Py_EQ); + if (!equal) + break; + if (equal < 0) { + result = NULL; + goto done; + } + } + if (j == sublen) { + result = Py_True; + goto done; + } + } + result = Py_False; + + done: + Py_DECREF(fast_seq); + Py_DECREF(fast_subseq); + Py_XINCREF(result); + return result; +} + /* module level code ********************************************************/ PyDoc_STRVAR(module_doc, @@ -2327,6 +2397,7 @@ static struct PyMethodDef module_functions[] = { {"_count_elements", _count_elements, METH_VARARGS, _count_elements_doc}, + {"has_subsequence", has_subsequence, METH_VARARGS, has_subsequence_doc}, {NULL, NULL} /* sentinel */ };