How to iterate over a hash of sets - ruby

I have a hash that has the name of the players of a game as the key, and a set object containing the names of other players each player defeated as the value. The sets are initialized empty, then two players are picked at random and fight each other, the winner gets the other player key added to its set, representing victory over them. Once the victory is established, players can't fight each other again. Finally, a player that defeated another should also have on his set the players defeated by the other, and the others defeated by them, and so on.
In a given game situation there are 5 players, Claudia, Rosa, Bob, Carlos and Tim. Let's suppose Rosa defeats Bob, and then Bob defeats Carlos and Claudia defeats Tim the given data at this point would look like this:
#match_data = {"claudia"=>#<Set: {"tim">,
"rosa"=>#<Set: {"bob", "carlos"}>,
"bob"=>#<Set: {"carlos"},
"carlos"=>#<Set: {},
"tim"=>#<Set: {}}
So at this point Claudia has a victory against Tim so they will not have another battle, Rosa has a victory against Bob and Carlos thus she will never need to battle against them again, and Bob has against Carlos and they won't battle either. Imagine that after this, Bob defeats Claudia, the desired data at this moment should be:
#match_data = {"claudia"=>#<Set: {"tim"}>,
"rosa"=>#<Set: {"bob", "carlos", "claudia", "tim"}>,
"bob"=>#<Set: {"carlos", "claudia", "tim"},
"carlos"=>#<Set: {},
"tim"=>#<Set: {}}
When Bob defeats Claudia, he acquire not only the victory against her, but also against Tim which has been previously defeated, and more importantly, Rosa acquire the victory against Claudia and Tim, because she already has the victory against Bob. So in this case Rosa wins the game, Bob is second place, and the others are still playing. This can get more complicated because the number of players is not limited.
The problem I am trying to overcome is to create an function that will update the state of the game. Every time a match is over, this code will look at the match_data and find which victories have been acquired by the outcome of the match. This code is one of my attempts:
def update_set(key)
store = Set.new
#match_data[key].each { |value| store.merge #match_data[value] }
if store.size > 0
store.each { |value| update_set(value) }
end
#match_data[key].merge store
end
#match_data.sort.map do |key, set|
update_set(key)
end
My other attempts get an error saying that I'm going too deep, or that I can't iterate a hash during a loop. Alternatively, I could try using another data structure, but I have no idea which one.
EDIT: I edited the original question just for clarity, as it was not clear what was my desired output when running my code. Nevertheless the answer given #ddubs works perfectly for my code.
Additionally, I would like to point a mistake at my given example, in my program the data could never really turn exactly as shown on the first code snippet, as the desired function would run always between each combat, refreshing the state of the dictionary then. I guess I did not want to write too much useless information and make my question more complex then it needed to be, but I ended up suppressing important information.
If needed I could post the whole program logic, but I don't think that this is necessary.
EDIT2: Added the whole game logic, as I found out it still wasn't clear enough, the solution given by #ddubs still works, but maybe someone can come with something better, now that all the information is given.

Set's seem to make this a lil odd, or maybe its just the layout of the #match_data object. Anyways, this function has to be applied against each player and their corresponding set. It will also require that you pass the match data to it as well:
def compile_sets(set, data, results = Set.new)
set.each do |s|
results << s
results << compile_sets(data[s], data, results)
end
results.flatten
end
Example Usage:
results = #match_data.map { |player,set| { player => compile_sets(set, #match_data) } }
p results
Returns:
[
{"carlos"=>#<Set: {}>},
{"bob"=>#<Set: {"carlos", "claudia"}>},
{"lisa"=>#<Set: {"bob", "carlos", "claudia"}>},
{"tim"=>#<Set: {"lisa", "bob", "carlos", "claudia"}>},
{"mary"=>#<Set: {"lisa", "bob", "carlos", "claudia"}>},
{"rosa"=>#<Set: {"tim", "lisa", "bob", "carlos", "claudia"}>},
{"claudia"=>#<Set: {}>}
]

Related

Python Birthday paradox math not working

it run corectly but it should have around 500 matches but it only has around 50 and I dont know why!
This is a probelm for my comsci class that I am having isues with
we had to make a function that checks a list for duplication I got that part but then we had to apply it to the birthday paradox( more info here http://en.wikipedia.org/wiki/Birthday_problem) thats where I am runing into problem because my teacher said that the total number of times should be around 500 or 50% but for me its only going around 50-70 times or 5%
duplicateNumber=0
import random
def has_duplicates(listToCheck):
for i in listToCheck:
x=listToCheck.index(i)
del listToCheck[x]
if i in listToCheck:
return True
else:
return False
listA=[1,2,3,4]
listB=[1,2,3,1]
#print has_duplicates(listA)
#print has_duplicates(listB)
for i in range(0,1000):
birthdayList=[]
for i in range(0,23):
birthday=random.randint(1,365)
birthdayList.append(birthday)
x= has_duplicates(birthdayList)
if x==True:
duplicateNumber+=1
else:
pass
print "after 1000 simulations with 23 students there were", duplicateNumber,"simulations with atleast one match. The approximate probibilatiy is", round(((duplicateNumber/1000)*100),3),"%"
This code gave me a result in line with what you were expecting:
import random
duplicateNumber=0
def has_duplicates(listToCheck):
number_set = set(listToCheck)
if len(number_set) is not len(listToCheck):
return True
else:
return False
for i in range(0,1000):
birthdayList=[]
for j in range(0,23):
birthday=random.randint(1,365)
birthdayList.append(birthday)
x = has_duplicates(birthdayList)
if x==True:
duplicateNumber+=1
print "after 1000 simulations with 23 students there were", duplicateNumber,"simulations with atleast one match. The approximate probibilatiy is", round(((duplicateNumber/1000.0)*100),3),"%"
The first change I made was tidying up the indices you were using in those nested for loops. You'll see I changed the second one to j, as they were previously bot i.
The big one, though, was to the has_duplicates function. The basic principle here is that creating a set out of the incoming list gets the unique values in the list. By comparing the number of items in the number_set to the number in listToCheck we can judge whether there are any duplicates or not.
Here is what you are looking for. As this is not standard practice (to just throw code at a new user), I apologize if this offends any other users. However, I believe showing the OP a correct way to write a program should be could all do us a favor if said user keeps the lack of documentation further on in his career.
Thus, please take a careful look at the code, and fill in the blanks. Look up the python doumentation (as dry as it is), and try to understand the things that you don't get right away. Even if you understand something just by the name, it would still be wise to see what is actually happening when some built-in method is being used.
Last, but not least, take a look at this code, and take a look at your code. Note the differences, and keep trying to write your code from scratch (without looking at mine), and if it messes up, see where you went wrong, and start over. This sort of practice is key if you wish to succeed later on in programming!
def same_birthdays():
import random
'''
This is a program that does ________. It is really important
that we tell readers of this code what it does, so that the
reader doesn't have to piece all of the puzzles together,
while the key is right there, in the mind of the programmer.
'''
count = 0
#Count is going to store the number of times that we have the same birthdays
timesToRun = 1000 #timesToRun should probably be in a parameter
#timesToRun is clearly defined in its name as well. Further elaboration
#on its purpose is not necessary.
for i in range(0,timesToRun):
birthdayList = []
for j in range(0,23):
random_birthday = random.randint(1,365)
birthdayList.append(random_birthday)
birthdayList = sorted(birthdayList) #sorting for easier matching
#If we really want to, we could provide a check in the above nester
#for loop to check right away if there is a duplicate.
#But again, we are here
for j in range(0, len(birthdayList)-1):
if (birthdayList[j] == birthdayList[j+1]):
count+=1
break #leaving this nested for-loop
return count
If you wish to find the percent, then get rid of the above return statement and add:
return (count/timesToRun)
Here's a solution that doesn't use set(). It also takes a different approach with the array so that each index represents a day of the year. I also removed the hasDuplicate() function.
import random
sim_total=0
birthdayList=[]
#initialize an array of 0's representing each calendar day
for i in range(365):
birthdayList.append(0)
for i in range(0,1000):
first_dup=True
for n in range(365):
birthdayList[n]=0
for b in range(0, 23):
r = random.randint(0,364)
birthdayList[r]+=1
if (birthdayList[r] > 1) and (first_dup==True):
sim_total+=1
first_dup=False
avg = float(sim_total) / 1000 * 100
print "after 1000 simulations with 23 students there were", sim_total,"simulations with atleast one duplicate. The approximate problibility is", round(avg,3),"%"

Best approach for deleting when using Array.combination()?

I want to compare every object in lectures with each other and if some_condition is true, the second object has to be deleted:
toDelete=[]
lectures.combination(2).each do |first, second|
if (some_condition)
toDelete << second
end
end
toDelete.uniq!
lectures=lectures-toDelete
I got some weird errors while trying to delete inside the .each loop, so I came up with this approach.
Is there a more efficient way to do this?
EDIT after first comments:
I wanted to keep the source code free of unnecessary things, but now that you ask:
The elements of the lectures array are hashes containing data of different university lectures, like the name, room,the calendar weeks in which they are taught and begin and end time.
I parse the timetables of all student groups to get this data, but because some lectures are held in more than one student group and these sometimes differ in the weeks they are taught, I compare them with each other. If the compared ones only differ in certain values, I add the values from the second object to the first object and delete the second object. That's why.
The errors when deleting while in .each-loop: When using the Rails Hash.diff method, I got something like "Cannot convert Symbol to Integer". Turns out there was suddenly an Integer value of 16 in the array, although I tested before the loop that there are only hashes in the array...
Debugging is really hard if you have 9000 hashes.
EDIT:
Sample Data:
lectures = [ {:day=>0, :weeks=>[11, 12, 13, 14], :begin=>"07:30", :end=>"09:30", :rooms=>["Li201", "G221"], :name=>"TestSubject1", :kind=>"Vw", :lecturers=>["WALDM"], :tut_groups=>["11INM"]},
{:day=>0, :weeks=>[11, 12, 13, 14], :begin=>"07:30", :end=>"09:30", :rooms=>["Li201", "G221"], :name=>"TestSubject1", :kind=>"Vw", :lecturers=>["WALDM"], :tut_groups=>["11INM"]} ]
You mean something like this?
cleaned_lectures = lectures.combination(2).reject{|first, second| some_condition}

How to recurse through arrays in Ruby

I'm trying to use the two following methods to recursively traverse arrays of arrays until the bottom and then come back up with the match results.
You know how in a tennis tournament they start with 32 matches and pair by pair the winner moves ahead, and at the end there's only one winner? That's what I want to replicate in Ruby.
I created a match_winner that always returns the first array for the sake of simplicity. Then, I send the whole tournament array into winner that calls itself recursively until it finds a simple array corresponding to a single match.
def match_winner(array_of_arrays)
return array_of_arrays[0]
end
def winner(tournament)
if tournament[0][0].is_a?(String)
return match_winner(tournament)
else
tournament.each{|e|
winner(e)
}
end
end
tournament = [
[["one", "two"],["three", "four"]],
[["five", "six"],["seven", "eight"]]
]
puts winner(tournament).inspect
Which outputs:
[[["one", "two"], ["three", "four"]], [["five", "six"], ["seven", "eight"]]]
I tried different permutations and variations on this algorithm but I couldn't make it work correctly and return only the final winner.
Does anyone see anything obviously wrong here?
Now I'm calling winner.
I know that the question looks like it's answered, but I just did the same problem and I have to say that simply changing each to map didn't work for me, because, as the code posted, the result is an array of the first-round winners. What worked for me is:
def winner(tournament)
if tournament[0][0].is_a?(String)
return match_winner(tournament)
else
tournament.map!{|e| #use map!, because we need to apply winner() to new values
e=winner(e) #assign new value somewhere, so recursion can climb back
}
end
end
Maybe more experienced developers can explain why that is. Without these two tips it won't work.
And yes, I know "bang" is a bad coding style, danger danger high voltage, but it's my second day with Ruby and I wanted to get this to work.
And, to understand recursion, you have to understand recursion.
Looks like you want map, not each, and, as a commenter above notes, you didn't call winner in the above code.
When you call:
tournament.each {...}
that method actually returns the tournament, which is thus what winner returns.
What you want is to replace it with
tournament.map {...}
which returns a new array consisting of calling "winner" on each element of tournament.
Assuming you have 2^n number of games always and match_winner works ok:
def winner(game)
if game[0][0][0] == game[0][0][0][0]
match_winner( [ game[0], game[1] ] )
else
match_winner( [winner(game[0]), winner(game[1])] )
end
end

What would you use for `n to n` relations in python?

after fiddling around with dictionaries, I came to the conclusion, that I would need a data structure that would allow me an n to n lookup. One example would be: A course can be visited by several students and each student can visit several courses.
What would be the most pythonic way to achieve this? It wont be more than 500 Students and 100 courses, to stay with the example. So I would like to avoid using a real database software.
Thanks!
Since your working set is small, I don't think it is a problem to just store the student IDs as lists in the Course class. Finding students in a class would be as simple as doing
course.studentIDs
To find courses a student is in, just iterate over the courses and find the ID:
studentIDToGet = "johnsmith001"
studentsCourses = list()
for course in courses:
if studentIDToGet in course.studentIDs:
studentsCourses.append(course.id)
There's other ways you could do it. You could have a dictionary of studentIDs mapped to courseIDs or two dictionaries that - one mapped studentIDs:courseIDs and another courseIDs:studentIDs - when updated, update each other.
The implementation I wrote out the code for would probably be the slowest, which is why I mentioned that your working set is small enough that it would not be a problem. The other implentations I mentioned but did not show the code for would require some more code to make them work that just aren't worth the effort.
It depends completely on what operations you want the structure to be able to carry out quickly.
If you want to be able to quickly look up properties related to both a course and a student, for example how many hours a student has spent on studies for a specific course, or what grade the student has in the course if he has finished it, and if he has finished it etc. a vector containing n*m elements is probably what you need, where n is the number of students and m is the number of courses.
If on the other hand the average number of courses a student has taken is much less than the total number of courses (which it probably is for a real case scenario), and you want to be able to quickly look up all the courses a student has taken, you probably want to use an array consisting of n lists, either linked lists, resizable vectors or similar – depending on if you want to be able to with the lists; maybe that is to quickly remove elements in the middle of the lists, or quickly access an element at a random location. If you both want to be able to quickly remove elements in the middle of the lists and have quick random access to list elements, then maybe some kind of tree structure would be the most suitable for you.
Most tree data structures carry out all basic operations in logarithmic time to the number of elements in the tree. Beware that some tree data structures have an amortized time on these operators that is linear to the number of elements in the tree, even though the average time for a randomly constructed tree would be logarithmic. A typical example of when this happens is if you use a binary search tree and build it up with increasingly large elements. Don't do that; scramble the elements before you use them to build up the tree in that case, or use a divide-and-conquer method and split the list in two parts and one pivot element and create the tree root with the pivot element, then recursively create trees from both the left part of the list and the right part of the list, these also using the divide-and-conquer method, and attach them to the root as the left child and the right child respectively.
I'm sorry, I don't know python so I don't know what data structures that are part of the language and which you have to create yourself.
I assume you want to index both the Students and Courses. Otherwise you can easily make a list of tuples to store all Student,Course combinations: [ (St1, Crs1), (St1, Crs2) .. (St2, Crs1) ... (Sti, Crsi) ... ] and then do a linear lookup everytime you need to. For upto 500 students this ain't bad either.
However if you'd like to have a quick lookup either way, there is no builtin data structure. You can simple use two dictionaries:
courses = { crs1: [ st1, st2, st3 ], crs2: [ st_i, st_j, st_k] ... }
students = { st1: [ crs1, crs2, crs3 ], st2: [ crs_i, crs_j, crs_k] ... }
For a given student s, looking up courses is now students[s]; and for a given course c, looking up students is courses[c].
For something simple like what you want to do, you could create a simple class with data members and methods to maintain them and keep them consistent with each other. For this problem two dictionaries would be needed. One keyed by student name (or id) that keeps track of the courses each is taking, and another that keeps track of which students are in each class.
defaultdicts from the 'collections' module could be used instead of plain dicts to make things more convenient. Here's what I mean:
from collections import defaultdict
class Enrollment(object):
def __init__(self):
self.students = defaultdict(set)
self.courses = defaultdict(set)
def clear(self):
self.students.clear()
self.courses.clear()
def enroll(self, student, course):
if student not in self.courses[course]:
self.students[student].add(course)
self.courses[course].add(student)
def drop(self, course, student):
if student in self.courses[course]:
self.students[student].remove(course)
self.courses[course].remove(student)
# remove student if they are not taking any other courses
if len(self.students[student]) == 0:
del self.students[student]
def display_course_enrollments(self):
print "Class Enrollments:"
for course in self.courses:
print ' course:', course,
print ' ', [student for student in self.courses[course]]
def display_student_enrollments(self):
print "Student Enrollments:"
for student in self.students:
print ' student', student,
print ' ', [course for course in self.students[student]]
if __name__=='__main__':
school = Enrollment()
school.enroll('john smith', 'biology 101')
school.enroll('mary brown', 'biology 101')
school.enroll('bob jones', 'calculus 202')
school.display_course_enrollments()
print
school.display_student_enrollments()
school.drop('biology 101', 'mary brown')
print
print 'After mary brown drops biology 101:'
print
school.display_course_enrollments()
print
school.display_student_enrollments()
Which when run produces the following output:
Class Enrollments:
course: calculus 202 ['bob jones']
course: biology 101 ['mary brown', 'john smith']
Student Enrollments:
student bob jones ['calculus 202']
student mary brown ['biology 101']
student john smith ['biology 101']
After mary brown drops biology 101:
Class Enrollments:
course: calculus 202 ['bob jones']
course: biology 101 ['john smith']
Student Enrollments:
student bob jones ['calculus 202']
student john smith ['biology 101']

How do I find a integer/max integer in an array for ruby and return the indexed position?

This is what I have so far
ages = [20,19,21,17,31,33,34]
names = [Bob, Bill, Jill, Aimee, Joe, Matt, Chris]
How do I take ages and apply a method to it to extract the largest integer out of it and learn its indexed position. The reason being I want to know which person in names is the oldest. Parallel assignment is blocking my ability to do a .sort on the array becasue it changes the position of element associted with names.
Please include code to mull over thanks,
ages.zip(names).max
names[ages.index(ages.max)]
What it does is find the maximum value in ages (ages.max), get the index of the first matching value in ages, and use it to get the corresponding person. Note that if two or more people have the same age which is the maximum, it'll only give you the name of the first one in the array.
Edit: To address your comment, I'm not sure why you need this parallel arrays structure (it'd be much easier if you just had a person object). Nevertheless, you can try something like:
indices = []
ages.each_with_index { |age, i| indices << i if age < 20 }
indices.each { |i| puts names[i] }

Resources