Python sort based on 2 class attributes - python

Edit: just found out that I'm using py 2.6.2 (work installed so I can't do much about that)
So I'm trying to find the best way to sort a list based on 2 different class attributes
This list is basically some info for moving people from room to room in a company where some people might be part of a chain move
(i.e. Joe Blow has to move before we can move Jane Doe into Joe's spot and Jane has to move before John Wick can move into Jane's spot etc.)
I get all the info something like below but there can also be people that aren't part of the chain move like Dan Man in the example below.
John Wick 303.10 -> 415.09
Dan Man 409.08 -> 221.02
Joe Blow 225.06 -> 512.01
Jane Doe 415.09 -> 225.06
I have all the relevant info split into a class with
startRoom
endRoom
originalString
So that part isn't an issue but when I try to "brute force" sort it like below: (Note, I do the list(chains) as it is previously a set to make sure I don't get doubles in there)
def sortChains():
global chains
#convert the set of chains to a list for list functions
chains = list(chains)
for x, move1 in enumerate(chains):
for y, move2 in enumerate(chains):
if move1.startRoom == move2.endRoom:
temp = chains[y]
chains.remove(move2)
chains.insert(x,temp)
continue
My problem is the sorting. One part of the problem is finding the person that is at the start of the chain and then sorting correctly after that.
Any ideas/help is totally appreciated. And yes I know a double loop while moving stuff in the loop isn't the best but it's been the best I could think of at the time.

First, you have to create a dependency graph and determine (a) which person has to move before some other person can move, and (b) which persons can move right now. We can use a 1:1 mapping here, but in the more general case, you might have to use a 1:n, n:1, or n:m mapping.
moves = {"John Wick": ("303.10", "415.09"),
"Dan Man": ("409.08", "221.02"),
"Joe Blow": ("225.06", "512.01"),
"Jane Doe": ("415.09", "225.06")}
# or dict((move.originalString, (move.startRoom, move.endRoom)) for move in list_of_moves)
# mapping {initial room -> name}
rooms = {start: name for (name, (start, end)) in moves.items()}
# Python 2.6: dict((start, name) for (name, (start, end)) in moves.items())
# mapping {moves_first: moves_after}
before = {rooms[end]: name for name, (start, end) in moves.items() if end in rooms}
# Python 2.6: dict((rooms[end], name) for name, (start, end) in moves.items() if end in rooms)
# persons that can move now
can_move = set(moves) - set(before.values())
Now, we can see who can move, move that person, and then update the persons who can move based on what person had to wait for that person to move, if any.
result = []
while can_move:
# get person that can move, add to result
name = can_move.pop()
result.append(name)
# add next to can_move set
if name in before:
can_move.add(before.pop(name))
Afterwards, result is ['Joe Blow', 'Jane Doe', 'John Wick', 'Dan Man']
Complexity should be O(n), but of course, this will fail if there are cyclic dependencies.

def do(moves):
"""RETURNS: [0] Sequence of persons to move.
[1] Remainder
"""
# (following line copied from 'tobias_k', replaced 'rooms' with 'current_db')
# map: target position to person who occupies it
current_db = { start: name for (name, (start, end)) in moves.items() }
# maintain set of persons who are free to move to their target location
liberated_set = set()
# map occupier of a location -> set of people who would take his place.
liberation_db = defaultdict(set)
# whosoever wants to move to a free place -> liberated.
# else -> liberation_db
for name, (start, end) in moves.items():
occupier = current_db.get(start)
if occupier is None: liberated_set.add(name)
else: liberation_db[occupier].add(name)
sequence = []
while liberated_set:
# add people to the sequence who are free to move
sequence.extend(liberated_set)
# get new set of people who are free to move to their target
# because their target position is no longer occupied.
new_liberated_set = set()
for occupier in liberated_set:
if not occupier in liberation_db: continue
new_liberated_set.extend(liberation_db[occupier])
del liberation_db[occupier]
liberated_set = new_liberated_set
return sequence, set(liberation_db.values())

Related

Having trouble creating script to find the name that shows up in each list in Python

Dr. Smith was killed in the studio with a knife by one of his heirs! Create a script to find the murderer! Make sure to show your answer.
The following people are Smith's heirs: Aiden, Tori, Lucas, Isabelle.
The following people were in the studio: Lucas, Natalie, Tori.
The following people own a knife: Isabelle, Tori, Natalie.
My code:
heirs = ["Aiden", "Tori", "Lucas", "Isabelle"]
ppleinstudio = ["Lucas", "Natalie", "Tori"]
knife = ["Isabelle", "Tori", "Natalie"]
# killer is the one who exists in three of the lists
# merge the lists
merged = [*heirs,*ppleinstudio,*knife]
L1=[]
for i in merged:
if i not in L1:
L1.append(i)
else:
print(i,end=' ')
output:
Lucas Tori Isabelle Tori Natalie
What am I missing to get it to look for the repeating name?
I am not sure that the code you implemented is doing what you wanted it to do, maybe you should try to check the contents of the merged list and see what happens as you iterate through the for loop.
Nevertheless, for the sake of providing a solution to your problem, if you are allowed to use sets you could easily solve this by doing the following:
heirs = ["Aiden", "Tori", "Lucas", "Isabelle"]
ppleinstudio = ["Lucas", "Natalie", "Tori"]
knife = ["Isabelle", "Tori", "Natalie"]
h_set = set(heirs)
s_set = set(ppleinstudio)
k_set = set(knife)
culprit = h_set.intersection(s_set.intersection(k_set)).pop()
print(culprit)
>> 'Tori'
But if this is some kind of homework you should probably try to work your way to a solution on paper/whiteboard first, and figure out why your approach is not working.
You could do something like this, cycle through each entry in the merged list, and break the three requirements into three boolean statements:
heirs = ["Aiden", "Tori", "Lucas", "Isabelle"]
ppleinstudio = ["Lucas", "Natalie", "Tori"]
knife = ["Isabelle", "Tori", "Natalie"]
# killer is the one who exists in three of the lists
# merge the lists
merged = [*heirs,*ppleinstudio,*knife]
for person in merged:
is_heir = person in heirs
is_in_studio = person in ppleinstudio
has_knife = person in knife
if(is_heir and is_in_studio and has_knife):
print(person)
break
Output:
Tori
This will be a little inefficient because if you print out the contents of merged, you'll notice that there are duplicate names, but seeing as your question doesn't mention anything about efficiency - this will get the job done just fine.
If you are concerned about this inefficiency you can use the set operator on the merged list and iterate over that instead:
merged = set(merged)

Python: get previous list entry(relative to a variable assigned to a current list position)

I am putting together a very simple text-based game in Python which contains 5 rooms that the player can move between.
The player starts in the central room, gameMap[2].
Below is the code that provides the player's location.
gameMap = ['room0','room1','room2','room3','room4']
playerLocation = gameMap[2]
Now suppose the player wants to move to the left.
This means that I must assign playerLocation to gameMap[1].
playerLocation = gameMap[1]
With just 5 rooms, this could be done reasonably easily. But for scaling purposes, I want to be able to assign playerLocation to 'the current list entry -1', assuming this is in range.
This instruction would make assignments as follows:
if playerLocation is gameMap[4], playerLocation = gameMap[3]
if playerLocation is gameMap[1], playerLocation = gameMap[0]
I have considered using next(gameMap) but this seems to present 2 issues:
1) It does not allow me to reference the player's current location, since it must start at gameMap[0]
2) It does not seem to work in reverse, and there doesn't seem to be a previous() function owing to Python's architecture. (sorry if I've got the wrong terminology here :).
I have looked everywhere but cannot seem to find any way to do this. Is it possible?
What you can do in this case is have a variable which is used as the point in which the player is at; for example:
current = 0
then do whatever calculations you must and call playerLocation = gameMap[current]
If you wanted to go back a level, you can just do:
current -= 1
playerLocation = gameMap[current]
I'm not sure if I'm missing a part of your question as this seems relatively straight forward; correct me if I misunderstood.
Have you tried simply having some sort of index to the players's position?
Then if the player is in the first room the index is 0 for example, and you can do:
index = index + 1 if index + 1 < len(gameMap) else index
when going forward and
index = index - 1 if index > 0 else index
when going backwards.
If you want it scalable, probably some state machine implementation that you can populate via some externally loaded text file would be preferable. These threads might contain some pointers in this direction:
Python state-machine design
https://codereview.stackexchange.com/q/193753
https://codereview.stackexchange.com/q/155661
If you want to keep it simple, make your map a dictionary. There you can also contain the state transitions. Optionally, you can add a sub-dictionary that contains possibly state-specific available actions and the corresponding state transitions.
Untested example:
map = {
'room0': {'next': 'room1', 'prev': 'room4', 'description': '...'},
'room1': {'next': 'room2', 'prev': 'room0', 'description': '...'},
'room2': {'next': 'room3', 'prev': 'room1', 'description': 'The entrance'},
'room3': {'next': 'room4', 'prev': 'room2', 'description': '...'},
'room4': {'next': 'room0', 'prev': 'room3', 'description': '...'},
}
current_state = 'room2'
# transitions be like
next_state = map[current_state]['next']
prev_state = map[current_state]['prev']

Two way matching for Secret Santa game [duplicate]

This question already has answers here:
Secret santa algorithm
(9 answers)
Closed 7 years ago.
I am trying to write a script that pairs up men and women for a secret Santa type event. So I have 2 lists of boys and girls, and want to carry out 2 way matching, but at the moment I can only seem to figure out how to do 1 way matching.
Furthermore the problem I have is this... in the example below if Kedrick gets Annabel, then Annabel can't get Kedrick. Kedrick has to get someone else from the list.
My current implementation is as follows, how can I extend its functionality to meet the abovementioned requirements?
boys = ['Kedrick','Jonathan','Tim','Philip','John','Quincy'];
girls = ['Annabel','Janet','Jocelyn','Pamela','Priscilla','Viviana'];
matches = []
for i in boys:
rand - randint(0, len(girls-1)
fullname = "{} matched with {}".format(i, girls(rand)
del girls(rand)
matches.append(fullname)
print matches
This could probably be done with fewer loops and a lot less code but here is my solution! Created 2 dict's to store names and their targets (dict's could be combined or done at the same time to cut down on memory issues, but with a program this size I don't think you would ever run into this issue!
boys = ['Kedrick','Jonathan','Tim','Philip','John','Quincy'];
girls = ['Annabel','Janet','Jocelyn','Pamela','Priscilla','Viviana'];
matchesBoys = {i:{'to':''} for i in boys}
matchesGirls = {i:{'to':''} for i in girls}
for name in boys:
giveTo = girls[random.randint(0, len(girls)-1)]
girls.remove(giveTo)
matchesBoys[name]['to']=giveTo
for name in matchesGirls:
giveTo = boys[random.randint(0, len(boys)-1)]
boys.remove(giveTo)
matchesGirls[name]['to']=giveTo
del boys, girls
for i in matchesBoys:
print "%s matched with %s"%(i, matchesBoys[i]['to'])
for i in matchesGirls:
print '%s matched with %s'%(i, matchesGirls[i]['to'])
Shuffle both lists and put them in a ring, with every other element being from the first or second list. Each person gives a gift to the one on their right. Something similar to a list like this:
[Girl, Boy, Girl, Boy, ..., Boy]
The last element gives a gift to the first.
It works under the assumption that both lists have the same amount of elements and that there are at least four elements in total, otherwise the problem is unsolvable.
This gives one solution that fulfills your constraints. The general solution to the problem is to find a directed bipartite graph between the sets where each vertex have exactly two edges, one incoming and one outgoing. Perhaps the solution to that problem also always creates a ring?
This is an implementation that creates a circle with alternate boy and girl. See #Emil Vickstom's answer for an explanation of the idea.
from random import shuffle
boys = ['Kedrick','Jonathan','Tim','Philip','John','Quincy'];
girls = ['Annabel','Janet','Jocelyn','Pamela','Priscilla','Viviana'];
shuffle(boys)
shuffle(girls)
circle = [person for pair in zip(boys, girls) for person in pair]
print(' -> '.join(circle + circle[:1]))
Output:
Tim -> Priscilla -> Quincy -> Annabel -> John -> Janet -> Kedrick ->
Jocelyn -> Philip -> Pamela -> Jonathan -> Viviana -> Tim

Pair items of a list depending on value

I have an xml file like the following:
<edge from="0/0" to="0/1" speed="10"/>
<edge from="0/0" to="1/0" speed="10"/>
<edge from="0/1" to="0/0" speed="10"/>
<edge from="0/1" to="0/2" speed="10"/>
...
Note, that there exist pairs of from-to and vice versa. (In the example above only the pair ("0/0","0/1") and ("0/1","0/0") is visible, however there is a partner for every entry.) Also, note that those pairs are not ordered.
The file describes edges within a SUMO network simulation. I want to assign new speeds randomly to the different streets. However, every <edge> entry only describes one direction(lane) of a street. Hence, I need to find its "partner".
The following code distributes the speed values lane-wise only:
import xml.dom.minidom as dom
import random
edgexml = dom.parse("plain.edg.xml")
MAX_SPEED_OPTIONS = ["8","9","10"]
for edge in edgexml.getElementsByTagName("edge"):
x = random.randint(0,2)
edge.setAttribute("speed", MAX_SPEED_OPTIONS[x])
Is there a simple (pythonic) way to maybe gather those pairs in tuples and then assign the same value to both?
If you know a better way to solve my problem using SUMO tools, I'd be happy too. However I'm still interested in how I can solve the given abstract list problem in python as it is not just a simple zip like in related questions.
Well, you can walk the list of edges and nest another iteration over all edges to search for possible partners. Since this is of quadratic complexity, we can even reduce calculation time by only walking over not yet visited edges in the nested run.
Solution
(for a detailed description, scroll down)
import xml.dom.minidom as dom
import random
edgexml = dom.parse('sampledata/tmp.xml')
MSO = [8, 9, 10]
edge_groups = []
passed = []
for idx, edge in enumerate(edgexml.getElementsByTagName('edge')):
if edge in passed:
continue
partners = []
for partner in edgexml.getElementsByTagName('edge')[idx:]:
if partner.getAttribute('from') == edge.getAttribute('to') \
and partner.getAttribute('to') == edge.getAttribute('from'):
partners.append(partner)
edge_groups.append([edge] + partners)
passed.extend([edge] + partners)
for e in edge_groups:
print('NEW EDGE GROUP')
x = random.choice(MSO)
for p in e:
p.setAttribute('speed', x)
print(' E from "%s" to "%s" at "%s"' % (p.getAttribute('from'), p.getAttribute('to'), x))
Yields the output:
NEW EDGE GROUP
E from "0/0" to "0/1" at "8"
E from "0/1" to "0/0" at "8"
NEW EDGE GROUP
E from "0/0" to "1/0" at "10"
NEW EDGE GROUP
E from "0/1" to "0/2" at "9"
Detailed description
edge_groups = []
passed = []
Initialize the result structure edge_groups, which will be a list of lists holding partnered edges in groups. The additional list passed will help us to avoid redundant edges in our result.
for idx, edge in enumerate(edgexml.getElementsByTagName('edge')):
Start iterating over the list of all edges. I use enumerate here to obtain the index at the same time, because our nested iteration will only iterate over a sub-list starting at the current index to reduce complexity.
if edge in passed:
continue
Stop, if we have visited this edge at any point in time before. This does only happen if the edge has been recognized as a partner of another list before (due to index-based sublisting). If it has been taken as the partner of another list, we can omit it with no doubt.
partners = []
for partner in edgexml.getElementsByTagName('edge')[idx:]:
if partner.getAttribute('from') == edge.getAttribute('to') \
and partner.getAttribute('to') == edge.getAttribute('from'):
partners.append(partner)
Initialize helper list to store identified partner edges. Then, walk through all edges in the remaining list starting from the current index. I.e. do not iterate over edges that have already been passed in the outer iteration. If the potential partner is an actual partner (from/to matches), then append it to our partners list.
edge_groups.append([edge] + partners)
passed.extend([edge] + partners)
The nested iteration has passed and partners holds all identified partners for the current edge. Push them into one list and append it to the result variable edge_groups. Since it is unneccessarily complex to check against the 2-level list edge_groups to see whether we have already traversed an edge in the next run, we will additionally keep a list of already used nodes and call it passed.
for e in edge_groups:
print('NEW EDGE GROUP')
x = random.choice(MSO)
for p in e:
p.setAttribute('speed', x)
print(' E from "%s" to "%s" at "%s"' % (p.getAttribute('from'), p.getAttribute('to'), x))
Finally, we walk over all groups of edges in our result edge_groups, randomly draw a speed from MSO (hint: use random.choice() to randomly choose from a list), and assign it to all edges in this group.

Python Data Structure Selection

Let's say I have a list of soccer players. For now, I only have four players. [Messi, Iniesta, Xavi, Neymar]. More players will be added later on. I want to keep track of the number of times these soccer players pass to each other during the course of a game. To keep track of the passes, I believe I'll need a data structure similar to this
Messi = {Iniesta: 4, Xavi: 5 , Neymar: 8}
Iniesta = {Messi: 4, Xavi: 10 , Neymar: 5}
Xavi = {Messi: 5, Iniesta: 10 , Neymar: 6}
Neymar = {Messi: 8, Iniesta: 5 , Xavi: 6}
Am I right to use a dictionary? If not, what data structure would be better suited? If yes, how do I approach this using a dictionary though? How do I address the issue of new players being included from time to time, and creating a dictionary for them as well.
As an example, If I get the first element in the list, List(i) in the first iteration is Messi, how do i use the value stored in it to create a dictionary with the name Messi. That is how do i get the line below.
Messi = [Iniesta: 4, Xavi: 5 , Neymar: 8]
It was suggested I try something like this
my_dynamic_vars = dict()
string = 'someString'
my_dynamic_vars.update({string: dict()})
Python and programming newbie here. Learning with experience as I go along. Thanks in advance for any help.
This is a fun question, and perhaps a good situation where something like a graph might be useful. You could implement a graph in python by simply using a dictionary whose keys are the names of the players and whose values are lists players that have been passed the ball.
passes = {
'Messi' : ['Iniesta', 'Xavi','Neymar', 'Xavi', 'Xavi'],
'Iniesta' : ['Messi','Xavi', 'Neymar','Messi', 'Xavi'],
'Xavi' : ['Messi','Neymar','Messi','Neymar'],
'Neymar' : ['Iniesta', 'Xavi','Iniesta', 'Xavi'],
}
To get the number of passes by any one player:
len(passes['Messi'])
To add a new pass to a particular player:
passes['Messi'].append('Xavi')
To count the number of times Messi passed to Xavi
passes['Messi'].count('Xavi')
To add a new player, just add him the first time he makes a pass
passes['Pele'] = ['Messi']
Now, he's also ready to have more passes 'appended' to him
passes['Pele'].append['Xavi']
What's great about this graph-like data structure is that not only do you have the number of passes preserved, but you also have information about each pass preserved (from Messi to Iniesta)
And here is a super bare-bones implementation of some functions which capture this behavior (I think a beginner should be able to grasp this stuff, let me know if anything below is a bit too confusing)
passes = {}
def new_pass(player1, player2):
# if p1 has no passes, create a new entry in the dict, else append to existing
if player1 not in passes:
passes[player1] = [player2]
else:
passes[player1].append(player2)
def total_passes(player1):
# if p1 has any passes, return the total number; otherewise return 0
total = len(passes[player1]) if player1 in passes else 0
return total
def total_passes_from_p1_to_p2(player1, player2):
# if p1 has any passes, count number of passes to player 2; otherwise return 0
total = passes[player1].count(player2) if player1 in passes else 0
return total
Ideally, you would be saving passes in some database that you could continuously update, but even without a database, you can add the following code and run it to get the idea:
# add some new passes!
new_pass('Messi', 'Xavi')
new_pass('Xavi', 'Iniesta')
new_pass('Iniesta', 'Messi')
new_pass('Messi', 'Iniesta')
new_pass('Iniesta', 'Messi')
# let's see where we currently stand
print total_passes('Messi')
print total_passes('Iniesta')
print total_passes_from_p1_to_p2('Messi', 'Xavi')
Hopefully you find this helpful; here's some more on python implementation of graphs from the python docs (this was a fun answer to write up, thanks!)
I suggest you construct a two dimensional square array. The array should have dimensions N x N. Each index represents a player. So the value at passes[i][j] is the number of times the player i passed to player j. The value passes[i][i] is always zero because a player can't pass to themselves
Here is an example.
players = ['Charles','Meow','Rebecca']
players = dict( zip(players,range(len(players)) ) )
rplayers = dict(zip(range(len(players)),players.keys()))
passes = []
for i in range(len(players)):
passes.append([ 0 for i in range(len(players))])
def pass_to(f,t):
passes[players[f]][players[t]] += 1
pass_to('Charles','Rebecca')
pass_to('Rebecca','Meow')
pass_to('Charles','Rebecca')
def showPasses():
for i in range(len(players)):
for j in range(len(players)):
print("%s passed to %s %d times" % ( rplayers[i],rplayers[j],passes[i][j],))
showPasses()

Categories

Resources