mirror of
https://github.com/python/cpython.git
synced 2025-12-04 08:34:25 +00:00
Improvements to collections.deque():
* Add doctests for the examples in the library reference. * Add two methods, left() and right(), modeled after deques in C++ STL. * Apply the new method to asynchat.py. * Add comparison operators to make deques more substitutable for lists. * Replace the LookupErrors with IndexErrors to more closely match lists.
This commit is contained in:
parent
fe99927630
commit
738ec90ca1
4 changed files with 229 additions and 18 deletions
|
|
@ -54,14 +54,24 @@ Deque objects support the following methods:
|
||||||
reversing the order of elements in the iterable argument.
|
reversing the order of elements in the iterable argument.
|
||||||
\end{methoddesc}
|
\end{methoddesc}
|
||||||
|
|
||||||
|
\begin{methoddesc}{left}{}
|
||||||
|
Return leftmost element from the deque.
|
||||||
|
If no elements are present, raises a \exception{IndexError}.
|
||||||
|
\end{methoddesc}
|
||||||
|
|
||||||
\begin{methoddesc}{pop}{}
|
\begin{methoddesc}{pop}{}
|
||||||
Remove and return an element from the right side of the deque.
|
Remove and return an element from the right side of the deque.
|
||||||
If no elements are present, raises a \exception{LookupError}.
|
If no elements are present, raises a \exception{IndexError}.
|
||||||
\end{methoddesc}
|
\end{methoddesc}
|
||||||
|
|
||||||
\begin{methoddesc}{popleft}{}
|
\begin{methoddesc}{popleft}{}
|
||||||
Remove and return an element from the left side of the deque.
|
Remove and return an element from the left side of the deque.
|
||||||
If no elements are present, raises a \exception{LookupError}.
|
If no elements are present, raises a \exception{IndexError}.
|
||||||
|
\end{methoddesc}
|
||||||
|
|
||||||
|
\begin{methoddesc}{right}{}
|
||||||
|
Return the rightmost element from the deque.
|
||||||
|
If no elements are present, raises a \exception{IndexError}.
|
||||||
\end{methoddesc}
|
\end{methoddesc}
|
||||||
|
|
||||||
\begin{methoddesc}{rotate}{n}
|
\begin{methoddesc}{rotate}{n}
|
||||||
|
|
@ -80,22 +90,27 @@ Example:
|
||||||
>>> from collections import deque
|
>>> from collections import deque
|
||||||
>>> d = deque('ghi') # make a new deque with three items
|
>>> d = deque('ghi') # make a new deque with three items
|
||||||
>>> for elem in d: # iterate over the deque's elements
|
>>> for elem in d: # iterate over the deque's elements
|
||||||
print elem.upper()
|
... print elem.upper()
|
||||||
|
|
||||||
|
|
||||||
G
|
G
|
||||||
H
|
H
|
||||||
I
|
I
|
||||||
|
|
||||||
>>> d.append('j') # add a new entry to the right side
|
>>> d.append('j') # add a new entry to the right side
|
||||||
>>> d.appendleft('f') # add a new entry to the left side
|
>>> d.appendleft('f') # add a new entry to the left side
|
||||||
>>> d # show the representation of the deque
|
>>> d # show the representation of the deque
|
||||||
deque(['f', 'g', 'h', 'i', 'j'])
|
deque(['f', 'g', 'h', 'i', 'j'])
|
||||||
|
|
||||||
>>> d.pop() # return and remove the rightmost item
|
>>> d.pop() # return and remove the rightmost item
|
||||||
'j'
|
'j'
|
||||||
>>> d.popleft() # return and remove the leftmost item
|
>>> d.popleft() # return and remove the leftmost item
|
||||||
'f'
|
'f'
|
||||||
>>> list(d) # list the contents of the deque
|
>>> list(d) # list the contents of the deque
|
||||||
['g', 'h', 'i']
|
['g', 'h', 'i']
|
||||||
|
|
||||||
|
>>> d.left() # peek at leftmost item
|
||||||
|
'g'
|
||||||
|
>>> d.right() # peek at rightmost item
|
||||||
|
'i'
|
||||||
>>> list(reversed(d)) # list the contents of a deque in reverse
|
>>> list(reversed(d)) # list the contents of a deque in reverse
|
||||||
['i', 'h', 'g']
|
['i', 'h', 'g']
|
||||||
>>> 'h' in d # search the deque
|
>>> 'h' in d # search the deque
|
||||||
|
|
@ -109,15 +124,15 @@ deque(['l', 'g', 'h', 'i', 'j', 'k'])
|
||||||
>>> d.rotate(-1) # left rotation
|
>>> d.rotate(-1) # left rotation
|
||||||
>>> d
|
>>> d
|
||||||
deque(['g', 'h', 'i', 'j', 'k', 'l'])
|
deque(['g', 'h', 'i', 'j', 'k', 'l'])
|
||||||
|
|
||||||
>>> deque(reversed(d)) # make a new deque in reverse order
|
>>> deque(reversed(d)) # make a new deque in reverse order
|
||||||
deque(['l', 'k', 'j', 'i', 'h', 'g'])
|
deque(['l', 'k', 'j', 'i', 'h', 'g'])
|
||||||
>>> d.clear() # empty the deque
|
>>> d.clear() # empty the deque
|
||||||
>>> d.pop() # cannot pop from an empty deque
|
>>> d.pop() # cannot pop from an empty deque
|
||||||
|
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
File "<pyshell#6>", line 1, in -toplevel-
|
File "<pyshell#6>", line 1, in -toplevel-
|
||||||
d.pop()
|
d.pop()
|
||||||
LookupError: pop from an empty deque
|
IndexError: pop from an empty deque
|
||||||
|
|
||||||
>>> d.extendleft('abc') # extendleft() reverses the input order
|
>>> d.extendleft('abc') # extendleft() reverses the input order
|
||||||
>>> d
|
>>> d
|
||||||
|
|
|
||||||
|
|
@ -262,11 +262,7 @@ class fifo:
|
||||||
return self.list == []
|
return self.list == []
|
||||||
|
|
||||||
def first (self):
|
def first (self):
|
||||||
it = iter(self.list)
|
return self.list.left()
|
||||||
try:
|
|
||||||
return it.next()
|
|
||||||
except StopIteration:
|
|
||||||
raise IndexError
|
|
||||||
|
|
||||||
def push (self, data):
|
def push (self, data):
|
||||||
self.list.append(data)
|
self.list.append(data)
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,23 @@ class TestBasic(unittest.TestCase):
|
||||||
self.assertEqual(right, range(150, 400))
|
self.assertEqual(right, range(150, 400))
|
||||||
self.assertEqual(list(d), range(50, 150))
|
self.assertEqual(list(d), range(50, 150))
|
||||||
|
|
||||||
|
def test_comparisons(self):
|
||||||
|
d = deque('xabc'); d.popleft()
|
||||||
|
for e in [d, deque('abc'), deque('ab'), deque(), list(d)]:
|
||||||
|
self.assertEqual(d==e, type(d)==type(e) and list(d)==list(e))
|
||||||
|
self.assertEqual(d!=e, not(type(d)==type(e) and list(d)==list(e)))
|
||||||
|
|
||||||
|
args = map(deque, ('', 'a', 'b', 'ab', 'ba', 'abc', 'xba', 'xabc', 'cba'))
|
||||||
|
for x in args:
|
||||||
|
for y in args:
|
||||||
|
self.assertEqual(x == y, list(x) == list(y), (x,y))
|
||||||
|
self.assertEqual(x != y, list(x) != list(y), (x,y))
|
||||||
|
self.assertEqual(x < y, list(x) < list(y), (x,y))
|
||||||
|
self.assertEqual(x <= y, list(x) <= list(y), (x,y))
|
||||||
|
self.assertEqual(x > y, list(x) > list(y), (x,y))
|
||||||
|
self.assertEqual(x >= y, list(x) >= list(y), (x,y))
|
||||||
|
self.assertEqual(cmp(x,y), cmp(list(x),list(y)), (x,y))
|
||||||
|
|
||||||
def test_extend(self):
|
def test_extend(self):
|
||||||
d = deque('a')
|
d = deque('a')
|
||||||
self.assertRaises(TypeError, d.extend, 1)
|
self.assertRaises(TypeError, d.extend, 1)
|
||||||
|
|
@ -40,6 +57,14 @@ class TestBasic(unittest.TestCase):
|
||||||
d.extendleft('bcd')
|
d.extendleft('bcd')
|
||||||
self.assertEqual(list(d), list(reversed('abcd')))
|
self.assertEqual(list(d), list(reversed('abcd')))
|
||||||
|
|
||||||
|
def test_leftright(self):
|
||||||
|
d = deque('superman')
|
||||||
|
self.assertEqual(d.left(), 's')
|
||||||
|
self.assertEqual(d.right(), 'n')
|
||||||
|
d = deque()
|
||||||
|
self.assertRaises(IndexError, d.left)
|
||||||
|
self.assertRaises(IndexError, d.right)
|
||||||
|
|
||||||
def test_rotate(self):
|
def test_rotate(self):
|
||||||
s = tuple('abcde')
|
s = tuple('abcde')
|
||||||
n = len(s)
|
n = len(s)
|
||||||
|
|
@ -93,7 +118,7 @@ class TestBasic(unittest.TestCase):
|
||||||
self.assertEqual(len(d), 1)
|
self.assertEqual(len(d), 1)
|
||||||
d.pop()
|
d.pop()
|
||||||
self.assertEqual(len(d), 0)
|
self.assertEqual(len(d), 0)
|
||||||
self.assertRaises(LookupError, d.pop)
|
self.assertRaises(IndexError, d.pop)
|
||||||
self.assertEqual(len(d), 0)
|
self.assertEqual(len(d), 0)
|
||||||
d.append('c')
|
d.append('c')
|
||||||
self.assertEqual(len(d), 1)
|
self.assertEqual(len(d), 1)
|
||||||
|
|
@ -104,8 +129,8 @@ class TestBasic(unittest.TestCase):
|
||||||
|
|
||||||
def test_underflow(self):
|
def test_underflow(self):
|
||||||
d = deque()
|
d = deque()
|
||||||
self.assertRaises(LookupError, d.pop)
|
self.assertRaises(IndexError, d.pop)
|
||||||
self.assertRaises(LookupError, d.popleft)
|
self.assertRaises(IndexError, d.popleft)
|
||||||
|
|
||||||
def test_clear(self):
|
def test_clear(self):
|
||||||
d = deque(xrange(100))
|
d = deque(xrange(100))
|
||||||
|
|
@ -374,6 +399,63 @@ class TestSubclass(unittest.TestCase):
|
||||||
|
|
||||||
#==============================================================================
|
#==============================================================================
|
||||||
|
|
||||||
|
libreftest = """
|
||||||
|
Example from the Library Reference: Doc/lib/libcollections.tex
|
||||||
|
|
||||||
|
>>> from collections import deque
|
||||||
|
>>> d = deque('ghi') # make a new deque with three items
|
||||||
|
>>> for elem in d: # iterate over the deque's elements
|
||||||
|
... print elem.upper()
|
||||||
|
G
|
||||||
|
H
|
||||||
|
I
|
||||||
|
>>> d.append('j') # add a new entry to the right side
|
||||||
|
>>> d.appendleft('f') # add a new entry to the left side
|
||||||
|
>>> d # show the representation of the deque
|
||||||
|
deque(['f', 'g', 'h', 'i', 'j'])
|
||||||
|
>>> d.pop() # return and remove the rightmost item
|
||||||
|
'j'
|
||||||
|
>>> d.popleft() # return and remove the leftmost item
|
||||||
|
'f'
|
||||||
|
>>> list(d) # list the contents of the deque
|
||||||
|
['g', 'h', 'i']
|
||||||
|
>>> d.left() # peek at leftmost item
|
||||||
|
'g'
|
||||||
|
>>> d.right() # peek at rightmost item
|
||||||
|
'i'
|
||||||
|
>>> list(reversed(d)) # list the contents of a deque in reverse
|
||||||
|
['i', 'h', 'g']
|
||||||
|
>>> 'h' in d # search the deque
|
||||||
|
True
|
||||||
|
>>> d.extend('jkl') # add multiple elements at once
|
||||||
|
>>> d
|
||||||
|
deque(['g', 'h', 'i', 'j', 'k', 'l'])
|
||||||
|
>>> d.rotate(1) # right rotation
|
||||||
|
>>> d
|
||||||
|
deque(['l', 'g', 'h', 'i', 'j', 'k'])
|
||||||
|
>>> d.rotate(-1) # left rotation
|
||||||
|
>>> d
|
||||||
|
deque(['g', 'h', 'i', 'j', 'k', 'l'])
|
||||||
|
>>> deque(reversed(d)) # make a new deque in reverse order
|
||||||
|
deque(['l', 'k', 'j', 'i', 'h', 'g'])
|
||||||
|
>>> d.clear() # empty the deque
|
||||||
|
>>> d.pop() # cannot pop from an empty deque
|
||||||
|
Traceback (most recent call last):
|
||||||
|
File "<pyshell#6>", line 1, in -toplevel-
|
||||||
|
d.pop()
|
||||||
|
IndexError: pop from an empty deque
|
||||||
|
|
||||||
|
>>> d.extendleft('abc') # extendleft() reverses the input order
|
||||||
|
>>> d
|
||||||
|
deque(['c', 'b', 'a'])
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
#==============================================================================
|
||||||
|
|
||||||
|
__test__ = {'libreftest' : libreftest}
|
||||||
|
|
||||||
def test_main(verbose=None):
|
def test_main(verbose=None):
|
||||||
import sys
|
import sys
|
||||||
from test import test_sets
|
from test import test_sets
|
||||||
|
|
@ -395,5 +477,9 @@ def test_main(verbose=None):
|
||||||
counts[i] = sys.gettotalrefcount()
|
counts[i] = sys.gettotalrefcount()
|
||||||
print counts
|
print counts
|
||||||
|
|
||||||
|
# doctests
|
||||||
|
from test import test_deque
|
||||||
|
test_support.run_doctest(test_deque, verbose)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
test_main(verbose=True)
|
test_main(verbose=True)
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,8 @@ typedef struct {
|
||||||
int len;
|
int len;
|
||||||
} dequeobject;
|
} dequeobject;
|
||||||
|
|
||||||
|
PyTypeObject deque_type;
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
deque_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
|
deque_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
|
||||||
{
|
{
|
||||||
|
|
@ -109,7 +111,7 @@ deque_pop(dequeobject *deque, PyObject *unused)
|
||||||
block *prevblock;
|
block *prevblock;
|
||||||
|
|
||||||
if (deque->len == 0) {
|
if (deque->len == 0) {
|
||||||
PyErr_SetString(PyExc_LookupError, "pop from an empty deque");
|
PyErr_SetString(PyExc_IndexError, "pop from an empty deque");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
item = deque->rightblock->data[deque->rightindex];
|
item = deque->rightblock->data[deque->rightindex];
|
||||||
|
|
@ -144,7 +146,7 @@ deque_popleft(dequeobject *deque, PyObject *unused)
|
||||||
block *prevblock;
|
block *prevblock;
|
||||||
|
|
||||||
if (deque->len == 0) {
|
if (deque->len == 0) {
|
||||||
PyErr_SetString(PyExc_LookupError, "pop from an empty deque");
|
PyErr_SetString(PyExc_IndexError, "pop from an empty deque");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
item = deque->leftblock->data[deque->leftindex];
|
item = deque->leftblock->data[deque->leftindex];
|
||||||
|
|
@ -174,6 +176,38 @@ deque_popleft(dequeobject *deque, PyObject *unused)
|
||||||
|
|
||||||
PyDoc_STRVAR(popleft_doc, "Remove and return the leftmost element.");
|
PyDoc_STRVAR(popleft_doc, "Remove and return the leftmost element.");
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
deque_right(dequeobject *deque, PyObject *unused)
|
||||||
|
{
|
||||||
|
PyObject *item;
|
||||||
|
|
||||||
|
if (deque->len == 0) {
|
||||||
|
PyErr_SetString(PyExc_IndexError, "deque is empty");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
item = deque->rightblock->data[deque->rightindex];
|
||||||
|
Py_INCREF(item);
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyDoc_STRVAR(right_doc, "Return the rightmost element.");
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
deque_left(dequeobject *deque, PyObject *unused)
|
||||||
|
{
|
||||||
|
PyObject *item;
|
||||||
|
|
||||||
|
if (deque->len == 0) {
|
||||||
|
PyErr_SetString(PyExc_IndexError, "deque is empty");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
item = deque->leftblock->data[deque->leftindex];
|
||||||
|
Py_INCREF(item);
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyDoc_STRVAR(left_doc, "Return the leftmost element.");
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
deque_extend(dequeobject *deque, PyObject *iterable)
|
deque_extend(dequeobject *deque, PyObject *iterable)
|
||||||
{
|
{
|
||||||
|
|
@ -467,6 +501,82 @@ deque_tp_print(PyObject *deque, FILE *fp, int flags)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
deque_richcompare(PyObject *v, PyObject *w, int op)
|
||||||
|
{
|
||||||
|
PyObject *it1=NULL, *it2=NULL, *x, *y;
|
||||||
|
int i, b, vs, ws, minlen, cmp=-1;
|
||||||
|
|
||||||
|
if (v->ob_type != &deque_type || w->ob_type != &deque_type) {
|
||||||
|
Py_INCREF(Py_NotImplemented);
|
||||||
|
return Py_NotImplemented;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Shortcuts */
|
||||||
|
vs = ((dequeobject *)v)->len;
|
||||||
|
ws = ((dequeobject *)w)->len;
|
||||||
|
if (op == Py_EQ) {
|
||||||
|
if (v == w)
|
||||||
|
Py_RETURN_TRUE;
|
||||||
|
if (vs != ws)
|
||||||
|
Py_RETURN_FALSE;
|
||||||
|
}
|
||||||
|
if (op == Py_NE) {
|
||||||
|
if (v == w)
|
||||||
|
Py_RETURN_FALSE;
|
||||||
|
if (vs != ws)
|
||||||
|
Py_RETURN_TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Search for the first index where items are different */
|
||||||
|
it1 = PyObject_GetIter(v);
|
||||||
|
if (it1 == NULL)
|
||||||
|
goto done;
|
||||||
|
it2 = PyObject_GetIter(w);
|
||||||
|
if (it2 == NULL)
|
||||||
|
goto done;
|
||||||
|
minlen = (vs < ws) ? vs : ws;
|
||||||
|
for (i=0 ; i < minlen ; i++) {
|
||||||
|
x = PyIter_Next(it1);
|
||||||
|
if (x == NULL)
|
||||||
|
goto done;
|
||||||
|
y = PyIter_Next(it2);
|
||||||
|
if (y == NULL) {
|
||||||
|
Py_DECREF(x);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
b = PyObject_RichCompareBool(x, y, Py_EQ);
|
||||||
|
if (b == 0) {
|
||||||
|
cmp = PyObject_RichCompareBool(x, y, op);
|
||||||
|
Py_DECREF(x);
|
||||||
|
Py_DECREF(y);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
Py_DECREF(x);
|
||||||
|
Py_DECREF(y);
|
||||||
|
if (b == -1)
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
/* Elements are equal through minlen. The longest input is the greatest */
|
||||||
|
switch (op) {
|
||||||
|
case Py_LT: cmp = vs < ws; break;
|
||||||
|
case Py_LE: cmp = vs <= ws; break;
|
||||||
|
case Py_EQ: cmp = vs == ws; break;
|
||||||
|
case Py_NE: cmp = vs != ws; break;
|
||||||
|
case Py_GT: cmp = vs > ws; break;
|
||||||
|
case Py_GE: cmp = vs >= ws; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
done:
|
||||||
|
Py_XDECREF(it1);
|
||||||
|
Py_XDECREF(it2);
|
||||||
|
if (cmp == 1)
|
||||||
|
Py_RETURN_TRUE;
|
||||||
|
if (cmp == 0)
|
||||||
|
Py_RETURN_FALSE;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
deque_init(dequeobject *deque, PyObject *args, PyObject *kwds)
|
deque_init(dequeobject *deque, PyObject *args, PyObject *kwds)
|
||||||
{
|
{
|
||||||
|
|
@ -509,6 +619,8 @@ static PyMethodDef deque_methods[] = {
|
||||||
METH_O, extend_doc},
|
METH_O, extend_doc},
|
||||||
{"extendleft", (PyCFunction)deque_extendleft,
|
{"extendleft", (PyCFunction)deque_extendleft,
|
||||||
METH_O, extendleft_doc},
|
METH_O, extendleft_doc},
|
||||||
|
{"left", (PyCFunction)deque_left,
|
||||||
|
METH_NOARGS, left_doc},
|
||||||
{"pop", (PyCFunction)deque_pop,
|
{"pop", (PyCFunction)deque_pop,
|
||||||
METH_NOARGS, pop_doc},
|
METH_NOARGS, pop_doc},
|
||||||
{"popleft", (PyCFunction)deque_popleft,
|
{"popleft", (PyCFunction)deque_popleft,
|
||||||
|
|
@ -517,6 +629,8 @@ static PyMethodDef deque_methods[] = {
|
||||||
METH_NOARGS, reduce_doc},
|
METH_NOARGS, reduce_doc},
|
||||||
{"__reversed__", (PyCFunction)deque_reviter,
|
{"__reversed__", (PyCFunction)deque_reviter,
|
||||||
METH_NOARGS, reversed_doc},
|
METH_NOARGS, reversed_doc},
|
||||||
|
{"right", (PyCFunction)deque_right,
|
||||||
|
METH_NOARGS, right_doc},
|
||||||
{"rotate", (PyCFunction)deque_rotate,
|
{"rotate", (PyCFunction)deque_rotate,
|
||||||
METH_VARARGS, rotate_doc},
|
METH_VARARGS, rotate_doc},
|
||||||
{NULL, NULL} /* sentinel */
|
{NULL, NULL} /* sentinel */
|
||||||
|
|
@ -553,7 +667,7 @@ PyTypeObject deque_type = {
|
||||||
deque_doc, /* tp_doc */
|
deque_doc, /* tp_doc */
|
||||||
(traverseproc)set_traverse, /* tp_traverse */
|
(traverseproc)set_traverse, /* tp_traverse */
|
||||||
(inquiry)deque_clear, /* tp_clear */
|
(inquiry)deque_clear, /* tp_clear */
|
||||||
0, /* tp_richcompare */
|
(richcmpfunc)deque_richcompare, /* tp_richcompare */
|
||||||
0, /* tp_weaklistoffset*/
|
0, /* tp_weaklistoffset*/
|
||||||
(getiterfunc)deque_iter, /* tp_iter */
|
(getiterfunc)deque_iter, /* tp_iter */
|
||||||
0, /* tp_iternext */
|
0, /* tp_iternext */
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue