Combine two or more numpy ndarray with structured dtype on common key 
    column(s).

    Merge a list (or dictionary) of numpy ndarray with structured dtype, given
    by `L`, on key columns listed in `keycols`.

    This function is actually a wrapper for 
    :func:`tabular.spreadsheet.strictjoin`.

    The ``strictjoin`` function has a few restrictions, and this ``join``
    function will try to ensure that they are satisfied:

    *   each element of `keycol` must be a valid column name in `X`
        and each array in `L`, and all of the same data-type.

    *   for each column `col`  in `keycols`, and each array `A` in `L`, the 
        values in `A[col]` must be unique, -- and same for `X[col]`.  
        (Actually this uniqueness doesn't have to hold for the first tabarray
        in L, that is, L[0], but must for all the subsequent ones.)

    *   the *non*-key-column column names in each of the arrays must be 
        disjoint from each other -- or disjoint after a renaming (see below).

    An error will be thrown if these conditions are not met.

    If you don't provide a value of `keycols`, the algorithm will attempt to 
    infer which columns should be used by trying to find the largest set of 
    common column names that contain unique values in each array and have the 
    same data type.  An error will be thrown if no such inference can be made.

    *Renaming of overlapping columns*

            If the non-keycol column names of the arrays overlap, ``join`` will 
            by default attempt to rename the columns by using a simple 
            convention:

            *   If `L` is a list, it will append the number in the list to the 
                key associated with the array.

            *   If `L` is a dictionary, the algorithm will append the string 
                representation of the key associated with an array to the 
                overlapping columns from that array.

            You can override the default renaming scheme using the `renamer` 
            parameter.

    *Nullvalues for keycolumn differences*

            If there are regions of the keycolumns that are not overlapping 
            between merged arrays, `join` will fill in the relevant entries 
            with null values chosen by default:

            *   '0' for integer columns

            *   '0.0' for float columns

            *   the empty character ('') for string columns.

    **Parameters**

            **L** :  list or dictionary

                    Numpy recarrays to merge.  If `L` is a dictionary, the keys
                    name each numpy recarray, and the corresponding values are 
                    the actual numpy recarrays.

            **keycols** :  list of strings

                    List of the names of the key columns along which to do the 
                    merging.

            **nullvals** :  function, optional

                    A function that returns a null value for a numpy format
                    descriptor string, e.g. ``'<i4'`` or ``'|S5'``.

                    See the default function for further documentation:

                            :func:`tabular.spreadsheet.DEFAULT_NULLVALUEFORMAT`

            **renamer** :  function, optional

                    A function for renaming overlapping non-key column names 
                    among the numpy recarrays to merge.

                    See the default function for further documentation:

                            :func:`tabular.spreadsheet.DEFAULT_RENAMER`

            **returnrenaming** :  Boolean, optional

                    Whether to return the result of the `renamer` function.

                    See the default function for further documentation:

                            :func:`tabular.spreadsheet.DEFAULT_RENAMER`

            **Names**: list of strings:

                    If `L` is a list, than names for elements of `L` can be 
                    specified with `Names` (without losing the ordering as you 
                    would if you did it with a dictionary).  
                    
                    `len(L)` must equal `len(Names)`

    **Returns**

            **result** :  numpy ndarray with structured dtype

                    Result of the join, e.g. the result of merging the input
                    numpy arrays defined in `L` on the key columns listed in 
                    `keycols`.

            **renaming** :  dictionary of dictionaries, optional

                    The result returned by the `renamer` function. Returned 
                    only if `returnrenaming == True`.

                    See the default function for further documentation:

                            :func:`tabular.spreadsheet.DEFAULT_RENAMER`

    **See Also:**

            :func:`tabular.spreadsheet.strictjoin`

def join(L, keycols=None, nullvals=None, renamer=None, 
         returnrenaming=False, Names=None):
    """
    Combine two or more numpy ndarray with structured dtype on common key 
    column(s).

    Merge a list (or dictionary) of numpy ndarray with structured dtype, given
    by `L`, on key columns listed in `keycols`.

    This function is actually a wrapper for 
    :func:`tabular.spreadsheet.strictjoin`.

    The ``strictjoin`` function has a few restrictions, and this ``join``
    function will try to ensure that they are satisfied:

    *   each element of `keycol` must be a valid column name in `X`
        and each array in `L`, and all of the same data-type.

    *   for each column `col`  in `keycols`, and each array `A` in `L`, the 
        values in `A[col]` must be unique, -- and same for `X[col]`.  
        (Actually this uniqueness doesn't have to hold for the first tabarray
        in L, that is, L[0], but must for all the subsequent ones.)

    *   the *non*-key-column column names in each of the arrays must be 
        disjoint from each other -- or disjoint after a renaming (see below).

    An error will be thrown if these conditions are not met.

    If you don't provide a value of `keycols`, the algorithm will attempt to 
    infer which columns should be used by trying to find the largest set of 
    common column names that contain unique values in each array and have the 
    same data type.  An error will be thrown if no such inference can be made.

    *Renaming of overlapping columns*

            If the non-keycol column names of the arrays overlap, ``join`` will 
            by default attempt to rename the columns by using a simple 
            convention:

            *   If `L` is a list, it will append the number in the list to the 
                key associated with the array.

            *   If `L` is a dictionary, the algorithm will append the string 
                representation of the key associated with an array to the 
                overlapping columns from that array.

            You can override the default renaming scheme using the `renamer` 
            parameter.

    *Nullvalues for keycolumn differences*

            If there are regions of the keycolumns that are not overlapping 
            between merged arrays, `join` will fill in the relevant entries 
            with null values chosen by default:

            *   '0' for integer columns

            *   '0.0' for float columns

            *   the empty character ('') for string columns.

    **Parameters**

            **L** :  list or dictionary

                    Numpy recarrays to merge.  If `L` is a dictionary, the keys
                    name each numpy recarray, and the corresponding values are 
                    the actual numpy recarrays.

            **keycols** :  list of strings

                    List of the names of the key columns along which to do the 
                    merging.

            **nullvals** :  function, optional

                    A function that returns a null value for a numpy format
                    descriptor string, e.g. ``'<i4'`` or ``'|S5'``.

                    See the default function for further documentation:

                            :func:`tabular.spreadsheet.DEFAULT_NULLVALUEFORMAT`

            **renamer** :  function, optional

                    A function for renaming overlapping non-key column names 
                    among the numpy recarrays to merge.

                    See the default function for further documentation:

                            :func:`tabular.spreadsheet.DEFAULT_RENAMER`

            **returnrenaming** :  Boolean, optional

                    Whether to return the result of the `renamer` function.

                    See the default function for further documentation:

                            :func:`tabular.spreadsheet.DEFAULT_RENAMER`

            **Names**: list of strings:

                    If `L` is a list, than names for elements of `L` can be 
                    specified with `Names` (without losing the ordering as you 
                    would if you did it with a dictionary).  
                    
                    `len(L)` must equal `len(Names)`

    **Returns**

            **result** :  numpy ndarray with structured dtype

                    Result of the join, e.g. the result of merging the input
                    numpy arrays defined in `L` on the key columns listed in 
                    `keycols`.

            **renaming** :  dictionary of dictionaries, optional

                    The result returned by the `renamer` function. Returned 
                    only if `returnrenaming == True`.

                    See the default function for further documentation:

                            :func:`tabular.spreadsheet.DEFAULT_RENAMER`

    **See Also:**

            :func:`tabular.spreadsheet.strictjoin`

    """

    if isinstance(L, dict):
        Names = L.keys()
        LL = L.values()
    else:
        if Names == None:
            Names = range(len(L))
        else:
            assert len(Names) == len(L)
        LL = L

    if not keycols:
        keycols = utils.listintersection([a.dtype.names for a in LL])
        if len(keycols) == 0:
            raise ValueError('No common column names found.')

        keycols = [l for l in keycols if all([a.dtype[l] == LL[0].dtype[l] 
                   for a in LL])]
        if len(keycols) == 0:
            raise ValueError('No suitable common keycolumns, '
                             'with identical datatypes found.')

        keycols = [l for l in keycols if all([isunique(a[keycols]) 
                   for a in LL])]
        if len(keycols) == 0:
            raise ValueError('No suitable common keycolumns, '
                             'with unique value sets in all arrays to be '
                             'merged, were found.')
        else:
            print('Inferring keycols to be:', keycols)

    elif isinstance(keycols,str):
        keycols = [l.strip() for l in keycols.split(',')]

    commons = set(Commons([l.dtype.names for l in LL])).difference(keycols)
    renaming = {}
    if len(commons) > 0:
        print 'common attributes, forcing a renaming ...'
        if renamer == None:
            print('Using default renamer ...')
            renamer = DEFAULT_RENAMER
        renaming = renamer(L, Names=Names)
        if not RenamingIsInCorrectFormat(renaming, L, Names=Names):
            print('Renaming from specified renamer is not in correct format,'
                  'using default renamer instead ...')
            renaming = DEFAULT_RENAMER(L, Names = Names)
        NewNames = [[l if l not in renaming[k].keys() else renaming[k][l] 
                     for l in ll.dtype.names] for (k, ll) in zip(Names, LL)]
        if set(Commons(NewNames)).difference(keycols):
            raise ValueError('Renaming convention failed to produce '
                             'separated names.')

    Result = strictjoin(L, keycols, nullvals, renaming, Names=Names)

    if returnrenaming:
        return [Result, renaming]
    else:
        if renaming:
            print('There was a nontrivial renaming, to get it set '      
                  '"returnrenaming = True" in keyword to join function.')
        return Result