'Perform a map/reduce query using the current query spec
and ordering. While ``map_reduce`` respects ``QuerySet`` chaining,
it must be the last call made, as it does not return a maleable
``QuerySet``.
See the :meth:`~mongoengine.tests.QuerySetTest.test_map_reduce`
and :meth:`~mongoengine.tests.QuerySetTest.test_map_advanced`
tests in ``tests.queryset.QuerySetTest`` for usage examples.
:param map_f: map function, as :class:`~bson.code.Code` or string
:param reduce_f: reduce function, as
:class:`~bson.code.Code` or string
:param output: output collection name, if set to \'inline\' will try to
use :class:`~pymongo.collection.Collection.inline_map_reduce`
This can also be a dictionary containing output options
see: http://docs.mongodb.org/manual/reference/command/mapReduce/#dbcmd.mapReduce
:param finalize_f: finalize function, an optional function that
performs any post-reduction processing.
:param scope: values to insert into map/reduce global scope. Optional.
:param limit: number of objects from current query to provide
to map/reduce method
Returns an iterator yielding
:class:`~mongoengine.document.MapReduceDocument`.
.. note::
Map/Reduce changed in server version **>= 1.7.4**. The PyMongo
:meth:`~pymongo.collection.Collection.map_reduce` helper requires
PyMongo version **>= 1.11**.
.. versionchanged:: 0.5
- removed ``keep_temp`` keyword argument, which was only relevant
for MongoDB server versions older than 1.7.4
.. versionadded:: 0.3'

def map_reduce(self, map_f, reduce_f, output, finalize_f=None, limit=None, scope=None):

    queryset = self.clone()
    MapReduceDocument = _import_class('MapReduceDocument')
    if (not hasattr(self._collection, 'map_reduce')):
        raise NotImplementedError('Requires    MongoDB    >=    1.7.1')
    map_f_scope = {}
    if isinstance(map_f, Code):
        map_f_scope = map_f.scope
        map_f = six.text_type(map_f)
    map_f = Code(queryset._sub_js_fields(map_f), map_f_scope)
    reduce_f_scope = {}
    if isinstance(reduce_f, Code):
        reduce_f_scope = reduce_f.scope
        reduce_f = six.text_type(reduce_f)
    reduce_f_code = queryset._sub_js_fields(reduce_f)
    reduce_f = Code(reduce_f_code, reduce_f_scope)
    mr_args = {'query': queryset._query}
    if finalize_f:
        finalize_f_scope = {}
        if isinstance(finalize_f, Code):
            finalize_f_scope = finalize_f.scope
            finalize_f = six.text_type(finalize_f)
        finalize_f_code = queryset._sub_js_fields(finalize_f)
        finalize_f = Code(finalize_f_code, finalize_f_scope)
        mr_args['finalize'] = finalize_f
    if scope:
        mr_args['scope'] = scope
    if limit:
        mr_args['limit'] = limit
    if ((output == 'inline') and (not queryset._ordering)):
        map_reduce_function = 'inline_map_reduce'
    else:
        map_reduce_function = 'map_reduce'
        if isinstance(output, six.string_types):
            mr_args['out'] = output
        elif isinstance(output, dict):
            ordered_output = []
            for part in ('replace', 'merge', 'reduce'):
                value = output.get(part)
                if value:
                    ordered_output.append((part, value))
                    break
            else:
                raise OperationError('actionData    not    specified    for    output')
            db_alias = output.get('db_alias')
            remaing_args = ['db', 'sharded', 'nonAtomic']
            if db_alias:
                ordered_output.append(('db', get_db(db_alias).name))
                del remaing_args[0]
            for part in remaing_args:
                value = output.get(part)
                if value:
                    ordered_output.append((part, value))
            mr_args['out'] = SON(ordered_output)
    results = getattr(queryset._collection, map_reduce_function)(map_f, reduce_f, **mr_args)
    if (map_reduce_function == 'map_reduce'):
        results = results.find()
    if queryset._ordering:
        results = results.sort(queryset._ordering)
    for doc in results:
        (yield MapReduceDocument(queryset._document, queryset._collection, doc['_id'], doc['value']))
