I have an input file that's in the following format.
Fred,Karl,Technician,2010--Karl,Cathy,VP,2009--Cathy,NULL,CEO,2007--
--Vince,Cathy,Technician,2010
I need to parse this information to where it ends up looking something like this in an output file:
Cathy (CEO) 2007
-Karl (VP) 2009
--Fred (Technician) 2010
-Vince (Technician) 2010
With the CEO at the top, each subordinate should be under their superior. So whatever the second name is, that is the supervisor. The trick is that if an employee has 2 supervisors, they need to be indented twice "--" with their immediate supervisor above.
I've tried iterating through the list and parsing through the "--" and the commas but I'm struggling with the structure itself. This is what I have so far.
with open('org_chart_sample.in', 'r') as reader: # Open the input file
with open('output.out', 'w') as writer: # Make output file writable
reader.readline() # Ignore first line
lines = reader.readlines() # Read input lines
for line in lines: # Parse out input by the -- which separated attributes of people in the org
employees = line.split('--')
hierarchy = [] # Exterior list to aid in hierarchy
for employee in employees: # Logic that adds to the hierarchy list as algorithm runs
info = employee.split(',')
hierarchy.append(info)
I've been stuck on this problem for longer that I'd like to admit :(
Cool question, it was fun to work on. I tried to be thorough, and it ended up getting kind of long, I hope it's still readable.
Code:
##########################
#Input data cleaned a bit#
##########################
lines = ["Fred,Karl,Technician,2010",
"Karl,Cathy,VP,2009",
"Cathy,NULL,CEO,2007",
"Vince,Cathy,Technician,2010",
"Mary,NULL,CEO,2010",
"Steve,Mary,IT,2013"]
##################################
#Worker class to make things neat#
##################################
class Worker(object):
#Variables assigned to this worker
__slots__ = ["name","boss","job","year","employees","level"]
#Initialize this worker with a string in the form of:
#"name,boss,job,year"
def __init__(self,line):
self.name,self.boss,self.job,self.year = line.split(",")
self.level = 0 if self.boss == "NULL" else -1 #If boss is NULL, they are '0' level
self.employees = []
#A function to add another worker as this worker's employee
def employ(self,worker):
worker.level = self.level+1
self.employees.append(worker)
#This is a recursive function which returns a string of this worker
#and all of this workers employees (depth first)
def __repr__(self):
rep_str = ""
rep_str += "-"*self.level
rep_str += str(self.name)+" works for "+str(self.boss)
rep_str += " as a "+str(self.job)+" since "+str(self.year)+"\n"
for employee in self.employees:
rep_str += str(employee)
return rep_str
########################################
#Prepare to assign the bosses employees#
########################################
#1. Turn all of the lines into worker objects
workers = [Worker(line) for line in lines]
#2. Start from the top level bosses (the ones that had NULL as boss)
boss_level = 0
#3. Get a list of all the workers that have a boss_level of 0
bosses = [w for w in workers if w.level == boss_level]
#While there are still boses looking to employ then keep going
while len(bosses) > 0:
#For each boss look through all the workers and see if they work for this boss
#If they do, employ that worker to the boss
for boss in bosses:
for worker in workers:
if worker.level == -1 and boss.name == worker.boss:
boss.employ(worker)
#Move down a tier of management to sub-bosses
#If there are no sub-bosses at this level, then stop, otherwise while loop again
boss_level += 1
bosses = [w for w in workers if w.level == boss_level]
##########################
#Printing out the workers#
##########################
#1. Loop through the top bosses and
# print out them and all their workers
top_bosses = [w for w in workers if w.level == 0]
for top_boss in top_bosses:
print top_boss
Output:
Cathy works for NULL as a CEO since 2007
-Karl works for Cathy as a VP since 2009
--Fred works for Karl as a Technician since 2010
-Vince works for Cathy as a Technician since 2010
Mary works for NULL as a CEO since 2010
-Steve works for Mary as a IT since 2013
Related
I would like to keep track of the steps taken by the program in a text report file. Each step in the code returns a dataframe and there is a dependency between tasks (task n cannot be executed if task n-1 had found nothing).
My programme looks like this:
(kind of pseudo code)
import pandas as pd
step_1 = find_stuff()
if not step_1.empty:
step_2 = find_new_stuff()
if not step_2.empty:
step_3 = find_new_stuff_again()
if not step_3.empty:
report (step_1, step_2, step_3)
else:
report (step_1, step_2, step_3=pd.DataFrame())
else:
report (step_1, step_2=pd.DataFrame(), step_3=pd.DataFrame())
else:
report (step_1=pd.DataFrame(), step_2=pd.DataFrame(), step_3=pd.DataFrame())
def report (step_1, step_2, step_3) :
report_file = open("report.txt", "a")
if not step_1.empty:
report_file.write(f'Here what was found for step 1 \n : { step_1} \n')
if not step_2.empty:
report_file.write(f'Here what was found for step 2 \n : { step_2} \n')
if not step_3.empty:
report_file.write(f'Here what was found for step 3 \n : { step_3} \n')
else:
report_file.write('Nothing was found \n')
This way of doing things is very basic but does what I ask it to do. Though, I was wondering if there was a way to avoid/reduce all these "if" or an alternative way to generate this kind of report?
My answer is similar to #Nik's so that you need to iterate over some functions but I added it to a class so you can have the state together with the functions in the same scope. I also changed you file opening to use with as it is considered the safe way to handle files:
class StepFailedException(Exception): pass
class StuffFinder:
def __init__(self):
self.findings = []
def find_stuff_1(self):
stuff = None
# find stuff
if stuff.empty:
raise StepFailedException
self.findings.append(stuff)
def find_stuff_2(self):
stuff = None
# find stuff
if stuff.empty:
raise StepFailedException
self.findings.append(stuff)
def find_stuff_3(self):
stuff = None
# find stuff
if stuff.empty:
raise StepFailedException
self.findings.append(stuff)
def report(self):
if self.findings:
with open("report.txt", "a") as report_file:
for i,stuff in enumerate(self.findings):
report_file.write(f'Here what was found for step {i+1} \n : { stuff } \n')
def clear(self):
self.findings.clear()
def find_stuff(self):
self.clear()
try:
self.find_stuff_1()
self.find_stuff_2()
self.find_stuff_3()
except StepFailedException as e:
# handle exception if necessary
pass
self.report()
sf = StuffFinder() # you could add some initial values as argument to the constructor
sf.find_stuff()
# OR go step by step
sf.clear()
sf.find_stuff_1()
# sf.find_stuff_2() let's skip this one
sf.find_stuff_3()
sf.report()
In principle, I think your code is readable and gets the job done. However, if you have a lot of steps, you might want to iterate over them.
Here is an example:
import pandas as pd
def report(steps):
report_file = open("report.txt", "a")
for i, s in enumerate(steps):
report_file.write(f"Here what was found for step {i+1} \n : { s} \n")
steps = []
find_functions = [find_stuff, find_new_stuff, find_new_stuff_again]
for f in find_functions:
found_stuff = f()
if found_stuff.empty:
break
steps.append(found_stuff)
report(steps)
Also, mind that you are currently opening your report in "a" mode, so it will append results if you rerun the code.
I have a textfile for some carpool information, where the driver of the car is on the top, and passengers are listed under the driver and indented, I would like to have a system that reliably finds a passenger, then it will find who's the car driver. Here is an example of what it would look like.
Car: Steven
Jerry
Elaine
George
Car: Ross
Rachel
Joey
Car: Steve
Karl
Eric
Red
Ryan
I would like a function that takes a name like Eric and returns Steve, or takes Joey and returns Ross. The file will be much longer in actuality, but this is a snippet of what it looks like.
Assuming you can fit everything in memory and data looks something like
data = """Car: Steven
Jerry
Elaine
George
Car: Ross
Rachel
Joey
Car: Steve
Karl
Eric
Red
Ryan
"""
Then you can define a function by splitting at "Car:":
def find_driver(data, person):
for people in [car.split() for car in data.split("Car:")[1:]]:
if person in people:
return people[0]
return None
Then
find_driver(data, "Eric")
find_driver(data, "Joey")
returns the desired results.
Note splitting at "Car:" always gives a blank 0th element, so we slice past that. Then, we split the data normally (at newlines and spaces) using car.split(). Now we just search in order for what car a person is in.
The following function takes the text of the file as input and produces a dictionary that maps drivers to their passengers.
def parse_lines(text):
driver_to_passengers = dict()
car_prefix = "Car: "
current_driver = None
for line in text.split("\n"):
line = line.strip()
if line == "":
continue
if car_prefix in line:
driver_name = line.split(car_prefix)[-1]
driver_to_passengers[driver_name] = set()
current_driver = driver_name
else:
passenger_name = line
driver_to_passengers[current_driver].add(passenger_name)
return driver_to_passengers
To lookup a passenger, you can do the following:
driver_to_passenger = parse_lines(text)
passenger = "Joey" # Example passenger name
found = False
for driver in driver_to_passengers:
if passenger in driver_to_passengers[driver]:
found = True
break
This is a solution that compacts the space needed (the mapping is between driver and passenger) but increases the lookup time (since we are checking each driver's passengers). The lookup complexity for a single passenger is O(D) where D is the number of drivers (O(D) for iterating over the drivers, O(1) the lookup in the set).
A faster solution for your problem would be to reverse the mapping so that it now is passenger -> driver (but with this you will have some redundancy since a driver can have many passengers). The lookup complexity is reduced to O(1), and the two pieces of code above become:
def parse_lines(text):
passenger_to_driver = dict()
car_prefix = "Car: "
current_driver = None
for line in text.split("\n"):
line = line.strip()
if line == "":
continue
if car_prefix in line:
driver_name = line.split(car_prefix)[-1]
current_driver = driver_name
else:
passenger_name = line
passenger_to_driver[passenger_name] = current_driver
return passenger_to_driver
And the lookup:
passenger_to_driver = parse_lines(text)
passenger = "Joey" # Example passenger name
driver = passenger_to_driver.get(passenger, None) # driver will be None if the passenger does not exist
The issue I have is that I'm not able to get the correct part of my txt string. The txt string also has to be in the current order, I can't switch role and name.
txt = \
'''
Company name
leader: Claire
cashier: Ole
'''
def role(name):
start= txt.find(name)-len(name)
end= txt.find('\n', start)
return txt[start:end]
If I type role("Ole") I expect the output to be 'cashier'.
The output I am getting though is 'r: Ole'.
You can create a dictionary that associates the names to the correspondent roles, so that it becomes very easy to retrieve them even in a much longer list:
txt = \
'''
Company name
leader: Claire
cashier: Ole
'''
t = txt.split()
roles = {t[i] : t[i-1].strip(':') for i in range(3, len(t), 2)}
def role(name):
return roles[name]
Once set up, it's pretty intuitive.
stringy = "cashier: Ole"
stringy.partition(':')[0]
you can use .partition and bring only the first element
this will work but if there is anything diff about your string it may break down.
stringy = """ Company name
leader: Claire
cashier: Ole"""
def role(name):
slice = stringy.partition(name)[0]
role_str = slice.split(' ')[-2]
return role_str
I have the following function in python that takes input and parses it into a dictionary. I am trying to pass it the following input and for some reason on the lines artist=block[0] causes it to break because the list index is out of range and I am really confused why. It breaks after reading in the second Led Zeppelin. Any help with this issue would be greatly appreciated.
Input
Led Zeppelin
1969 II
-Whole Lotta Love
-What Is and What Should Never Be
-The Lemon Song
-Thank You
-Heartbreaker
-Living Loving Maid (She's Just a Woman)
-Ramble On
-Moby Dick
-Bring It on Home
Led Zeppelin
1979 In Through the Outdoor
-In the Evening
-South Bound Saurez
-Fool in the Rain
-Hot Dog
-Carouselambra
-All My Love
-I'm Gonna Crawl
Hello
Hello
Hello
Hello
Bob Dylan
1966 Blonde on Blonde
-Rainy Day Women #12 & 35
-Pledging My Time
-Visions of Johanna
-One of Us Must Know (Sooner or Later)
-I Want You
-Stuck Inside of Mobile with the Memphis Blues Again
-Leopard-Skin Pill-Box Hat
-Just Like a Woman
-Most Likely You Go Your Way (And I'll Go Mine)
-Temporary Like Achilles
-Absolutely Sweet Marie
-4th Time Around
-Obviously 5 Believers
-Sad Eyed Lady of the Lowlands
Function
def add(data, block):
artist = block[0]
album = block[1]
songs = block[2:]
if artist in data:
data[artist][album] = songs
else:
data[artist] = {album: songs}
return data
def parseData():
global data,file
file=os.getenv('CDDB')
data = {}
with open(file) as f:
block = []
for line in f:
line = line.strip()
if line == '':
data = add(data, block)
block = []
else:
block.append(line)
data = add(data, block)
f.close()
return data
Just add a sanity check to your add() function:
def add(data, block):
if not block:
return
Also, there is no good reason to use global variables. Here's an illustration:
def parseData(path):
data = {}
block = []
with open(path) as f:
for line in f:
line = line.strip()
if line == '':
add(data, block)
block = []
else:
block.append(line)
add(data, block)
return data
For a school project, I'm creating a game that has a score system, and I would like to create some sort of leaderboard. Once finished, the teachers will upload it to a shared server where other students can download a copy of the game, but unfortunately students can't save to that server; if we could, leaderboards would be a piece of cake. There would at most be a few hundred scores to keep track of, and all the computers have access to the internet.
I don't know much about servers or hosting, and I don't know java, html, or any other language commonly used in web development, so other related questions don't really help. My game prints the scoring information to a text file, and from there I don't know how to get it somewhere online that everyone can access.
Is there a way to accomplish such a task with just python?
Here I have the code for updating a leaderboard file (assuming it would just be a text file) once I have the scores. This would assume that I had a copy of the leaderboard and the score file in the same place.
This is the format of my mock-leaderboard (Leaderboards.txt):
Leaderboards
1) JOE 10001
2) ANA 10000
3) JAK 8400
4) AAA 4000
5) ABC 3999
This is what the log-file would print - the initials and score (log.txt):
ABC
3999
Code (works for both python 2.7 and 3.3):
def extract_log_info(log_file = "log.txt"):
with open(log_file, 'r') as log_info:
new_name, new_score = [i.strip('\n') for i in log_info.readlines()[:2]]
new_score = int(new_score)
return new_name, new_score
def update_leaderboards(new_name, new_score, lb_file = "Leaderboards.txt"):
cur_index = None
with open(lb_file, 'r') as lb_info:
lb_lines = lb_info.readlines()
lb_lines_cp = list(lb_lines) # Make a copy for iterating over
for line in lb_lines_cp:
if 'Leaderboards' in line or line == '\n':
continue
# Now we're at the numbers
position, name, score = [ i for i in line.split() ]
if new_score > int(score):
cur_index = lb_lines.index(line)
cur_place = int(position.strip(')'))
break
# If you have reached the bottom of the leaderboard, and there
# are no scores lower than yours
if cur_index is None:
# last_place essentially gets the number of entries thus far
last_place = int(lb_lines[-1].split()[0].strip(')'))
entry = "{}) {}\t{}\n".format((last_place+1), new_name, new_score)
lb_lines.append(entry)
else: # You've found a score you've beaten
entry = "{}) {}\t{}\n".format(cur_place, new_name, new_score)
lb_lines.insert(cur_index, entry)
lb_lines_cp = list(lb_lines) # Make a copy for iterating over
for line in lb_lines_cp[cur_index+1:]:
position, entry_info = line.split(')', 1)
new_entry_info = str(int(position)+1) + ')' + entry_info
lb_lines[lb_lines.index(line)] = new_entry_info
with open(lb_file, 'w') as lb_file_o:
lb_file_o.writelines(lb_lines)
if __name__ == '__main__':
name, score = extract_log_info()
update_leaderboards(name, score)
Some more info:
The score would be less than 1 000 000
Ideally, the solution would just be some code external to the game, so that I would just make an executable that the user would run after they've finished
I know it doesn't sound very secure - and it isn't - but that's ok, it's doesn't need to be hackproof
The easiest is probably to just use MongoDB or something (MongoDB is a NoSQL type database that allows you to save dictionary data easily...)
You can use the free account at https://mongolab.com (that should give you plenty of space).
You will need pymongo as well pip install pymongo.
Then you can simply save records there:
from pymongo import MongoClient, DESCENDING
uri = "mongodb://test1:test1#ds051990.mongolab.com:51990/joran1"
my_db_cli = MongoClient(uri)
db = my_db_cli.joran1 # select the database ...
my_scores = db.scores # this will be created if it doesn't exist!
# add a new score
my_scores.insert({"user_name": "Leeeeroy Jenkins", "score": 124, "time": "11/24/2014 13:43:22"})
my_scores.insert({"user_name": "bob smith", "score": 88, "time": "11/24/2014 13:43:22"})
# get a list of high scores (from best to worst)
print(list(my_scores.find().sort("score", DESCENDING)))
Those credentials will actually work if you want to test the system (keep in mind I added leeroy a few times).