def _ap(relevances, scores, topn=None):
    """Returns the average precision (AP) of a single ranked list.
    The implementation here is copied from Equation (1.7) in
    Liu, T-Y "Learning to Rank for Information Retrieval" found at
    https://www.nowpublishers.com/article/DownloadSummary/INR-016
    Args:
      relevances: A `list` of document relevances, which are binary.
      scores: A `list` of document scores.
      topn: An `integer` specifying the number of items to be considered in the
        average precision computation.
    Returns:
      The MAP of the list as a float computed using the formula
      sum([P@k * rel for k, rel in enumerate(relevance)]) / sum(relevance)
      where P@k is the precision of the list at the cut off k.
    """

    def argsort(arr, reverse=True):
        arr_ind = sorted([(a, i) for i, a in enumerate(arr)], reverse=reverse)
        return list(zip(*arr_ind))[1]

    num_docs = len(relevances)
    if isinstance(topn, int) and topn > 0:
        num_docs = min(num_docs, topn)
    indices = argsort(scores)[:num_docs]
    ranked_relevances = [1. * relevances[i] for i in indices]
    precision = {}
    for k in range(1, num_docs + 1):
        precision[k] = sum(ranked_relevances[:k]) / k
    num_rel = sum(ranked_relevances[:num_docs])
    average_precision = sum(precision[k] * ranked_relevances[k - 1]
                            for k in precision) / num_rel if num_rel else 0
    return average_precision