The :func:`research` function uses :func:`remap` to recurse over
    any data nested in *root*, and find values which match a given
    criterion, specified by the *query* callable.

    Results are returned as a list of ``(path, value)`` pairs. The
    paths are tuples in the same format accepted by
    :func:`get_path`. This can be useful for comparing values nested
    in two or more different structures.

    Here's a simple example that finds all integers:

    >>> root = {'a': {'b': 1, 'c': (2, 'd', 3)}, 'e': None}
    >>> res = research(root, query=lambda p, k, v: isinstance(v, int))
    >>> print(sorted(res))
    [(('a', 'b'), 1), (('a', 'c', 0), 2), (('a', 'c', 2), 3)]

    Note how *query* follows the same, familiar ``path, key, value``
    signature as the ``visit`` and ``enter`` functions on
    :func:`remap`, and returns a :class:`bool`.

    Args:
       root: The target object to search. Supports the same types of
          objects as :func:`remap`, including :class:`list`,
          :class:`tuple`, :class:`dict`, and :class:`set`.
       query (callable): The function called on every object to
          determine whether to include it in the search results. The
          callable must accept three arguments, *path*, *key*, and
          *value*, commonly abbreviated *p*, *k*, and *v*, same as
          *enter* and *visit* from :func:`remap`.
       reraise (bool): Whether to reraise exceptions raised by *query*
          or to simply drop the result that caused the error.


    With :func:`research` it's easy to inspect the details of a data
    structure, like finding values that are at a certain depth (using
    ``len(p)``) and much more. If more advanced functionality is
    needed, check out the code and make your own :func:`remap`
    wrapper, and consider `submitting a patch`_!

    .. _submitting a patch: https://github.com/mahmoud/boltons/pulls

def research(root, query=lambda p, k, v: True, reraise=False):
    """The :func:`research` function uses :func:`remap` to recurse over
    any data nested in *root*, and find values which match a given
    criterion, specified by the *query* callable.

    Results are returned as a list of ``(path, value)`` pairs. The
    paths are tuples in the same format accepted by
    :func:`get_path`. This can be useful for comparing values nested
    in two or more different structures.

    Here's a simple example that finds all integers:

    >>> root = {'a': {'b': 1, 'c': (2, 'd', 3)}, 'e': None}
    >>> res = research(root, query=lambda p, k, v: isinstance(v, int))
    >>> print(sorted(res))
    [(('a', 'b'), 1), (('a', 'c', 0), 2), (('a', 'c', 2), 3)]

    Note how *query* follows the same, familiar ``path, key, value``
    signature as the ``visit`` and ``enter`` functions on
    :func:`remap`, and returns a :class:`bool`.

    Args:
       root: The target object to search. Supports the same types of
          objects as :func:`remap`, including :class:`list`,
          :class:`tuple`, :class:`dict`, and :class:`set`.
       query (callable): The function called on every object to
          determine whether to include it in the search results. The
          callable must accept three arguments, *path*, *key*, and
          *value*, commonly abbreviated *p*, *k*, and *v*, same as
          *enter* and *visit* from :func:`remap`.
       reraise (bool): Whether to reraise exceptions raised by *query*
          or to simply drop the result that caused the error.


    With :func:`research` it's easy to inspect the details of a data
    structure, like finding values that are at a certain depth (using
    ``len(p)``) and much more. If more advanced functionality is
    needed, check out the code and make your own :func:`remap`
    wrapper, and consider `submitting a patch`_!

    .. _submitting a patch: https://github.com/mahmoud/boltons/pulls
    """
    ret = []

    if not callable(query):
        raise TypeError('query expected callable, not: %r' % query)

    def enter(path, key, value):
        try:
            if query(path, key, value):
                ret.append((path + (key,), value))
        except Exception:
            if reraise:
                raise
        return default_enter(path, key, value)

    remap(root, enter=enter)
    return ret