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

如何生成与所需列表保持固定距离的元素列表

  •  6
  • Mathieu  · 技术社区  · 7 年前

    我有一个可能性列表和一个期望的输入:

    possibles = [20, 30, 40, 50, 60, 70, 80, 100]
    desired = [20, 30, 40]
    

    我想生成“近距”列表。例子:

    # Distance of 1 (i.e. 1 element changes to a close-by)
    [30, 30, 40]
    [20, 40, 40]
    [20, 30, 30]
    [20, 30, 50]
    
    # Distance of 2:
    [40, 30, 40]
    [30, 30, 50]
    [30, 40, 40]
    ...
    

    我目前的版本一次只改变一个元素,因此,一旦距离超过1,我就会错过很多组合。

    def generate_close_by(possibles, desired):
        for k in range(1, 4):
            for i, elt in enumerate(desired):
                id = possibles.index(elt)
    
                new = desired[:]
                if id < len(possibles)-k-1:
                    new[i] = possibles[id+k]
                    yield (new)
    
                if id > k:
                    new[i] = possibles[id-k]
                    yield (new)
    
    # Output
    [30, 30, 40]
    [20, 40, 40]
    [20, 30, 50]
    [20, 30, 30]
    [40, 30, 40]
    [20, 50, 40]
    [20, 30, 60]
    [50, 30, 40]
    [20, 60, 40]
    [20, 30, 70]
    

    我很确定应该已经存在一个模块来进行这种迭代(itertools?),你能给我指一下写函数吗?

    谢谢。

    编辑:

    尝试时更新…

    我正在尝试生成一个与所需元素大小相同的列表,其中每个元素对应于我必须移动所需元素的量。

    desired = [20, 30, 40]
    # Distance of 1:
    distance = [1, 0, 0]
    distance = [0, 1, 0]
    distance = [0, 0, 1]
    distance = [-1, 0, 0]
    distance = [0, -1, 0]
    distance = [0, 0, -1]
    

    然后计划尝试创建新的列表,如果它不能(超出界限),它就继续。还没起作用,但可能是个好方法。

    4 回复  |  直到 7 年前
        1
  •  1
  •   Spinor8    7 年前

    我想我将展示一种更为冗长的方法,它更容易归纳。

    我先把问题写下来。

    possible_pts = [20, 30, 40, 50, 60, 70, 80, 100]
    starting_pt_in_idx = [0, 1, 2]
    distance = 2
    

    有三个轴可以“改变”。我首先找到轴变化的组合。

    N = len(starting_pt_in_idx)
    axis = list(range(N))
    
    import itertools
    axismoves = list(itertools.combinations_with_replacement(axis, distance))
    print(axismoves)
    

    接下来,我们把它放进垃圾箱。例如,如果我看到轴0出现两次,它就会变成[2,0,0]。

    abs_movements = []
    for combi in axismoves:
        move_bin = [0] * N
        for i in combi:
            move_bin[i] += 1
        abs_movements.append(move_bin)
    print(abs_movements)
    

    上面给出了绝对运动。要找到实际的运动,我们必须考虑沿该轴的变化可以是正的也可以是负的。

    import copy
    actual_movements = []
    for movement in abs_movements:
        actual_movements.append(movement)
        for i, move in enumerate(movement):
            if move != 0:
                _movement = copy.deepcopy(movement)
                _movement[i] = - move
                actual_movements.append(_movement)
    print(actual_movements)
    

    最后一步是将索引转换为实际位置。所以我们首先编写这个助手函数。

    def translate_idx_to_pos(idx_vect, points):
        idx_bounds = [0, len(points) - 1]
        pos_point = [0] * len(idx_vect)
        for i, idx_pos in enumerate(idx_vect):
            if idx_pos < idx_bounds[0] or idx_pos > idx_bounds[1]:
                return None
            else:
                pos_point[i] = points[idx_pos]
        return pos_point
    

    使用实际的移动作用于起始点索引,然后将其转换回位置。

    from operator import add
    final_pts = []
    for movement in actual_movements:
        final_pt_in_idx = list(map(add, starting_pt_in_idx, movement))
        final_point = translate_idx_to_pos(final_pt_in_idx, possible_pts)
        if final_point is not None:
            final_pts.append(final_point)
    
    print(final_pts)
    

    这给了

    [40, 30, 40]
    [30, 40, 40]
    [30, 20, 40]
    [30, 30, 50]
    [30, 30, 30]
    [20, 50, 40]
    [20, 40, 50]
    [20, 20, 50]
    [20, 40, 30]
    [20, 30, 60]
    [20, 30, 20]
    
        2
  •  1
  •   Zuma    7 年前

    是的,你是对的, itertools 在这里会很有用的。您需要的是查找 possibles 列出重复项,执行此操作的函数是 itertools.product

    from itertools import product
    
    possibles = [20, 30, 40, 50, 60, 70, 80, 100]
    desired = [20, 30, 40]
    
    def fake_hamming(cur, desired, possibles):
        assert len(cur) == len(desired)
    
        hamm = 0
        for i in range(len(cur)):
            assert cur[i] in possibles
            assert desired[i] in possibles
            hamm += abs(possibles.index(cur[i]) - possibles.index(desired[i]))
    
        return hamm
    
    def generate_close_by(desired, possibles, dist):
        all_possible_lists = product(possibles, repeat=len(desired))
        return [l for l in all_possible_lists if fake_hamming(l, desired, possibles) == dist]
    
    print(generate_close_by(desired, possibles,1))
    >>> [(20, 20, 40), (20, 30, 30), (20, 30, 50), (20, 40, 40), (30, 30, 40)]
    

    编辑 现在,您可以更改产品的组合(请参见下面的@tobias_k comment),这里还有 fake_hamming 函数xd 当然,对于大名单来说,这会很慢,但这是最通用的方法。

        3
  •  1
  •   Seljuk Gulcan    7 年前
    def distribute(number, bucket):
      if bucket == 1:
        yield [number]
        if number != 0:
          yield [-1 * number]
      elif number == 0:
        yield [0]*bucket
      else:
        for i in range(number+1):
          for j in distribute(number-i, 1):
            for k in distribute(i, bucket-1):
              yield j+k
    
    def generate(possibles, desired, distance):
      for index_distance_tuple in distribute(distance, len(desired)):
        retval = desired[:]
        for i, index in enumerate(index_distance_tuple):
          if index + i < 0 or index + i >= len(possibles):
            break
          retval[i] = possibles[index + i]
        else:
          yield retval
    

    对于距离1:

    for i in generate(possibles, desired, 1):
      print(i)
    

    输出:

    [30, 30, 40]
    [20, 40, 40]
    [20, 20, 40]
    [20, 30, 50]
    [20, 30, 30]
    

    对于距离2:

    for i in generate(possibles, desired, 2):
      print(i)
    

    输出:

    [40, 30, 40]
    [30, 40, 40]
    [30, 20, 40]
    [30, 30, 50]
    [30, 30, 30]
    [20, 50, 40]
    [20, 40, 50]
    [20, 40, 30]
    [20, 20, 50]
    [20, 20, 30]
    [20, 30, 60]
    [20, 30, 20]
    
        4
  •  0
  •   tobias_k    7 年前

    您可以尝试一种递归方法:跟踪剩余的距离并生成相关元素的组合。

    def get_with_distance(poss, des, dist, k=0):
        if k < len(des):
            i = poss.index(des[k])
            for n in range(-dist, dist+1):
                if 0 <= i + n < len(poss):
                    for comb in get_with_distance(poss, des, dist - abs(n), k+1):
                        yield [poss[i + n]] + comb
        elif dist == 0:
            yield []
    

    如果还存在一些“死胡同”,这仍然会遇到一些“死胡同”。 dist 剩余,但 des 列表是空的,但总的来说,比起前面生成所有组合并在后面检查它们的距离,检查组合的次数要少得多。

    如果可能元素的列表较长,则可能需要创建 dict 首先将元素映射到它们的索引,因此您不必这样做 poss.index(first) 每次。

    例子:

    possibles = [20, 30, 40, 50, 60, 70, 80, 100]
    desired = [20, 30, 40]
    for x in get_with_distance(possibles, desired, 2):
        print(x)
    

    输出:

    [20, 20, 30]
    [20, 20, 50]
    [20, 30, 20]
    [20, 30, 60]
    [20, 40, 30]
    [20, 40, 50]
    [20, 50, 40]
    [30, 20, 40]
    [30, 30, 30]
    [30, 30, 50]
    [30, 40, 40]
    [40, 30, 40]