代码之家  ›  专栏  ›  技术社区  ›  jbochi

SQL计算多个向量的Tanimoto系数

  •  4
  • jbochi  · 技术社区  · 16 年前

    我有一个包含配方成分的表,我实现了一个函数来计算 Tanimoto coefficient 在成分之间。它的速度足以计算两个成分之间的系数(需要3个sql查询),但它的伸缩性不好。要计算所有可能成分组合之间的系数,需要N+(N*(N-1))/2次查询或500500次查询,只需1k种成分。有没有更快的方法?以下是我目前得到的信息:

    class Filtering():
      def __init__(self):
        self._connection=sqlite.connect('database.db')
    
      def n_recipes(self, ingredient_id):
        cursor = self._connection.cursor()
        cursor.execute('''select count(recipe_id) from recipe_ingredient
            where ingredient_id = ? ''', (ingredient_id, ))
        return cursor.fetchone()[0]
    
      def n_recipes_intersection(self, ingredient_a, ingredient_b):
        cursor = self._connection.cursor()
        cursor.execute('''select count(drink_id) from recipe_ingredient where
            ingredient_id = ? and recipe_id in (
            select recipe_id from recipe_ingredient
            where ingredient_id = ?) ''', (ingredient_a, ingredient_b))
        return cursor.fetchone()[0]
    
      def tanimoto(self, ingredient_a, ingredient_b):
        n_a, n_b = map(self.n_recipes, (ingredient_a, ingredient_b))
        n_ab = self.n_recipes_intersection(ingredient_a, ingredient_b)
        return float(n_ab) / (n_a + n_b - n_ab)
    
    4 回复  |  直到 16 年前
        1
  •  4
  •   S.Lott    16 年前

    你为什么不把所有的食谱都存到内存中,然后在内存中计算谷本系数呢?

    它更简单,速度更快。

        2
  •  3
  •   jbochi    16 年前

    如果有人感兴趣,这是我在Alex和s.Lotts的建议后提出的代码。谢谢你们。

    def __init__(self):
        self._connection=sqlite.connect('database.db')
        self._counts = None
        self._intersections = {}
    
    def inc_intersections(self, ingredients):
        ingredients.sort()
        lenght = len(ingredients)
        for i in xrange(1, lenght):
            a = ingredients[i]
            for j in xrange(0, i):
                b = ingredients[j]
                if a not in self._intersections:
                    self._intersections[a] = {b: 1}
                elif b not in self._intersections[a]:
                    self._intersections[a][b] = 1
                else:
                    self._intersections[a][b] += 1
    
    
    def precompute_tanimoto(self):
        counts = {}
        self._intersections = {}
    
        cursor = self._connection.cursor()
        cursor.execute('''select recipe_id, ingredient_id
            from recipe_ingredient
            order by recipe_id, ingredient_id''')
        rows = cursor.fetchall()            
    
        print len(rows)
    
        last_recipe = None
        for recipe, ingredient in rows:
            if recipe != last_recipe:
                if last_recipe != None:
                    self.inc_intersections(ingredients)
                last_recipe = recipe
                ingredients = [ingredient]
            else:
                ingredients.append(ingredient)
    
            if ingredient not in counts:
                counts[ingredient] = 1
            else:
                counts[ingredient] += 1
    
        self.inc_intersections(ingredients)
    
        self._counts = counts
    
    def tanimoto(self, ingredient_a, ingredient_b):
        if self._counts == None:
            self.precompute_tanimoto()
    
        if ingredient_b > ingredient_a:
            ingredient_b, ingredient_a = ingredient_a, ingredient_b
    
        n_a, n_b = self._counts[ingredient_a], self._counts[ingredient_b]
        n_ab = self._intersections[ingredient_a][ingredient_b]
    
        print n_a, n_b, n_ab
    
        return float(n_ab) / (n_a + n_b - n_ab)
    
        3
  •  1
  •   Alex Martelli    16 年前

    如果您有1000种配料,1000个查询就足以将每种配料映射到内存中的一组食谱。如果(比如)一种成分通常是大约100份食谱的一部分,那么每一组都需要几KB,因此整个词典只需要几MB——将整个内容保存在内存中绝对没有问题(如果每种成分的平均食谱数增长一个数量级,那么这仍然不是一个严重的内存问题)。

    result = dict()
    for ing_id in all_ingredient_ids:
        cursor.execute('''select recipe_id from recipe_ingredient
            where ingredient_id = ?''', (ing_id,))
        result[ing_id] = set(r[0] for r in cursor.fetchall())
    return result
    

        4
  •  0
  •   Shane Holloway    16 年前

    我认为这将把交叉点的选择减少到每对2个,每对总共4个查询。你无法摆脱O(N^2),因为你正在尝试所有对——N*(N-1)/2就是有多少对。

    def n_recipes_intersection(self, ingredient_a, ingredient_b):
      cursor = self._cur
      cursor.execute('''
        select count(recipe_id)
          from recipe_ingredient as A 
            join recipe_ingredient as B using (recipe_id)
          where A.ingredient_id = ? 
            and B.ingredient_id = ?;
          ''', (ingredient_a, ingredient_b))
      return cursor.fetchone()[0]