Your task is to determine the top 3 place finishes in a pole vault competition involving several different competitors. This is isn't always so simple, and it is often a source of confusion for people who don't know the actual rules.

Here's what you need to know:

As input, you will receive an array of objects. Each object contains the respective competitor's name (as a string) and his/her results at the various bar heights (as an array of strings):

[{name: "Sergey", results: ["", "O", "XO", "O", "XXO", "XXX", "", ""]}{name: "Jan", results: ["", "", "", "O", "O", "XO", "XXO", "XXX"]}{name: "Bruce", results: ["", "XO", "XXO", "XXX", "", "", "", ""]}{name: "HerrWert", results: ["XXX", "", "", "", "", "", "", ""]}]

In the array of strings described above, each string represents the vaulter's performance at a given height. The possible values are based on commonly used written notations on a pole vault scoresheet:
An empty string indicates that the vaulter did not jump at this height for a variety of possible reasons ("passed" at this height, was temporarily called away to a different track and field event, was already eliminated from competition, or withdrew due to injury, for example).An upper-case X in the string represents an unsuccessful attempt at the height. (As you may know, the vaulter is eliminated from the competition after three consecutive failed attempts.)An upper-case O represents a successful attempt. If present at all, this will be the final character in the string, because it indicates that the vaulter has now successfully completed this height and is ready to move on.

All vaulters will have a result string (though possibly empty) for every height involved in the competition, making it possible to match up the results of different vaulters with less confusion.

Obviously, your first task is to determine who cleared the greatest height successfully. In other words, who has a "O" mark at a higher array element than any other competitor? You might want to work through the arrays from right to left to determine this. In the most straightforward case, you would first determine the winner, then second place, and finally third place by this simple logic.

But what if there's a tie for one of these finishing places? Proceed as follows (according to American high school rules, at least):
First trace backwards to find the greatest height that both vaulters cleared successfully. Then determine who had the fewest unsuccessful attempts at this height (i.e., the fewest X's in the string for this height). This person wins the tie-break.But what if they're still tied with one another?  Do NOT continue to trace backwards through the heights! Instead, compare their total numbers of unsuccessful attempts at all heights in the competition. The vaulter with the fewest total misses wins the tie-break.But what if they're still tied? It depends on the finishing place:If it's for second or third place, the tie stands (i.e., is not broken).But if it's for first place, there must be a jump-off (like overtime or penalty kicks in other sports) to break the tie and determine the winner. (This jump-off occurs - hypothetically - after your code runs and is thus not a part of this kata.)

Return a single object as your result. Each place-finish that is included in the results (including at least first place as property "1st" and possibly second and third places as properties "2nd" and "3rd") should have as its value the respective vaulter's name. In the event of a tie, the value of the property is the names of all tied vaulters, in alphabetical order, separated by commas, and followed by the notation "(jump-off)" if the tie is for first place or "(tie)" if it's for second or third place.

Here are some possible outcomes to show you what I mean:
{1st: "Jan", 2nd: "Sergey"; 3rd: "Bruce"} (These results correspond to the sample input data given above.){1st: "Julia", 2nd: "Madi, Emily (tie)}"{1st: "Caleb, Dustin (jump-off)", 3rd: "Sam"}{1st: "Meredith", 2nd: "Maddy", 3rd: "Cierra, Sara (tie)"}{1st: "Alex, Basti, Max (jump-off)"}

If you are familiar with the awarding of place finishes in sporting events or team standings in a league, then you know that there won't necessarily be a 2nd or 3rd place, because ties in higher places "bump"  all lower places downward accordingly.

One more thing: You really shouldn't change the array of objects that you receive as input. This represents the physical scoresheet. We need this "original document" to be intact, so that we can refer back to it to resolve a disputed result!

Have fun with this!

- - - - -

Notes for the Python version:

The rules for the Python version are the same as the original JavaScript version. 

The input and output will look the same as the JavaScript version. But, the JavaScript objects will be replaced by Python dictionaries. The JavaScript arrays will be replaced by Python lists. The Python function name was changed to include underscores as is customary with Python names. The example below should help clarify all of this.

The input for the Python version will be a list containing dictionaries with the competitors' names and results. The names in the dictionaries are strings. The results are lists with a list of strings. And example follows.

score_pole_vault([
        {"name": "Linda", "results": ["XXO", "O","XXO", "O"]},
        {"name": "Vickie", "results": ["O","X", "", ""]},
        {"name": "Debbie", "results": ["XXO", "O","XO", "XXX"]},
        {"name": "Michelle", "results": ["XO","XO","XXX",""]},
        {"name": "Carol", "results": ["XXX", "","",""]}
])


The returned results should be in a dictionary with one to three elements. 

Examples of possible returned results:

  {'1st': 'Linda', '2nd': 'Debbie', '3rd': 'Michelle'}

  {'1st': 'Green, Hayes (jump-off)', '3rd': 'Garcia'}           
  Note: Since first place was tied in this case, there is no 2nd place awarded.

  {'1st': 'Wilson', '2nd': 'Laurie', '3rd': 'Joyce, Moore (tie)'}
  
I have tried to create test cases that have every concievable tie situation.

Have fun with this version, as well!

Source: codewars | Difficulty: EASY
Tags: Fundamentals

Sorting

Data structures

Starter code: def score_pole_vault(vaulter_list):
	

Solutions:
def score_pole_vault(vaulter_list):
	popytki = len(vaulter_list[0]['results'])
	temp = {}
	res = {}
	for mas in vaulter_list:
		i = popytki - 1
		while i >= 0 and mas['results'][i].find('O') == -1:
			i -= 1
		if i < 0:
			n = 0
			m = ''.join(mas['results']).count('X')
		else:
			n = mas['results'][i].count('X')
			m = ''.join(mas['results'][:i]).count('X')
		new_key = (popytki - i, n, m)
		temp[new_key] = temp.get(new_key, []) + [mas['name']]
	k = iter(sorted(temp))
	i = 0
	while i < 3:
		key = next(k)
		if i == 0 and len(temp[key]) == 1:
			res['1st'] = temp[key][0]
			i += 1
		elif i == 0 and len(temp[key]) > 1:
			res['1st'] = ', '.join(sorted(temp[key])) + ' (jump-off)'
			i += len(temp[key])
		elif i == 1 and len(temp[key]) == 1:
			res['2nd'] = temp[key][0]
			i += 1
		elif i == 1 and len(temp[key]) > 1:
			res['2nd'] = ', '.join(sorted(temp[key])) + ' (tie)'
			i += len(temp[key])
		elif i == 2 and len(temp[key]) == 1:
			res['3rd'] = temp[key][0]
			i += 1
		elif i == 2 and len(temp[key]) > 1:
			res['3rd'] = ', '.join(sorted(temp[key])) + ' (tie)'
			i += len(temp[key])
	return res


def score_pole_vault(vaulter_list):
	i = j = 0
	retDict = {}
	vaulter = {}
	updatedVaulterList = []
	for vaulter_dict in vaulter_list:
		vaulter = {'name': vaulter_dict['name'], 'results': vaulter_dict['results']}
		highestVaulterJump = 0
		totalUnsuccessfulAttempts = 0
		unsuccessfulAttemptsAtHighestJump = 0
		j = 0
		for resultsList in vaulter_list[i]['results']:
			j -= 1
			if 'O' in resultsList:
				highestVaulterJump = j
				unsuccessfulAttemptsAtHighestJump = resultsList.count('X')
			totalUnsuccessfulAttempts += resultsList.count('X')
		vaulter['highestSuccessfulJump'] = highestVaulterJump
		vaulter['numOfUnsuccessfulAttemptsAtHighestSuccessfulJump'] = unsuccessfulAttemptsAtHighestJump
		vaulter['numOfTotalUnsuccessfulAttempts'] = totalUnsuccessfulAttempts
		updatedVaulterList.append(dict(vaulter))
		i += 1
	updatedVaulterList = sorted(updatedVaulterList, key=lambda i: (i['highestSuccessfulJump'], i['numOfUnsuccessfulAttemptsAtHighestSuccessfulJump'], i['numOfTotalUnsuccessfulAttempts'], i['name']))
	jumpOff = 0
	positionsAssigned = 0
	for i in range(len(updatedVaulterList)):
		if i == 0:
			retDict['1st'] = updatedVaulterList[i]['name']
			positionsAssigned += 1
		if i > 0:
			if updatedVaulterList[i]['highestSuccessfulJump'] == updatedVaulterList[0]['highestSuccessfulJump']:
				if updatedVaulterList[i]['numOfUnsuccessfulAttemptsAtHighestSuccessfulJump'] == updatedVaulterList[0]['numOfUnsuccessfulAttemptsAtHighestSuccessfulJump']:
					if updatedVaulterList[i]['numOfTotalUnsuccessfulAttempts'] == updatedVaulterList[0]['numOfTotalUnsuccessfulAttempts']:
						retDict['1st'] = retDict['1st'] + ', ' + updatedVaulterList[i]['name']
						jumpOff = 1
						positionsAssigned += 1
	if jumpOff:
		retDict['1st'] = retDict['1st'] + ' (jump-off)'
	tie = 0
	if positionsAssigned == 1:
		for i in range(1, len(updatedVaulterList)):
			if i == 1:
				retDict['2nd'] = updatedVaulterList[i]['name']
				positionsAssigned += 1
			if i > 1:
				if updatedVaulterList[i]['highestSuccessfulJump'] == updatedVaulterList[1]['highestSuccessfulJump']:
					if updatedVaulterList[i]['numOfUnsuccessfulAttemptsAtHighestSuccessfulJump'] == updatedVaulterList[1]['numOfUnsuccessfulAttemptsAtHighestSuccessfulJump']:
						if updatedVaulterList[i]['numOfTotalUnsuccessfulAttempts'] == updatedVaulterList[1]['numOfTotalUnsuccessfulAttempts']:
							retDict['2nd'] = retDict['2nd'] + ', ' + updatedVaulterList[i]['name']
							tie = 1
							positionsAssigned += 1
	if tie:
		retDict['2nd'] = retDict['2nd'] + ' (tie)'
	tie = 0
	if positionsAssigned == 2:
		for i in range(2, len(updatedVaulterList)):
			if i == 2:
				retDict['3rd'] = updatedVaulterList[i]['name']
				positionsAssigned += 1
			if i > 2:
				if updatedVaulterList[i]['highestSuccessfulJump'] == updatedVaulterList[2]['highestSuccessfulJump']:
					if updatedVaulterList[i]['numOfUnsuccessfulAttemptsAtHighestSuccessfulJump'] == updatedVaulterList[2]['numOfUnsuccessfulAttemptsAtHighestSuccessfulJump']:
						if updatedVaulterList[i]['numOfTotalUnsuccessfulAttempts'] == updatedVaulterList[2]['numOfTotalUnsuccessfulAttempts']:
							retDict['3rd'] = retDict['3rd'] + ', ' + updatedVaulterList[i]['name']
							tie = 1
							positionsAssigned += 1
	if tie:
		retDict['3rd'] = retDict['3rd'] + ' (tie)'
	return retDict


def score_pole_vault(vaulters):
	(results, k, R) = (sorted([(v['name'], score(v['results'])) for v in vaulters], key=lambda k: k[1]), 0, {})
	for (key, tie) in [('1st', ' (jump-off)'), ('2nd', ' (tie)'), ('3rd', ' (tie)')]:
		if key == '2nd' and k > 1:
			continue
		(mx, r) = (results[-1][1], [])
		while results and results[-1][1] == mx:
			r += [results.pop()[0]]
		k += len(r)
		R[key] = ', '.join(sorted(r)) + tie if len(r) > 1 else r.pop()
		if k >= 3:
			break
	return R

def score(hh):
	return ([(-1, 0)] + [(i, -len(h)) for (i, h) in [(i, h) for (i, h) in enumerate(hh)] if 'O' in h]).pop() + (-sum((j.count('X') for j in hh)),)


from collections import OrderedDict

def solve_tie_2(_vaulter_list, names, res, c):
	third_criterion = {}
	for player in _vaulter_list:
		if player['name'] in names:
			fails = ''.join(player['results']).count('X')
			if fails in third_criterion:
				third_criterion[fails].append(player['name'])
			else:
				third_criterion[fails] = [player['name']]
	third_criterion = OrderedDict(sorted(third_criterion.items()))
	for pair in third_criterion.items():
		if len(pair[1]) == 1:
			res.append(pair[1][0])
			remove_from_dict(_vaulter_list, res[-1])
			c += 1
			if len(res) >= 3 or c >= 3:
				return c
		else:
			res.append(', '.join(sorted(pair[1])))
			for name in pair[1]:
				remove_from_dict(_vaulter_list, name)
			c += len(pair[1])
			if len(res) >= 3 or c >= 3:
				return c
	return c

def solve_tie_1(_vaulter_list, names, index, res, c):
	second_criterion = {}
	for player in _vaulter_list:
		if player['name'] in names:
			fails = player['results'][index].count('X')
			if fails in second_criterion:
				second_criterion[fails].append(player['name'])
			else:
				second_criterion[fails] = [player['name']]
	second_criterion = OrderedDict(sorted(second_criterion.items()))
	for pair in second_criterion.items():
		if len(pair[1]) == 1:
			res.append(pair[1][0])
			remove_from_dict(_vaulter_list, res[-1])
			c += 1
			if len(res) >= 3 or c >= 3:
				return c
		else:
			c = solve_tie_2(_vaulter_list, pair[1], res, c)
			if len(res) >= 3:
				return c
	return c

def remove_from_dict(_vaulter_list, name):
	for i in range(len(_vaulter_list)):
		if _vaulter_list[i]['name'] == name:
			del _vaulter_list[i]
			break

def score_pole_vault(vaulter_list):
	if len(vaulter_list) < 1:
		return {}
	_vaulter_list = vaulter_list.copy()
	res = []
	c = 0
	l = len(_vaulter_list[0]['results'])
	best_height_index = l
	while len(res) < 3 and c < 3:
		for i in range(l - 1, -1, -1):
			for player in _vaulter_list:
				if 'O' in player['results'][i]:
					best_height_index = i
					break
			if best_height_index < l:
				break
		first_criterion_players = []
		for player in _vaulter_list:
			if 'O' in player['results'][best_height_index]:
				first_criterion_players.append(player['name'])
		if len(first_criterion_players) == 1:
			res.append(first_criterion_players[0])
			remove_from_dict(_vaulter_list, res[-1])
			c += 1
		else:
			c = solve_tie_1(_vaulter_list, first_criterion_players, best_height_index, res, c)
		l = best_height_index
	res_dict = {'1st': res[0] + (' (jump-off)' if ',' in res[0] else '')}
	n = len(res_dict['1st'].split(','))
	if n == 3:
		return res_dict
	if n == 2:
		res_dict['3rd'] = res[1] + (' (tie)' if ',' in res[1] else '')
		return res_dict
	else:
		res_dict['2nd'] = res[1] + (' (tie)' if ',' in res[1] else '')
		if ',' in res_dict['2nd']:
			return res_dict
		else:
			res_dict['3rd'] = res[2] + (' (tie)' if ',' in res[2] else '')
			return res_dict


def score_pole_vault(vaulter_list):
	r = {}
	for v in vaulter_list:
		x = [len(v['results']), 0, 0]
		f = 0
		for (i, t) in enumerate(v['results'][::-1]):
			if 'O' in t and x[0] == len(v['results']):
				x = [i, t.count('X'), 0]
			f += t.count('X')
		x[2] = f
		r[v['name']] = tuple(x)
	rank = sorted(list(r.keys()), key=lambda x: r[x])
	first = [rank[0]]
	second = []
	third = []
	for name in rank[1:]:
		if r[first[0]] == r[name]:
			first.append(name)
		elif not second or r[second[0]] == r[name]:
			second.append(name)
		elif not third or r[third[0]] == r[name]:
			third.append(name)
	if len(first) >= 2:
		d = {'1st': ', '.join(sorted(first)) + ' (jump-off)'}
		if len(first) == 2:
			if len(second) == 1:
				d['3rd'] = second[0]
			else:
				d['3rd'] = ', '.join(sorted(second)) + ' (tie)'
	else:
		d = {'1st': first[0]}
		if len(second) >= 2:
			d['2nd'] = ', '.join(sorted(second)) + ' (tie)'
		else:
			d['2nd'] = second[0]
			if len(third) == 1:
				d['3rd'] = third[0]
			else:
				d['3rd'] = ', '.join(sorted(third)) + ' (tie)'
	return d


def score_pole_vault(vaulter_list):
	summary = []
	for i in vaulter_list:
		total_X = 0
		highest_cleared = -1
		X_at_highest = 0
		for j in range(len(i['results'])):
			if 'O' in i['results'][j]:
				highest_cleared = j
				X_at_highest = i['results'][j].count('X')
			total_X += i['results'][j].count('X')
		summary.append((-highest_cleared, X_at_highest, total_X, i['name']))
	final_standing = sorted(summary)
	result = {}
	winners_stat = final_standing[0][:3]
	winners = []
	for i in final_standing:
		if i[:3] == winners_stat[:3]:
			winners.append(i[3])
	result['1st'] = ', '.join(winners) + ' (jump-off)' * (len(winners) > 1)
	if len(winners) > 2:
		return result
	if len(winners) == 1:
		second_stat = final_standing[1][:3]
		seconds = []
		for i in final_standing:
			if i[:3] == second_stat[:3]:
				seconds.append(i[3])
		result['2nd'] = ', '.join(seconds) + ' (tie)' * (len(seconds) > 1)
		if len(seconds) > 1:
			return result
	thirds = []
	third_stat = final_standing[2][:3]
	for i in final_standing:
		if i[:3] == third_stat[:3]:
			thirds.append(i[3])
	result['3rd'] = ', '.join(thirds) + ' (tie)' * (len(thirds) > 1)
	return result


from collections import defaultdict

def score_pole_vault(vaulter_list):
	scoreboard = defaultdict(list)
	for v in vaulter_list:
		score = 0
		fails = 0
		for (i, result) in enumerate(v['results'], 1):
			fails += result.count('X')
			if 'O' in result:
				score = i * 10 - result.count('X')
		scoreboard[-score, fails].append(v['name'])
	nWinners = 0
	places = ['3rd', '2nd', '1st']
	answer = {}
	for performance in sorted(scoreboard):
		place = places.pop()
		winnerList = sorted(scoreboard[performance])
		l = len(winnerList)
		winners = ', '.join(winnerList)
		if l > 1:
			winners += ' (jump-off)' if place == '1st' else ' (tie)'
		answer[place] = winners
		nWinners += l
		if nWinners >= 3:
			break
		if nWinners == len(places) == 2:
			places.pop()
	return answer