assoc(m, k1, v1, k2, v2...) yields a map equivalent to m without the arguments k1, k2, etc.
      associated with the values v1, v2, etc. If m is a mutable python dictionary, this is
      equivalent to using m[k] = v for all the given key-value pairs then returning m itself. If m
      is a persistent map, this is equivalent to calling m = m.set(k, v) for all the key-value pairs
      then returning m.

    Keys given in the arguments list are always associated in order, then keys in the keyword
    arguments are associated, meaning that the keyword arguments have precedence.

    Note that in addition to key-value pairs in the ordinary arguments list, assoc() also uses the
    key-value pairs given as named/keyword arguments.
    
    If m is not a map but is instead a list or persistent vector, this operates as if the
    list/vector is a map whose keys are integers. If an item is added beyond the extent of the list
    then it is extended by adding None to the list. If negative indices are given, they are always
    interpreted relative to the end of the initial input list m and not in terms of the list as it
    has grown at the point that the key-value pair is processed.

def assoc(m, *args, **kw):
    '''
    assoc(m, k1, v1, k2, v2...) yields a map equivalent to m without the arguments k1, k2, etc.
      associated with the values v1, v2, etc. If m is a mutable python dictionary, this is
      equivalent to using m[k] = v for all the given key-value pairs then returning m itself. If m
      is a persistent map, this is equivalent to calling m = m.set(k, v) for all the key-value pairs
      then returning m.

    Keys given in the arguments list are always associated in order, then keys in the keyword
    arguments are associated, meaning that the keyword arguments have precedence.

    Note that in addition to key-value pairs in the ordinary arguments list, assoc() also uses the
    key-value pairs given as named/keyword arguments.
    
    If m is not a map but is instead a list or persistent vector, this operates as if the
    list/vector is a map whose keys are integers. If an item is added beyond the extent of the list
    then it is extended by adding None to the list. If negative indices are given, they are always
    interpreted relative to the end of the initial input list m and not in terms of the list as it
    has grown at the point that the key-value pair is processed.
    '''
    if len(args) % 2 != 0: raise ValueError('assoc arguments must be given as key-value pairs')
    args = (u for it in [zip(args[::2],args[1::2]), six.iteritems(kw)] for u in it)
    if is_pmap(m):
        for (k,v) in args: m = m.set(k, v)
        return m
    elif is_map(m):
        for (k,v) in args: m[k] = v
        return m
    elif isinstance(m, tuple_type):
        args = list(args)
        return m if len(args) == 0 else tuple(assoc(list(m), *args, **kw))
    elif isinstance(m, list_type):
        n0 = len(m)
        n = n0
        for (k,v) in args:
            if not is_int(k): TypeError('Keys for list args must be integers')
            if k < -n: KeyError(k)
            if k < 0: k = n0 + k
            if k >= n: (n,m) = (k+1, m + [None]*(k - n + 1))
            m[k] = v
        return m
    elif is_nparray(m):
        args = list(args)
        if len(args) == 0: return m
        return np.asarray(assoc(m.tolist(), *[x for u in args for x in u]))
    else: raise ValueError('Cannot assoc given type: %s' % type(m))