Convert an SArray of list, array, or dict type to an SFrame with
        multiple columns.

        `unpack` expands an SArray using the values of each list/array/dict as
        elements in a new SFrame of multiple columns. For example, an SArray of
        lists each of length 4 will be expanded into an SFrame of 4 columns,
        one for each list element. An SArray of lists/arrays of varying size
        will be expand to a number of columns equal to the longest list/array.
        An SArray of dictionaries will be expanded into as many columns as
        there are keys.

        When unpacking an SArray of list or array type, new columns are named:
        `column_name_prefix`.0, `column_name_prefix`.1, etc. If unpacking a
        column of dict type, unpacked columns are named
        `column_name_prefix`.key1, `column_name_prefix`.key2, etc.

        When unpacking an SArray of list or dictionary types, missing values in
        the original element remain as missing values in the resultant columns.
        If the `na_value` parameter is specified, all values equal to this
        given value are also replaced with missing values. In an SArray of
        array.array type, NaN is interpreted as a missing value.

        :py:func:`turicreate.SFrame.pack_columns()` is the reverse effect of unpack

        Parameters
        ----------
        column_name_prefix: str, optional
            If provided, unpacked column names would start with the given prefix.

        column_types: list[type], optional
            Column types for the unpacked columns. If not provided, column
            types are automatically inferred from first 100 rows. Defaults to
            None.

        na_value: optional
            Convert all values that are equal to `na_value` to
            missing value if specified.

        limit: list, optional
            Limits the set of list/array/dict keys to unpack.
            For list/array SArrays, 'limit' must contain integer indices.
            For dict SArray, 'limit' must contain dictionary keys.

        Returns
        -------
        out : SFrame
            A new SFrame that contains all unpacked columns

        Examples
        --------
        To unpack a dict SArray

        >>> sa = SArray([{ 'word': 'a',     'count': 1},
        ...              { 'word': 'cat',   'count': 2},
        ...              { 'word': 'is',    'count': 3},
        ...              { 'word': 'coming','count': 4}])

        Normal case of unpacking SArray of type dict:

        >>> sa.unpack(column_name_prefix=None)
        Columns:
            count   int
            word    str
        <BLANKLINE>
        Rows: 4
        <BLANKLINE>
        Data:
        +-------+--------+
        | count |  word  |
        +-------+--------+
        |   1   |   a    |
        |   2   |  cat   |
        |   3   |   is   |
        |   4   | coming |
        +-------+--------+
        [4 rows x 2 columns]
        <BLANKLINE>

        Unpack only keys with 'word':

        >>> sa.unpack(limit=['word'])
        Columns:
            X.word  str
        <BLANKLINE>
        Rows: 4
        <BLANKLINE>
        Data:
        +--------+
        | X.word |
        +--------+
        |   a    |
        |  cat   |
        |   is   |
        | coming |
        +--------+
        [4 rows x 1 columns]
        <BLANKLINE>

        >>> sa2 = SArray([
        ...               [1, 0, 1],
        ...               [1, 1, 1],
        ...               [0, 1]])

        Convert all zeros to missing values:

        >>> sa2.unpack(column_types=[int, int, int], na_value=0)
        Columns:
            X.0     int
            X.1     int
            X.2     int
        <BLANKLINE>
        Rows: 3
        <BLANKLINE>
        Data:
        +------+------+------+
        | X.0  | X.1  | X.2  |
        +------+------+------+
        |  1   | None |  1   |
        |  1   |  1   |  1   |
        | None |  1   | None |
        +------+------+------+
        [3 rows x 3 columns]
        <BLANKLINE>

def unpack(self, column_name_prefix = "X", column_types=None, na_value=None, limit=None):
        """
        Convert an SArray of list, array, or dict type to an SFrame with
        multiple columns.

        `unpack` expands an SArray using the values of each list/array/dict as
        elements in a new SFrame of multiple columns. For example, an SArray of
        lists each of length 4 will be expanded into an SFrame of 4 columns,
        one for each list element. An SArray of lists/arrays of varying size
        will be expand to a number of columns equal to the longest list/array.
        An SArray of dictionaries will be expanded into as many columns as
        there are keys.

        When unpacking an SArray of list or array type, new columns are named:
        `column_name_prefix`.0, `column_name_prefix`.1, etc. If unpacking a
        column of dict type, unpacked columns are named
        `column_name_prefix`.key1, `column_name_prefix`.key2, etc.

        When unpacking an SArray of list or dictionary types, missing values in
        the original element remain as missing values in the resultant columns.
        If the `na_value` parameter is specified, all values equal to this
        given value are also replaced with missing values. In an SArray of
        array.array type, NaN is interpreted as a missing value.

        :py:func:`turicreate.SFrame.pack_columns()` is the reverse effect of unpack

        Parameters
        ----------
        column_name_prefix: str, optional
            If provided, unpacked column names would start with the given prefix.

        column_types: list[type], optional
            Column types for the unpacked columns. If not provided, column
            types are automatically inferred from first 100 rows. Defaults to
            None.

        na_value: optional
            Convert all values that are equal to `na_value` to
            missing value if specified.

        limit: list, optional
            Limits the set of list/array/dict keys to unpack.
            For list/array SArrays, 'limit' must contain integer indices.
            For dict SArray, 'limit' must contain dictionary keys.

        Returns
        -------
        out : SFrame
            A new SFrame that contains all unpacked columns

        Examples
        --------
        To unpack a dict SArray

        >>> sa = SArray([{ 'word': 'a',     'count': 1},
        ...              { 'word': 'cat',   'count': 2},
        ...              { 'word': 'is',    'count': 3},
        ...              { 'word': 'coming','count': 4}])

        Normal case of unpacking SArray of type dict:

        >>> sa.unpack(column_name_prefix=None)
        Columns:
            count   int
            word    str
        <BLANKLINE>
        Rows: 4
        <BLANKLINE>
        Data:
        +-------+--------+
        | count |  word  |
        +-------+--------+
        |   1   |   a    |
        |   2   |  cat   |
        |   3   |   is   |
        |   4   | coming |
        +-------+--------+
        [4 rows x 2 columns]
        <BLANKLINE>

        Unpack only keys with 'word':

        >>> sa.unpack(limit=['word'])
        Columns:
            X.word  str
        <BLANKLINE>
        Rows: 4
        <BLANKLINE>
        Data:
        +--------+
        | X.word |
        +--------+
        |   a    |
        |  cat   |
        |   is   |
        | coming |
        +--------+
        [4 rows x 1 columns]
        <BLANKLINE>

        >>> sa2 = SArray([
        ...               [1, 0, 1],
        ...               [1, 1, 1],
        ...               [0, 1]])

        Convert all zeros to missing values:

        >>> sa2.unpack(column_types=[int, int, int], na_value=0)
        Columns:
            X.0     int
            X.1     int
            X.2     int
        <BLANKLINE>
        Rows: 3
        <BLANKLINE>
        Data:
        +------+------+------+
        | X.0  | X.1  | X.2  |
        +------+------+------+
        |  1   | None |  1   |
        |  1   |  1   |  1   |
        | None |  1   | None |
        +------+------+------+
        [3 rows x 3 columns]
        <BLANKLINE>
        """
        from .sframe import SFrame as _SFrame

        if self.dtype not in [dict, array.array, list]:
            raise TypeError("Only SArray of dict/list/array type supports unpack")

        if column_name_prefix is None:
            column_name_prefix = ""
        if not(isinstance(column_name_prefix, six.string_types)):
            raise TypeError("'column_name_prefix' must be a string")

        # validate 'limit'
        if limit is not None:
            if (not _is_non_string_iterable(limit)):
                raise TypeError("'limit' must be a list")

            name_types = set([type(i) for i in limit])
            if (len(name_types) != 1):
                raise TypeError("'limit' contains values that are different types")

            # limit value should be numeric if unpacking sarray.array value
            if (self.dtype != dict) and (name_types.pop() != int):
                raise TypeError("'limit' must contain integer values.")

            if len(set(limit)) != len(limit):
                raise ValueError("'limit' contains duplicate values")

        if (column_types is not None):
            if not _is_non_string_iterable(column_types):
                raise TypeError("column_types must be a list")

            for column_type in column_types:
                if (column_type not in (int, float, str, list, dict, array.array)):
                    raise TypeError("column_types contains unsupported types. Supported types are ['float', 'int', 'list', 'dict', 'str', 'array.array']")

            if limit is not None:
                if len(limit) != len(column_types):
                    raise ValueError("limit and column_types do not have the same length")
            elif self.dtype == dict:
                raise ValueError("if 'column_types' is given, 'limit' has to be provided to unpack dict type.")
            else:
                limit = range(len(column_types))

        else:
            head_rows = self.head(100).dropna()
            lengths = [len(i) for i in head_rows]
            if len(lengths) == 0 or max(lengths) == 0:
                raise RuntimeError("Cannot infer number of items from the SArray, SArray may be empty. please explicitly provide column types")

            # infer column types for dict type at server side, for list and array, infer from client side
            if self.dtype != dict:
                length = max(lengths)
                if limit is None:
                    limit = range(length)
                else:
                    # adjust the length
                    length = len(limit)

                if self.dtype == array.array:
                    column_types = [float for i in range(length)]
                else:
                    column_types = list()
                    for i in limit:
                        t = [(x[i] if ((x is not None) and len(x) > i) else None) for x in head_rows]
                        column_types.append(infer_type_of_list(t))


        with cython_context():
            if (self.dtype == dict and column_types is None):
                limit = limit if limit is not None else []
                return _SFrame(_proxy=self.__proxy__.unpack_dict(column_name_prefix.encode('utf-8'), limit, na_value))
            else:
                return _SFrame(_proxy=self.__proxy__.unpack(column_name_prefix.encode('utf-8'), limit, column_types, na_value))