【问题标题】:Best approach to make a reverse five-star rating calculator制作反向五星级评分计算器的最佳方法
【发布时间】:2016-09-10 17:15:12
【问题描述】:

在 5 星评分系统中,我有已知的评分数 N(投票。)
我还有所有这些 N 评级的最终(加权)平均值,假设它是 R(浮点到小数点后两位)。
我想知道生成所有可能组合(加权平均总数)并仅打印出导致 R 的最佳方法(算法)。打印“所有”可能的组合不是我想要的,因为对于一个大的 N 和小的 R 来说,它会运行数百亿。我比 Python 新手差了一步,但它将是首选的语言,而这个练习就是我对该语言的介绍。解决这个问题的最佳方法是什么?方法是我的问题,但非常感谢任何代码提示。

示例:

N= 20 位客户评价了一个产品
R = 3.85 是平均评分

输出: [14, 0, 0, 1, 5] 是 146 种可能的组合之一。 “14个五星,0个四星,0个三星,1个二星,5个一星”

还有组合: [487, 0, 1, 0, 12] 对于 N=500 和 R=4.90 等,是 1154 种可能的组合。

【问题讨论】:

  • 这个问题与 Python 无关。你可能需要一个算法。
  • 看起来像家庭作业,当你用 Python 标记它时,可能会向我们展示你最好的尝试,有人可能会帮助你找到你卡在哪里。
  • 为什么不能平均重新分配平均值?例如,仅考虑 10 个 5 星、4 个 4 星等的平均值和总数,你怎么知道?
  • @cricket_007 这不是重点。他想要每个组合都能得到给定的平均值。
  • N=20,N*R=77的积分解有146个,你真的需要全部找出来吗?

标签: python algorithm


【解决方案1】:

星星总数为 N*R(在您的示例中为 20 * 3.85 = 77)。现在你拥有类似于the change making problem 的东西,只是你的硬币总数是固定的。

一个有效的解决方案可能是从不超过总数的尽可能多的大硬币(5 星评级)开始,然后减少直到您的评级不超过总数。您最终仍然会检查不起作用的解决方案,但它比检查所有解决方案要快得多,尤其是对于大型问题。

这是我的解决方案:(编辑:解决方案已调试。我不认为它是最佳解决方案,但它比蛮力更好。1931 年递归调用 N=20 R=3.85 的示例案例)

def distribution(total, maxRating, N, solution):
    if total == 0 and N == 0:
        return [solution + [0] * maxRating] #we found a solution

    if total == 0 or N == 0:
        return [] # no solution possible

    largestUpperLimit = min(total // maxRating, N) # an upper limit for the number of reviews with the largest rating
    largestLowerLimit = max((total - N * (maxRating -1)) // maxRating, 0) # a lower limit for the number of reviews with the largest rating

    if N < largestLowerLimit:
        return [] # there aren't enough ratings to make any solutions
    else:
        solutions = []
        for i in range(largestLowerLimit, largestUpperLimit + 1): # plus 1 to include the upper limit
            solutions.extend(distribution(total - i * maxRating, maxRating - 1, N - i, solution + [i]))
        return solutions


# Using the function example:
solutions = distribution(N * R, 5, N, [])

【讨论】:

    【解决方案2】:

    您可以使用递归算法枚举所有投票分布,然后检查其中哪些具有正确的加权平均值。但请注意,组合的数量增长很快。

    def distributions(ratings, remaining):
        if len(ratings) > 1:
            # more than one rating: take some for first and distribute rest
            for n in range(remaining+1):
                for dist in distributions(ratings[1:], remaining - n):
                    yield [(ratings[0], n)] + dist
        elif len(ratings) == 1:
            # only one rating left -> all remaining go there
            yield [(ratings[0], remaining)]
        else:
            # should never happen
            raise ValueError("No more ratings left")
    
    def weighted_avg(votes):
        return sum(r * n for r, n in votes) / sum(n for r, n in votes)
    
    for dist in distributions([1, 2, 3, 4, 5], 20):
        if weighted_avg(dist) == 3.85:
            print(dist)
    

    总共有10626 分布,其中146 产生正确的平均值。输出(一些):

    [(1, 0), (2, 0), (3, 3), (4, 17), (5, 0)]
    [(1, 0), (2, 0), (3, 4), (4, 15), (5, 1)]
    ...
    [(1, 2), (2, 3), (3, 1), (4, 4), (5, 10)]
    ...
    [(1, 5), (2, 0), (3, 1), (4, 1), (5, 13)]
    [(1, 5), (2, 1), (3, 0), (4, 0), (5, 14)]
    

    【讨论】:

    • 我怀疑蛮力是最好的解决方案。
    • @WolfLink 完全同意,并渴望看到更好的解决方案。 ;-)
    • 虽然 N=60 仍然相当快(总共 635376 个,正确的 2767 个)。奇怪的是,N=10、30、50、70 等似乎没有合适的分布。跨度>
    • 对于 N=60 的情况,我的版本只有 61617 次递归调用(这感觉像是某种代码高尔夫哈哈)。我很确定它仍然不是最佳的。
    • @tobias_k:如果 N 为 10,则评级必须加起来为 38.5,这是不可能的,因为不允许使用半星。还是你的意思是 N 意味着别的东西?
    【解决方案3】:

    (低效)蛮力解决方案。

    注意:可以通过将product(range(0, N+1), repeat=5) 替换为可以生成总和为 N 的 5 个数字的列表的其他内容来提高效率。

    找到所有长度为 5 的列表(用于评分)直到 N,然后计算加权平均值并与 R 进行比较。边走边打印列表

    from itertools import product
    
    def weighed_avergage(l, total):
        if sum(l) != total:
            return 0
        return sum(rating * stars for rating, stars in zip(l, range(5, 0, -1))) / float(total)
    
    N = 20
    R = 3.85
    
    for p in product(range(0, N+1), repeat=5):
        w_avg = weighed_avergage(p, N)
        if w_avg == R:
            print p
    

    应该在输出中看到(10, 4, 1, 3, 2),对应于你的问题10个五星、4个四星、1个三星、3个二星和2个一星

    【讨论】:

    • p 应该是选票的分布,即(4,3,1,6,2) 表示 4 个 5 星投票,3 个 4 星投票,等等?在这种情况下,我认为这是行不通的。
    • 是的,评分数按星数降序排列。为什么这不起作用?
    • 好吧,除非我非常误解某事,否则这可以创建 p,如 (0,1,2,3,4)(20,19,18,...),两者的总和都不是 20。而且,它可能不 i> 生成p,如(4,4,4,4,4),或(0,0,0,0,20)
    • 更新的答案确实会生成(0,0,0,0,20)。关于 20 的总和 - 是的,我知道生成的值比必要的要多。
    • 匹配分布的数量是正确的,但它总共尝试了 4084101 次。
    【解决方案4】:

    这是一种不使用蛮力的算法。缩进的代码显示了一个示例。

    您有评分数 N 及其平均数 R。
    si 为 i-stars 评分数(i in [1..5])。
    我们有s1 + s2 + s3 + s4 + s5 = N
    我们还有s1 + 2*s2 + 3*s3 + 4*s4 + 5*s5 = R*N

      s1 + s2 + s3 + s4 + s5 = 20
      s1 + 2*s2 + 3*s3 + 4*s4 + 5*s5 = 77
    

    因此s2 + 2*s3 + 3*s4 + 4*s5 = R*N - N.
    现在为s1 选择一个值并计算s2 + s3 + s4 + s5 = N - s1

      s2 + 2*s3 + 3*s4 + 4*s5 = 57
      s1 = 4
      s2 + s3 + s4 + s5 = 16
    

    我们可以继续s3 + 2*s4 + 3*s5 = (R*N - N) - (N - s1)
    s2 选择一个值并计算s3 + s4 + s5 = N - s1 - s2

      s3 + 2*s4 + 3*s5 = 41
      s2 = 2
      s3 + s4 + s5 = 14
    

    使用s3 重复并获取s5s4 的值。

      s4 + 2*s5 = 27
      s3 = 9
      s4 + s5 = 5
    
      s5 = 22
      s4 = -17
    

    现在,显然,这会产生错误的解决方案(在示例中,s5 &gt; 20s4 &lt; 0)。为了避免这种情况,我们可以每次都限制值的选择。
    我们需要选择s3,这样s4 + s5 &gt;= (s4 + 2*s5)/2,所以我们最终得到s5 &lt;= s4 + s5
    这只有在s3 + s4 + s5 &gt;= (s3 + 2*s4 + 3*s5)/3 时才有可能,因此是另一个约束,这次是s2
    最后,s1 的约束是s2 + s3 + s4 + s5 &gt;= (s2 + 2*s3 + 3*s4 + 4*s5)/4

    【讨论】:

    • 如果有人在 python 中实现了它,我很乐意包含它。我在该语言方面的经验不足,无法提供明确的解决方案。
    • 这基本上就是我的算法的工作原理。看我的回答。
    • @WolfLink 我会怀疑。我的算法不会进行数千次递归调用。即便如此,如果你得到示例问题的 2767 个解决方案,那么你的算法就是错误的。
    • 2767 是针对 tobias_k 提出的 N=60 的情况。我的算法确定 s5 的值范围,然后对该范围内的每个值进行递归并找到 s4 的值范围等。虽然我认为您的方法更精简一些,但它非常相似。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-04-29
    • 2015-08-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-13
    相关资源
    最近更新 更多