//  [python]
def sparse_labels_with_seq_lens(x, seq_lens, dtype=tf.int32, collapse_repeated=False, post_filter_idx=None):
    """
    :param tf.Tensor x: shape (batch,time) -> index, some int type
    :param tf.Tensor|None seq_lens: shape (batch,) of int32|int64
    :param tf.DType|None dtype: if given, will cast the `x` values to this type. ctc_loss() wants int32
    :param bool collapse_repeated: like uniq() behavior
    :param int|list[int]|set[int]|None post_filter_idx: if given, after an optional collapse_repeated,
      will remove all those idx
    :return: SparseTensor, e.g. input for tf.nn.ctc_loss(), and seq_lens of shape (batch,)
    :rtype: (tf.SparseTensor, tf.Tensor)
    """
    with tf.name_scope("sparse_labels"):
        x = check_input_ndim(x, ndim=2)
        if seq_lens is not None:
            x = check_dim_equal(
                x,
                0,
                seq_lens,
                0,
                extra_msg=[
                    "x",
                    str(x),
                    "seq_lens",
                    str(seq_lens),
                    seq_lens,
                ],
            )
        if dtype:
            x = tf.cast(x, dtype)
        batch_size = tf.shape(x)[0]
        max_time = tf.shape(x)[1]
        if seq_lens is not None:
            mask = sequence_mask(seq_lens, maxlen=max_time)  # shape (batch,time)
        else:
            mask = tf.ones(dtype=tf.bool, shape=(batch_size, max_time))
        if collapse_repeated:
            with tf.name_scope("collapse_repeated"):
                diffs = tf.concat(
                    axis=1, values=[tf.ones_like(x[:, :1], dtype=tf.bool), tf.not_equal(x[:, 1:], x[:, :-1])]
                )  # shape (batch,time)
                mask = tf.logical_and(diffs, mask)
        if post_filter_idx is not None:
            with tf.name_scope("post_filter_idx"):
                if isinstance(post_filter_idx, int):
                    mask = tf.logical_and(mask, tf.not_equal(x, post_filter_idx))
                elif isinstance(post_filter_idx, (list, tuple, set)):
                    for _idx in sorted(post_filter_idx):
                        assert isinstance(_idx, int)
                        mask = tf.logical_and(mask, tf.not_equal(x, _idx))
                else:
                    raise TypeError("unexpected type post_filter_idx %r" % type(post_filter_idx))
        with tf.name_scope("flat_x"):
            flat_x = tf.boolean_mask(x, mask)  # (N, ...s...)
        with tf.name_scope("idxs"):
            if collapse_repeated or post_filter_idx is not None:
                # Recalculate mask, so that we have them all behind each other.
                seq_lens = tf.reduce_sum(tf.cast(mask, tf.int32), axis=1)  # (batch,)
                max_time = tf.reduce_max(seq_lens)
                mask = sequence_mask(seq_lens)
            time_idxs = expand_dims_unbroadcast(tf.range(max_time), 0, batch_size)  # shape (batch,time)
            flat_time_idxs = tf.boolean_mask(time_idxs, mask)  # (N,)
            batch_idxs = expand_dims_unbroadcast(tf.range(batch_size), 1, max_time)  # shape (batch,time)
            flat_batch_idxs = tf.boolean_mask(batch_idxs, mask)  # (N,)
            flat_idxs = tf.stack([flat_batch_idxs, flat_time_idxs], axis=1)  # shape (N, 2)
            # tf.SparseTensor requires int64 indices
            flat_idxs = tf.cast(flat_idxs, tf.int64)
        with tf.name_scope("shape"):
            shape = [batch_size, max_time]
            # tf.SparseTensor requires int64 shape
            shape = [tf.cast(d, tf.int64) for d in shape]
            shape = tf.convert_to_tensor(shape)
        # tf.SparseTensor args:
        #   indices: A 2-D int64 tensor of shape `[N, ndims]`.
        #   values: A 1-D tensor of any type and shape `[N]`.
        #   shape: A 1-D int64 tensor of shape `[ndims]`.
        return tf.SparseTensor(flat_idxs, flat_x, shape), seq_lens