//  [python]
def _get_index_nd(self, key):
        """
        Return an index array for use in `scatter_nd` and `gather_nd`,
        and a list of positions of new_axes in ouptut shape.
        """
        key_nd = tuple(idx for idx in key if idx is not None)
        if len(key_nd) < self.ndim:
            raise RuntimeError(
                'too few indices after normalization: expected `ndim` ({}) '
                'but got {}. This is a bug, please report it!'
                ''.format(self.ndim, len(key_nd))
            )
        if len(key_nd) > self.ndim:
            raise IndexError(
                'too many indices ({}) for array with {} dimensions'
                ''.format(len(key_nd), self.ndim)
            )
        ndim = len(key_nd)

        # --- Preparation --- #

        # - Make lists for bookkeeping of advanced indices & axes
        # - Drop trailing `slice(None)` entries in `key` for efficiency
        # - Determine whether the advanced indices are adjacent in `key`
        # - Depending on that, make index permutations to move around indices

        adv_axs = [ax for ax, idx in enumerate(key) if _is_advanced_index(idx)]
        adv_axs_nd = [ax for ax, idx in enumerate(key_nd) if _is_advanced_index(idx)]
        adv_idcs_are_adjacent = bool(np.all(np.diff(adv_axs) == 1))
        nonadv_axs_nd = [ax for ax in range(ndim) if ax not in adv_axs_nd]
        adv_idcs_nd = [key_nd[ax] for ax in adv_axs_nd]
        idcs_short = self._drop_slice_none_at_end(key_nd)
        dropped_axs = list(range(len(idcs_short), ndim))

        if adv_idcs_are_adjacent:
            # The easy case: the advanced block can stay at its position, and no
            # permutation needs to be done (identity permutation)
            axs_nd_permut = axs_nd_permut_inv = tuple(range(ndim))
            idcs_permut_short = idcs_short
            block_axs_nd = adv_axs_nd
        else:
            # The more complicated case: during broadcasting, we need to use the
            # indices in the *permuted* order, where the advanced block is
            # at the beginning, while the final index for `gather_nd` is stacked
            # in the *original* order, so that the association of index with
            # array axis remains the same.

            # This order is used for broadcasting: advanced block at the beginning
            idcs_permut_short = (
                adv_idcs_nd +
                [key_nd[ax] for ax in range(ndim)
                 if ax not in adv_axs_nd and ax not in dropped_axs]
            )
            block_axs_nd = list(range(len(adv_axs_nd)))
            axs_nd_permut = adv_axs_nd + nonadv_axs_nd
            axs_nd_permut_inv = list(np.argsort(axs_nd_permut))

        # --- Conversion, broadcasting and index stacking --- #

        # - Convert all indices in `key` to arrays: integers to 1-element arrays,
        #   `slice` objects to arrays with explicit indices
        # - Reshape arrays for broadcasting according to their position in the
        #   *permuted* key
        # - Broadcast and stack the indices in the *original* order

        shape_nd_permut = tuple(self.shape[ax] for ax in axs_nd_permut)
        converted_idcs_short = [
            self._advanced_index_to_array(idx, ax_len, self.context)
            for idx, ax_len in zip(idcs_permut_short, shape_nd_permut)
        ]
        bcast_idcs_permut_short = self._broadcast_advanced_indices(
            converted_idcs_short, block_axes=block_axs_nd
        )

        # Get the ndim of advanced indexing subspace
        converted_advanced_idcs = [
            self._advanced_index_to_array(idx, ax_len, self.context)
            for idx, ax_len in zip(adv_idcs_nd, [self.shape[ax] for ax in adv_axs_nd])
        ]
        bcast_advanced_shape = _broadcast_shapes(converted_advanced_idcs)

        # Undo the permutation to restore the original order
        bcast_idcs_short = [
            bcast_idcs_permut_short[ax]
            for ax in axs_nd_permut_inv
            if axs_nd_permut[ax] not in dropped_axs
        ]

        # Calculate where the newaxes are inserted after advanced indexing
        new_axes_positions = self._new_axes_after_advanced_indexing(key, adv_axs,\
                                len(bcast_advanced_shape), adv_idcs_are_adjacent)

                                # if any array is numpy.ndarray, stack in numpy ndarray class.
        for idcs in bcast_idcs_short:
            if type(idcs) != NDArray:  # pylint: disable=unidiomatic-typecheck
                return bcast_idcs_short, new_axes_positions

        return op.stack(*bcast_idcs_short), new_axes_positions