Break the input data into smaller batches, optionally saving each one to disk.

    Args:
        input_list: An input object that has a list-like interface (indexing and slicing).
        batch_size: The maximum number of input items in each batch.
        batch_storage_dir: The directory to save the checkpoints to.
        checkpoint: Whether to save each batch to a file.

    Returns:
        A list of batch objects with the following structure:
        {'index', 'data', 'input_filename', 'result_filename'}

def split_into_batches(input_list, batch_size, batch_storage_dir, checkpoint=False):
    """
    Break the input data into smaller batches, optionally saving each one to disk.

    Args:
        input_list: An input object that has a list-like interface (indexing and slicing).
        batch_size: The maximum number of input items in each batch.
        batch_storage_dir: The directory to save the checkpoints to.
        checkpoint: Whether to save each batch to a file.

    Returns:
        A list of batch objects with the following structure:
        {'index', 'data', 'input_filename', 'result_filename'}
    """

    if checkpoint and not os.path.exists(batch_storage_dir):
        os.mkdir(batch_storage_dir)

    batches = [
        {
            'index': batch_index,
            'data': input_list[start_index:start_index + batch_size],
            'input_filename': os.path.join(batch_storage_dir, 'batch-{:05d}-input.pickle'.format(batch_index)),
            'result_filename': os.path.join(batch_storage_dir, 'batch-{:05d}-output.pickle'.format(batch_index)),
        }
        for batch_index, start_index in enumerate(range(0, len(input_list), batch_size))
    ]

    if checkpoint:
        for batch in batches:
            save(batch['data'], batch['input_filename'])

    return batches