Makhele Sabata
Makhele Sabata

Reputation: 621

How can I create my own map() function in Python

I am trying to create the built-in map() function in python. Here is my attempt:

def mapper(func, *sequences):
   if len(sequences) > 1:
       while True:
          list.append(func(sequences[0][0],sequences[0][0],))
       return list
   return list

But I am really stuck, because if the user gives e.g. 100 arguments how do i deal with those?

Upvotes: 4

Views: 6966

Answers (3)

Asclepius
Asclepius

Reputation: 63312

The prior answer by Pardhu attempts to decompose the problem into mapper and zipper. This is good, but it doesn't have a reasonable or working implementation of zipper, also to more closely match Python's native zip. This answer uses a more reasonable implementation of zipper which uses iterables more correctly.

def zipper(*iterables):
    # Ref: https://stackoverflow.com/a/77297966
    if not iterables:
        return (_ for _ in ())  # Without this the function runs forever.

    iterables = [iter(it) for it in iterables]
    try:
        while True:
            yield tuple([next(it) for it in iterables])
    except StopIteration:
        return


def mapper(func, *iterables):
    # Ref: https://stackoverflow.com/a/77298108
    return (func(*i) for i in zipper(*iterables))  # Note: `zipper` aims to match `zip`.

Usage example:

> mapper(lambda *i: sum(i), range(11, 15), range(20, 30))
<generator object mapper.<locals>.<genexpr> at 0x7ffa1a448900>

> list(_)
[31, 33, 35, 37]

Upvotes: 2

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 476669

You use the asterisk * when you call the function:

def mapper(func, *sequences):
       result = []
       if len(sequences) > 0:
           minl = min(len(subseq) for subseq in sequences)
           for i in range(minl):
              result.append(func(*[subseq[i] for subseq in sequences]))
       return result

This produces:

>>> import operator
>>> mapper(operator.add, [1,2,4], [3,6,9])
[4, 8, 13]

By using the asterisk, we unpack the iterable as separate parameters in the function call.

Note that this is still not fully equivalent, since:

  1. the sequences should be iterables, not per se lists, so we can not always index; and
  2. the result of a map in is an iterable as well, so not a list.

A more -like map function would be:

def mapper(func, *sequences):
    if not sequences:
        raise TypeError('Mapper should have at least two parameters')
    iters = [iter(seq) for seq in sequences]
    try:
        while True:
            yield func(*[next(it) for it in iters])
    except StopIteration:
        pass

Note however that most Python interpreters will implement map closer to the interpreter than Python code, so it is definitely more efficient to use the builtin map, than writing your own.

N.B.: it is better not to use variable names like list, set, dict, etc. since these will override (here locally) the reference to the list type. As a result a call like list(some_iterable) will no longer work.

Upvotes: 5

Pardhu
Pardhu

Reputation: 1957

Separating the part of combining of the sequence or sequences logic is much more easier to read and understand.

def mapper(func, *args):
    for i in zip(*args):
        yield func(*i)

Here we are using Python inbuilt zip if you want to replace it entirely with your own implementation replace with zip with the below zipper function

def zipper(*args):
    for i in range(len(args[0])):
        index_elements = []
        for arg in args:
            index_elements.append(arg[i])
        yield positional_elements

Upvotes: 0

Related Questions