从列表中获得分数近似值

时间:2013-01-08 14:54:45

标签: python list matching

我刚刚开始使用Python。

这是一个齿轮率计算器。

我有一个包含367到7645373范围内5000个整数的列表和一个原始分数,可能是1/10或34561/43521到10/1。

我需要创建一个新的分数,其值接近原始分数,由表中存在的分子和分母组成。

实际上我想要一个匹配列表,按照它与原始分数的偏差排序。

我有一个解决方案但是需要很长时间才能得到值为1/10的结果,因为367/3670或368/3680或4352/43520等解决方案是等效的。

Pythonist如何做到这一点?

请不要告诉我这是一个C库的案例! :d

干杯 安东尼奥

def searcharatio(l, a):
    b = []
    mx = l[-1][0]
    ln = l[0][0]
    ld = l[0][0]
    i = max(int(ln/a.numerator-1), int(ld/a.denominator)-1)
    print i
    while 1:
        n = a.numerator * i
        d = a.denominator * i

        if n > mx or d > mx:
            return sorted(b)
        if n > 0.9*ln and d > 0.9*ld:
            # enumerate es lista 2 elem 0=num orden, 1=elemento
            ri = (min(enumerate(l), key=lambda x:abs(x[1][0]-n)))
            ro = (min(enumerate(l), key=lambda x:abs(x[1][0]-d)))
            ln = ri[1][0]
            ld = ro[1][0]

            e = [abs(1.0 - ((float(ln)/ld) / (float(n)/d))), i, ri, ro]
            b.append(e)
        i+=1

5 个答案:

答案 0 :(得分:2)

Python在迭代时往往很慢;使用NumPy的矢量化解决方案可能会更快。

def search_ratio(l, a):
    l = np.array(l)
    t = l.astype(float).reshape(-1, 1) / l.reshape(1, -1)
    i = np.unravel_index(np.argsort(np.where(t > a, t / a, a / t).flat), t.shape)
    return l[i[0]], l[i[1]]

例如,search_ratio(range(2, 6), 1.3)会给出:

(array([4, 5, 3, 5, 2, 3, 4, 5, 4, 4, 3, 5, 2, 3, 2, 2]),
 array([3, 4, 2, 3, 2, 3, 4, 5, 2, 5, 4, 2, 3, 5, 4, 5]))

因为4/3是最接近1.3的比率,5/4是下一个最接近的等等。

请注意,t(可用比率表)可以缓存以提高效率。

答案 1 :(得分:1)

from __future__ import division
import itertools

def searchratio(input_range):
    my_ratios=set()
    for x in itertools.combinations(input_range, 2):
        y=x[0]/x[1]
        if y==1/10 or (10/1>y>34561/43521):
            my_ratios.add(x)
    return my_ratios

if __name__=='__main__':
    from time import time
    t1=time()
    nk=len(searchratio(xrange(4000)))
    t2=time()
    print t2-t1, nk

在python中; 4000项目列表需要6.5秒 在pypy; 4000项目列表需要3.25秒 如果你想进一步减少它;您必须选择cython或将代码并行处理ipython。 简单的步骤就是在pypy-chttps://pypy.org/)中运行您的代码;您可以立即看到50%的时间缩短。

答案 2 :(得分:0)

计算所有结果并在之后排序。

from collections import namedtuple
import random

fraction_target = 1 / 10.0
limit_left = fraction_target * 0.9
limit_right = fraction_target * 1.1

result = namedtuple('result', 'val1 val2 fraction')

values = [random.randint(367, 7645373) for _ in range(5000)]

fraction_results = []
for value_1 in values:
    for value_2 in values:
        fraction = value_1 / float(value_2)
        if limit_left < fraction < limit_right:
            fraction_results.append(result(value_1, value_2, fraction))

fraction_results.sort(key=lambda x: abs(x.fraction - fraction_target))

print(fraction_results[0:10])

我将存储的结果限制在所需分数的90%到110%之间。

答案 3 :(得分:0)

由于此解决方案的局限性,我不确定这是否会有任何帮助,但我发布它仍然是因为理论至少非常适用于您的问题。

我假设您的列表只是一个数字列表。所以我们首先让它成为set,因为我们一再检查会员资格,而且设置更快:

integers = set(integers)

现在,fractions.Fractions类有一个名为limit_denominator的漂亮方法,可能对您有所帮助。它基于Euclid's algorithm,可用于为任何实数构建名为continued fraction的内容。然后,可以使用数字的连续分数来构造具有给定分母约束的该数的最佳有理逼近。

为了将它应用到您的问题中,我们从您希望表示的Fraction开始,并在前一个近似分母的下方重复调用limit_denominator,直到找到分子的分子为止和分母都在integers。由于较大的分母意味着更好的近似值,因此第一场比赛是最好的。

import fractions

def best_approximation(numerator, denominator, integers):
    min_int = min(integers)
    max_int = max(integers)
    org_frac = fractions.Fraction(numerator, denominator)
    if org_frac > 1:
        # the numerator is larger than the denominator, so our maximum 
        # denominator must be adjusted down accordingly, since we also
        # want the numerator to be below max_int
        curr_max = int(max_int / org_frac)
    else:
        curr_max = max_int
    while True:
        fr = org_frac.limit_denominator(max_int)
        if fr.numerator < min_int or fr.denominator < min_int:
            return None
        if fr.numerator in integers and fr.denominator in integers:
            return fr
        max_int = fr.denominator - 1

我用一个整数列表测试了它,它只是367和7645373之间的素数,我得到了这个输出:

>>> print best_approximation(34561, 43521, integers)
4513/5683

这仍然没有那么快,因为limit_denominator方法构建的内部结构可以重用。你可以通过复制源代码并修改它来修复它,或者只是使用维基百科文章从头开始实现算法。

我提到的限制是:使用此方法可能无法找到最佳近似值。如果整数列表太稀疏,这可能是个问题。在我的主要列表的特定情况下,使用7645373到17645373的随机分子和分母,它只适用于大约一半的情况,这显然不够好。另一方面,当你得到答案时,你会知道它是一个非常好的近似值。

答案 4 :(得分:0)

我结束了使用bisect和排序列表。

import bisect
import random
import time
def searchratio(t,n,d):
  #n must be <= d if it's not he case swap ,and swap the results returned
  b=[]
  a=float(n)/d
  t0=t[0]
  x1=bisect.bisect(t,t0/a)
  print a,x1,t0
  lm=len(t)-2
  for ti in t[x1:lm]:
      x1=bisect.bisect_left(t,ti*a)
      b.append([t[x1],ti])
      b.append([t[x1+1],ti])
  b.sort(key = lambda x:abs(x[0]/float(x[1])-a))
  return b   

#-----test program--------------------------------------------------   
l=[random.randrange(100000,10000000) for i in xrange(100000)]
l.sort()
n=3
d=100
t=time.clock()
r=searchratio(l,n,d)
t=time.clock() - t
print t, len(r), float(n)/d 
for x in r:
    print x, x[0]/float(x[1])