Create a sparse document-term-matrix (DTM) as scipy "coo_matrix" from vocabulary array `vocab`, document
    IDs/labels array `doc_labels`, dict of doc_label -> document terms `docs_terms` and the sum of unique terms
    per document `sum_uniques_per_doc`.
    The DTM's rows are document names, its columns are indices in `vocab`, hence a value `DTM[j, k]` is the
    term frequency of term `vocab[k]` in `docnames[j]`.

    Memory requirement: about 3 * <sum_uniques_per_doc>.

def create_sparse_dtm(vocab, doc_labels, docs_terms, sum_uniques_per_doc):
    """
    Create a sparse document-term-matrix (DTM) as scipy "coo_matrix" from vocabulary array `vocab`, document
    IDs/labels array `doc_labels`, dict of doc_label -> document terms `docs_terms` and the sum of unique terms
    per document `sum_uniques_per_doc`.
    The DTM's rows are document names, its columns are indices in `vocab`, hence a value `DTM[j, k]` is the
    term frequency of term `vocab[k]` in `docnames[j]`.

    Memory requirement: about 3 * <sum_uniques_per_doc>.
    """
    vocab_sorter = np.argsort(vocab)  # indices that sort <vocab>

    nvocab = len(vocab)
    ndocs = len(doc_labels)

    # create arrays for sparse matrix
    data = np.empty(sum_uniques_per_doc, dtype=np.intc)  # all non-zero term frequencies at data[k]
    cols = np.empty(sum_uniques_per_doc, dtype=np.intc)  # column index for kth data item (kth term freq.)
    rows = np.empty(sum_uniques_per_doc, dtype=np.intc)  # row index for kth data item (kth term freq.)

    ind = 0  # current index in the sparse matrix data
    # go through all documents with their terms
    for i, (doc_label, terms) in enumerate(docs_terms.items()):
        if len(terms) == 0: continue   # skip empty documents

        # find indices into `vocab` such that, if the corresponding elements in `terms` were
        # inserted before the indices, the order of `vocab` would be preserved
        # -> array of indices of `terms` in `vocab`
        term_indices = vocab_sorter[np.searchsorted(vocab, terms, sorter=vocab_sorter)]

        # count the unique terms of the document and get their vocabulary indices
        uniq_indices, counts = np.unique(term_indices, return_counts=True)
        n_vals = len(uniq_indices)
        ind_end = ind + n_vals

        data[ind:ind_end] = counts  # save the counts (term frequencies)
        cols[ind:ind_end] = uniq_indices  # save the column index: index in <vocab>
        doc_idx = np.where(doc_labels == doc_label)  # get the document index for the document name
        assert len(doc_idx) == 1
        rows[ind:ind_end] = np.repeat(doc_idx, n_vals)  # save it as repeated value

        ind = ind_end

    assert ind == len(data)

    return coo_matrix((data, (rows, cols)), shape=(ndocs, nvocab), dtype=np.intc)