Python逻辑,用于查找笛卡尔系统上两组点之间的最小总距离

mqxuamgl  于 2023-04-19  发布在  Python
关注(0)|答案(3)|浏览(121)

想象一个笛卡尔的游戏场。它有3个旗帜在3个不同的位置。也有3个球员在3个不同的位置。目标是让一个球员的位置的旗帜。我试图分配哪个球员应该去哪个旗帜通过最小化的总距离由所有3个球员。我正在寻找一个好的Python逻辑来实现这一点,并确定哪个球员应该去哪个旗帜。

# This is the dictionary of distances between each player and each flag.
# (A,B,C) are the flag IDs
# (x,y,z) are the player IDs
# so, the distance between Flag A and Player x is 12 units and so on

dist_dict = {
    "A": [{"x": 12}, {"y": 10}, {"z": 20}],
    "B": [{"x": 14}, {"y": 18}, {"z": 25}],
    "C": [{"x": 8}, {"y": 15}, {"z": 16}],
}

在这种情况下,预期的输出为

[{'A':'y'},{'B':'x'},{'C':'z'}]

因此,要考虑的约束条件是:
1.一名球员只能被分配到一面旗帜
1.任何玩家只需要到达旗帜一次
1.选择所有三个玩家行进的总距离最小的场景。因此,上面的玩家'x'被分配到旗'B',即使'x'更接近旗'C'
数据不需要以我在这里展示的方式存储。如果存储距离数据更容易获得所需的输出,请推荐一种不同的方法

y53ybaqx

y53ybaqx1#

内部数据结构(dist_dict内部)也可以是一个字典,这样您就可以通过键访问所有的距离值。
因为你只有三个玩家,所以可以迭代所有可能的玩家到旗帜的分配,看看哪一个会导致最小的总距离。有一个itertools函数可以获得这个可迭代对象:permutations

from itertools import permutations

dist_dict = {'A': {'x': 12, 'y': 10, 'z': 20},
             'B': {'x': 14, 'y': 18, 'z': 25},
             'C': {'x': 8, 'y': 15, 'z': 16}}

flags = list(dist_dict)
players = list(dist_dict['A'])

min_total_dist = 1000

for player_order in permutations(players):
    total_dist = sum(dist_dict[flag][player] 
                     for flag, player in zip(flags, player_order))
    if total_dist < min_total_dist:
        min_total_dist = total_dist
        best_player_order = player_order

print("optimal solution:")
for player, flag in zip(best_player_order, flags):
    print(f"    player {player} to flag {flag}")

print(f"optimal total distance: {min_total_dist}")
optimal solution:
    player y to flag A
    player x to flag B
    player z to flag C
optimal total distance: 40
ux6nzvsh

ux6nzvsh2#

有不同的方法来解决这个问题。这里有一个不太简短的答案和解释:

from collections import defaultdict
from itertools import product

dist_dict = {
    "A": [{"x": 12}, {"y": 10}, {"z": 20}],
    "B": [{"x": 14}, {"y": 18}, {"z": 25}],
    "C": [{"x": 8}, {"y": 15}, {"z": 16}],
}

def get_indexes(t: tuple):
    """return a list containing the indexes of items of the `t` inside `player_moves"""
    return [lst.index(i) for lst, i in zip(player_moves.values(), t)]

# Creating a dictionary which maps player names to all available moves.
player_moves = defaultdict(list)
for lst in dist_dict.values():
    for d in lst:
        # Here a copy is needed because later we want to use this dictionary
        player, distance = d.copy().popitem()
        player_moves[player].append(distance)
print(player_moves)

# While iterating, we need to keep track of three things:
# 1. `minimun`: minimum sum of distances
# 2. `moves`: the instances that each player should go
# 3. `moves_indexes`: the indexes of numer 2.
minimun = float("inf")
moves = None
moves_indexes = None
# `product` generates all available moves for us.
for i in product(*player_moves.values()):
    indexes = get_indexes(i)

    # This check is needed because there shouldn't be a player with no move!
    if len(indexes) == len(set(indexes)):
        sum_of_distances = sum(i)
        if sum_of_distances < minimun:
            minimun = sum_of_distances
            moves = i
            moves_indexes = indexes

print(moves)
print(moves_indexes)
print({k: v[i].popitem()[0] for (k, v), i in zip(dist_dict.items(), moves_indexes)})

输出:

defaultdict(<class 'list'>, {'x': [12, 14, 8], 'y': [10, 18, 15], 'z': [20, 25, 16]})
(14, 10, 16)
[1, 0, 2]
{'A': 'y', 'B': 'x', 'C': 'z'}
iyr7buue

iyr7buue3#

使用itertools.permutations,并使用一个计算总距离的函数最小化。(您可以对球员或旗帜进行排列。在本例中,我对旗帜进行排列。)

import itertools

dist = { # I have altered the format of this input slightly
        "A": {"x": 12, "y": 10, "z": 20},
        "B": {"x": 14, "y": 18, "z": 25},
        "C": {"x": 8, "y": 15, "z": 16},
    }

def total_distance(perm):
    return sum(d[flag] for d, flag in zip(dist.values(), perm))

best_perm = min(itertools.permutations("xyz"), key=total_distance)
result = dict(zip(dist, best_perm))

另一个选项使用pandasnumpy,这更复杂,但如果你有大量的旗帜和球员,可能会工作得更快。

import pandas as pd
import numpy as np
import itertools
dist = pd.DataFrame({ 
    "A": {"x": 12, "y": 10, "z": 20},
    "B": {"x": 14, "y": 18, "z": 25},
    "C": {"x": 8, "y": 15, "z": 16},
})
# make a numeric index [0,1,2]
index = list(range(len(dist)))             

# get all permutations of [0,1,2]
perms = list(itertools.permutations(index))   

# find the index of the permutation of [0,1,2] that minimizes total distance
best_perm_index = dist.to_numpy()[perms, index].sum(axis=1).argmin()  

# get the permutation itself, as a list
best_perm = list(perms[best_perm_index])     

# convert back to a dictionary
result = dict(zip(dist.columns, dist.iloc[best_perm].index))

相关问题