Print an ordered cross table of a round robin tournament that looks like this:

```
 #  Player            1  2  3  4  5  6  7  8  9 10 11 12 13 14  Pts   SB
==========================================================================
 1  Nash King            1  0  =  1  0  =  1  1  0  1  1  1  0  8.0  52.25
 2  Karsyn Marks      0     1  1  =  1  =  =  1  0  0  1  1  0  7.5  49.75
 3  Yandel Briggs     1  0     =  0  =  1  1  1  =  0  1  0  1  7.5  47.25
    Luka Harrell      =  0  =     1  1  1  =  0  0  1  1  0  1  7.5  47.25
    Pierre Medina     0  =  1  0     =  1  =  1  1  =  0  1  =  7.5  47.25
 6  Carlos Koch       1  0  =  0  =     =  =  1  1  =  0  =  1  7.0  43.50
 7  Tristan Pitts     =  =  0  0  0  =     1  0  1  =  1  1  1  7.0  40.75
    Luke Schaefer     0  =  0  =  =  =  0     1  =  =  1  1  1  7.0  40.75
 9  Tanner Dunn       0  0  0  1  0  0  1  0     1  1  =  1  1  6.5  37.25
10  Haylee Bryan      1  1  =  1  0  0  0  =  0     1  0  0  1  6.0  39.25
11  Dylan Turner      0  1  1  0  =  =  =  =  0  0     1  =  =  6.0  38.75
12  Adyson Griffith   0  0  0  0  1  1  0  0  =  1  0     1  1  5.5  31.75
13  Dwayne Shaw       0  0  1  1  0  =  0  0  0  1  =  0     1  5.0  30.50
14  Kadin Rice        1  1  0  0  =  0  0  0  0  0  =  0  0     3.0  22.25
```

The `#` column contains the rank. Ranks may be tied.  
A colum with index numbers `i` contains the match against the player on line `i`. `1` / `=` / `0` means win / draw / loss.  
The `Pts` column contains the score, 1 for each win, 0.5 for each draw.  
The `SB` column contains the Sonneborn-Berger score (see below).  

The rank is determined by the score, the Sonneborn-Berger score is used to break ties.
Players with the same score and the same SB score share the same rank.
In the cross table those tied players are ordered by their surenames.

# Sonneborn-Berger score

The Sonneborn-Berger score (`SB`) (sometimes also called Neustadtl Sonneborn–Berger or Neustadtl score) is a system to break ties in tournaments.
This score is based on the observation that it is usually harder to score against higher ranked than against lower ranked opponents.
The score is the sum of the points of opponents this player has defeated plus the half the sum of the points of opponents this player has drawn against.
For examples, see the table above.

# Task

Write a function `crosstable(players, results)`.

Input:
* `players`: a list of names (`["Forename Surename", ...]`)
* `results`: a list of lists of results, where result may be `1`, `0.5`, `0` or `None`.
    
Output:
    
* The cross table as a string.  
  There is no whitespace at the end of the lines.  
  The columns are separated by two spaces.  
  The column headers for the rank (`#`) and for the rounds are right adjusted, `Players` is left-adjusted, `Pts` and `SB` are centered.  
  The separator line consisting of `=` is just as long as the longest line in the output.

# Examples

```python
d, _ = 0.5, None

crosstable([
    'Emmett Frost', 'Cruz Sullivan', 'Deandre Bullock', 'George Bautista', 'Norah Underwood', 'Renee Preston'], [
    [_, 1, 0, 0, d, 0],
    [0, _, d, 1, 0, 0],
    [1, d, _, d, d, d],
    [1, 0, d, _, d, d],
    [d, 1, d, d, _, d],
    [1, 1, d, d, d, _]])

# returns
'''\
#  Player           1 2 3 4 5 6  Pts   SB
==========================================
1  Renee Preston      = = = 1 1  3.5  7.25
2  Deandre Bullock  =   = = = 1  3.0  6.75
   Norah Underwood  = =   = 1 =  3.0  6.75
4  George Bautista  = = =   0 1  2.5  6.25
5  Cruz Sullivan    0 = 0 1   0  1.5  4.00
6  Emmett Frost     0 0 = 0 1    1.5  3.00'''

crosstable([
    'Luke Schaefer', 'Adyson Griffith', 'Dylan Turner', 'Carlos Koch', 'Luka Harrell', 'Karsyn Marks', 'Haylee Bryan',
    'Dwayne Shaw', 'Pierre Medina', 'Nash King', 'Kadin Rice', 'Tristan Pitts', 'Tanner Dunn', 'Yandel Briggs'], [
    [_, 1, d, d, d, d, d, 1, d, 0, 1, 0, 1, 0],
    [0, _, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, d, 0],
    [d, 1, _, d, 0, 1, 0, d, d, 0, d, d, 0, 1],
    [d, 0, d, _, 0, 0, 1, d, d, 1, 1, d, 1, d],
    [d, 1, 1, 1, _, 0, 0, 0, 1, d, 1, 1, 0, d],
    [d, 1, 0, 1, 1, _, 0, 1, d, 0, 0, d, 1, 1],
    [d, 0, 1, 0, 1, 1, _, 0, 0, 1, 1, 0, 0, d],
    [0, 0, d, d, 1, 0, 1, _, 0, 0, 1, 0, 0, 1],
    [d, 0, d, d, 0, d, 1, 1, _, 0, d, 1, 1, 1],
    [1, 1, 1, 0, d, 1, 0, 1, 1, _, 0, d, 1, 0],
    [0, 0, d, 0, 0, 1, 0, 0, d, 1, _, 0, 0, 0],
    [1, 1, d, d, 0, d, 1, 1, 0, d, 1, _, 0, 0],
    [0, d, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, _, 0],
    [1, 1, 0, d, d, 0, d, 0, 0, 1, 1, 1, 1, _]])

# returns
'''\
 #  Player            1  2  3  4  5  6  7  8  9 10 11 12 13 14  Pts   SB
==========================================================================
 1  Nash King            1  0  =  1  0  =  1  1  0  1  1  1  0  8.0  52.25
 2  Karsyn Marks      0     1  1  =  1  =  =  1  0  0  1  1  0  7.5  49.75
 3  Yandel Briggs     1  0     =  0  =  1  1  1  =  0  1  0  1  7.5  47.25
    Luka Harrell      =  0  =     1  1  1  =  0  0  1  1  0  1  7.5  47.25
    Pierre Medina     0  =  1  0     =  1  =  1  1  =  0  1  =  7.5  47.25
 6  Carlos Koch       1  0  =  0  =     =  =  1  1  =  0  =  1  7.0  43.50
 7  Tristan Pitts     =  =  0  0  0  =     1  0  1  =  1  1  1  7.0  40.75
    Luke Schaefer     0  =  0  =  =  =  0     1  =  =  1  1  1  7.0  40.75
 9  Tanner Dunn       0  0  0  1  0  0  1  0     1  1  =  1  1  6.5  37.25
10  Haylee Bryan      1  1  =  1  0  0  0  =  0     1  0  0  1  6.0  39.25
11  Dylan Turner      0  1  1  0  =  =  =  =  0  0     1  =  =  6.0  38.75
12  Adyson Griffith   0  0  0  0  1  1  0  0  =  1  0     1  1  5.5  31.75
13  Dwayne Shaw       0  0  1  1  0  =  0  0  0  1  =  0     1  5.0  30.50
14  Kadin Rice        1  1  0  0  =  0  0  0  0  0  =  0  0     3.0  22.25'''

crosstable([
    'Mikaela Orozco', 'Mekhi Mayer', 'Marcus Galvan', 'Leroy Wilkins', 'Gregory Bates', 'Jayda Lynn', 'Makena Galloway',
    'Adriel Brock', 'Morgan Gillespie', 'Darwin Mack', 'Clayton Terrell', 'Bo Schmidt', 'Xzavier Clements', 'Rex Cummings',
    'Aldo Jackson', 'Justus Sloan', 'Rudy Herrera', 'Leonard Ponce', 'Kaden Harding', 'Anastasia Dodson'], [
    [_, 1, 1, 0, 1, d, 1, 1, 0, 1, 0, d, 0, d, d, 0, d, 0, 0, d],
    [0, _, 0, d, d, 0, d, 1, d, d, 0, 1, 1, d, d, d, d, 1, d, 0],
    [0, 1, _, 0, 0, d, 1, d, 0, d, 0, d, 0, 1, 1, 0, 0, 0, 1, 1],
    [1, d, 1, _, d, 0, d, 0, d, 0, 1, d, 0, 0, 1, 0, d, 1, d, 1],
    [0, d, 1, d, _, 1, d, 1, 1, 1, d, 1, 1, d, 0, 1, 1, 1, d, d],
    [d, 1, d, 1, 0, _, 0, d, d, 1, d, 0, d, 0, 0, 0, 1, d, 1, 1],
    [0, d, 0, d, d, 1, _, 1, 1, 1, 1, 0, d, 0, 1, 1, d, 1, 1, 0],
    [0, 0, d, 1, 0, d, 0, _, 1, d, 0, d, 1, d, d, 1, d, 0, 0, 0],
    [1, d, 1, d, 0, d, 0, 0, _, 0, 1, 0, 0, 1, 1, d, d, 1, 0, 0],
    [0, d, d, 1, 0, 0, 0, d, 1, _, d, d, 1, 1, d, 0, 1, d, 1, d],
    [1, 1, 1, 0, d, d, 0, 1, 0, d, _, 1, d, 0, 0, d, 0, 0, d, d],
    [d, 0, d, d, 0, 1, 1, d, 1, d, 0, _, 1, d, d, 0, 1, 0, 0, 0],
    [1, 0, 1, 1, 0, d, d, 0, 1, 0, d, 0, _, 1, d, d, 0, 1, d, d],
    [d, d, 0, 1, d, 1, 1, d, 0, 0, 1, d, 0, _, d, 0, 0, 0, 1, 1],
    [d, d, 0, 0, 1, 1, 0, d, 0, d, 1, d, d, d, _, d, 1, 0, 1, 1],
    [1, d, 1, 1, 0, 1, 0, 0, d, 1, d, 1, d, 1, d, _, 1, 1, 0, d],
    [d, d, 1, d, 0, 0, d, d, d, 0, 1, 0, 1, 1, 0, 0, _, 0, 0, 1],
    [1, 0, 1, 0, 0, d, 0, 1, 0, d, 1, 1, 0, 1, 1, 0, 1, _, d, d],
    [1, d, 0, d, d, 0, 0, 1, 1, 0, d, 1, d, 0, 0, 1, 1, d, _, 0],
    [d, 1, 0, 0, d, 0, 1, 1, 1, d, d, 1, d, 0, 0, d, 0, d, 1, _]])

# returns
'''\
 #  Player             1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20  Pts     SB
===============================================================================================
 1  Gregory Bates         1  =  0  1  1  =  1  =  1  0  =  =  =  1  1  =  1  1  1  13.5  124.50
 2  Justus Sloan       0     0  =  1  1  =  =  1  1  1  1  =  0  =  1  =  1  1  0  12.0  109.00
 3  Makena Galloway    =  1     1  1  1  0  =  =  1  0  0  =  1  1  0  1  0  =  1  11.5  109.75
 4  Aldo Jackson       1  =  0     =  0  1  =  0  1  =  =  =  1  0  =  1  0  1  =  10.0   95.25
 5  Darwin Mack        0  0  0  =     =  =  1  1  0  0  1  =  1  1  =  =  =  1  =  10.0   89.00
 6  Leonard Ponce      0  0  0  1  =     =  0  0  =  1  1  0  =  0  1  1  1  1  1  10.0   87.50
 7  Anastasia Dodson   =  =  1  0  =  =     =  0  0  =  0  1  1  1  1  =  0  0  1   9.5   90.25
 8  Xzavier Clements   0  =  =  =  0  1  =     1  =  1  1  0  =  1  0  =  1  0  0   9.5   89.00
    Leroy Wilkins      =  0  =  1  0  1  1  0     0  1  0  =  =  =  =  1  1  =  0   9.5   89.00
10  Jayda Lynn         0  0  0  0  1  =  1  =  1     =  0  1  1  =  0  =  =  1  =   9.5   85.50
11  Mikaela Orozco     1  0  1  =  1  0  =  0  0  =     =  1  0  0  =  0  1  =  1   9.0   86.75
12  Rex Cummings       =  0  1  =  0  0  1  0  1  1  =     =  1  0  =  1  0  0  =   9.0   86.25
13  Mekhi Mayer        =  =  =  =  =  1  0  1  =  0  0  =     =  =  1  0  0  =  1   9.0   86.00
14  Kaden Harding      =  1  0  0  0  =  0  =  =  0  1  0  =     1  1  =  0  1  1   9.0   83.50
15  Morgan Gillespie   0  =  0  1  0  1  0  0  =  =  1  1  =  0     0  1  1  =  0   8.5   78.50
    Bo Schmidt         0  0  1  =  =  0  0  1  =  1  =  =  0  0  1     0  =  1  =   8.5   78.50
    Clayton Terrell    =  =  0  0  =  0  =  =  0  =  1  0  1  =  0  1     1  0  1   8.5   78.50
18  Marcus Galvan      0  0  1  1  =  0  1  0  0  =  0  1  1  1  0  =  0     0  =   8.0   75.75
19  Rudy Herrera       0  0  =  0  0  0  1  1  =  0  =  1  =  0  =  0  1  1     =   8.0   72.00
20  Adriel Brock       0  1  0  =  =  0  0  1  1  =  0  =  0  0  1  =  0  =  =      7.5   71.00'''
```

Source: codewars | Difficulty: MEDIUM
Tags: Sorting

Starter code: def crosstable(players, results):
	

Solutions:
def crosstable(players, scores):
	(points, le) = ({j: sum((k or 0 for k in scores[i])) for (i, j) in enumerate(players)}, len(players))
	SB = {j: sum((points[players[k]] / [1, 2][l == 0.5] for (k, l) in enumerate(scores[i]) if l)) for (i, j) in enumerate(players)}
	(SORTED, li) = ([[i, players.index(i)] for i in sorted(players, key=lambda x: (-points[x], -SB[x], x.split()[1]))], [])
	ps = [format(i, '.1f') for i in points.values()]
	Ss = [format(i, '.2f') for i in SB.values()]
	digit = len(str(le))
	name = len(max(players, key=len))
	pts = len(str(max(ps, key=lambda x: len(str(x)))))
	sb = len(str(max(Ss, key=lambda x: len(str(x)))))
	for (i, j) in enumerate(SORTED):
		ten_ = [' ', '  '][le >= 10]
		index = [str(i + 1), ' '][points[j[0]] == points[SORTED[i - 1][0]] and SB[j[0]] == SB[SORTED[i - 1][0]]].rjust(digit)
		name_ = j[0].ljust(name)
		team = ten_.join(['1=0 '[[1, 0.5, 0, None].index(scores[j[1]][l])] or '_' for (k, l) in SORTED])
		pt = str(format(points[j[0]], '.1f')).rjust(pts)
		Sb = str(format(SB[j[0]], '.2f')).rjust(sb)
		li.append(f"{index}  {name_}{['  ', '   '][le >= 10]}{team}  {pt}  {Sb}")
	fline = ' '.join(['#'.rjust(digit) + '  ' + 'Player'.ljust(name) + ['  ', '   '][len(players) >= 10] + ''.join([[' ', '  '][i < 10 and le >= 10] + str(i) for i in range(1, le + 1)]).strip() + '  ' + 'Pts'.center(pts) + '  ' + 'SB'.center(sb - [0, 2][sb & 1])]).rstrip()
	return '\n'.join([fline, '=' * len(max(li, key=len))] + li)


def transform(ch):
	s = ''
	for car in ch:
		car1 = car
		if 'A' <= car <= 'Z':
			car1 = chr(ord('Z') - ord(car) + ord('A'))
		if 'a' <= car <= 'z':
			car1 = chr(ord('z') - ord(car) + ord('a'))
		s += car1
	return s

def crosstable(players, results):
	scores = [sum([sc for sc in line if not sc is None]) for line in results]
	sb = [sum([scores[i] if results[j][i] == 1 else scores[i] / 2 if results[j][i] == 0.5 else 0 for i in range(len(results)) if not results[j][i] is None]) for j in range(len(results))]
	rank = [sum([1 if scores[j] > scores[i] or (scores[i] == scores[j] and sb[j] > sb[i]) else 0 for j in range(len(scores))]) + 1 for i in range(len(scores))]
	res = list(zip(scores, sb, [transform(player.split()[1]) for player in players], range(len(results))))
	res.sort(reverse=True)
	ranksize = max([len(str(r)) for r in rank])
	namesize = max([len(name) for name in players])
	ptssize = max([len('%0.1f' % (score,)) for score in scores])
	SBsize = max([len('%0.2f' % (sbs,)) for sbs in sb])
	scdetails = [' '.join([(' ' if results[pl[3]][pl1[3]] is None else '=' if results[pl[3]][pl1[3]] == 0.5 else '1' if results[pl[3]][pl1[3]] == 1 else '0').rjust(ranksize) for pl1 in res]) for pl in res]
	resultat = ['  '.join(['#'.rjust(ranksize), 'Player'.ljust(namesize), ' '.join([str(i).rjust(ranksize) for i in range(1, len(players) + 1)]), 'Pts'.ljust(ptssize), 'SB'.rjust(2 + (SBsize - 2) // 2)])]
	for i in range(len(res)):
		pl = res[i]
		ll = [(str(rank[pl[3]]) if i == 0 or rank[pl[3]] != rank[res[i - 1][3]] else ' ').rjust(ranksize), players[pl[3]].ljust(namesize), scdetails[i], ('%0.1f' % (pl[0],)).rjust(ptssize), ('%0.2f' % (pl[1],)).rjust(SBsize)]
		ll = '  '.join(ll)
		if i == 0:
			resultat.append('=' * len(ll))
		resultat.append(ll)
	return '\n'.join(resultat)


def crosstable(players, results):
	count = len(players)
	pts = [sum(res[:i] + res[i + 1:]) for (res, i) in zip(results, range(count))]
	sb = [sum([r * p for (r, p) in zip(res, pts) if r is not None]) for res in results]
	(l_rank, l_pl) = (len(str(count)), len(max(players, key=len)))
	l_one_score = len(str(count))
	l_score = (l_one_score + 1) * count - 1
	(template_pts, template_sb) = (f'{{:.1f}}', f'{{:.2f}}')
	(l_pts, l_sb) = (max([len(template_pts.format(num)) for num in pts]), max([len(template_sb.format(num)) for num in sb]))
	row_len = l_rank + l_pl + l_score + l_pts + l_sb + 8
	template_rank = f'{{: >{l_rank}}}  {{: <{l_pl}}}  {{: >{l_score}}}  '
	template_stat = f'{{:>{l_pts}.1f}}  {{:>{l_sb}.2f}}'
	template_stat_header = f'{{: ^{l_pts}}}  {{: ^{l_sb}}}'
	template_score = f'{{: >{l_one_score}}}'
	header = template_rank.format('#', 'Player', ' '.join([template_score.format(i) for i in range(1, count + 1)])) + template_stat_header.format('Pts', 'SB').rstrip() + '\n' + '=' * row_len + '\n'
	score = []
	for res in results:
		score.append([template_score.format(' ' if el is None else int(el) if el != 0.5 else '=') for el in res])
	sort_pts = list(sorted([dict(pts=pts[i], sb=sb[i], ind=i, name=players[i], res=score[i]) for i in range(count)], key=lambda el: el['pts'], reverse=True))
	duplicate: bool = False
	start = 0
	for i in range(0, count):
		if not duplicate and i != count - 1 and (sort_pts[i + 1]['pts'] == sort_pts[i]['pts']):
			(duplicate, start) = (True, i)
		elif duplicate and (i == count - 1 or sort_pts[i + 1]['pts'] != sort_pts[i]['pts']):
			sort_pts[start:i + 1] = sort_by_sb(list(sorted(sort_pts[start:i + 1], key=lambda el: el['sb'], reverse=True)), rank=start + 1)
			duplicate = False
		elif not duplicate:
			sort_pts[i]['rank'] = i + 1
	order = [rank['ind'] for rank in sort_pts]
	result = header
	for row in sort_pts:
		row['res'] = [row['res'][order[i]] for i in range(count)]
		result += template_rank.format(row['rank'], row['name'], ' '.join(row['res'])) + template_stat.format(row['pts'], row['sb']) + '\n'
	return result.rstrip()

def sort_by_sb(arr: list, rank: int):
	(duplicate, start, rank_duplicate) = (False, 0, rank)
	for i in range(len(arr)):
		if not duplicate and i != len(arr) - 1 and (arr[i]['sb'] == arr[i + 1]['sb']):
			(duplicate, start, rank_duplicate) = (True, i, rank)
		elif duplicate and (i == len(arr) - 1 or arr[i]['sb'] != arr[i + 1]['sb']):
			arr[start:i + 1] = list(sorted(arr[start:i + 1], key=lambda el: el['name'].split(' ')[1]))
			for el in arr[start:i + 1]:
				el['rank'] = ' '
			arr[start]['rank'] = rank_duplicate
			duplicate = False
		else:
			arr[i]['rank'] = rank
		rank += 1
	return arr


def score_string(array, col_width):
	scoreString = ''
	for elem in array:
		if elem is None:
			to_print = ''
		elif elem == 0.5:
			to_print = '='
		else:
			to_print = int(elem)
		scoreString += f"{to_print:{' '}{'>'}{col_width}}"
	return scoreString

def crosstable(players, results):
	tournament_results = {}
	(firstnames, lastnames) = zip(*[player.split(' ') for player in players])
	points_list = []
	for (player_idx, player) in enumerate(players):
		points = sum(filter(None, results[player_idx]))
		points_list.append(points)
		tournament_results[player] = {'Pts': points, 'Results': results[player_idx], 'Name': player}
	SB_list = []
	for (player_idx, player_result) in enumerate(results):
		player_SB = 0
		for (opponent_idx, score) in enumerate(player_result):
			if score is not None and score > 0:
				player_SB += tournament_results[players[opponent_idx]]['Pts'] * score
		SB_list.append(player_SB)
		tournament_results[players[player_idx]]['SB'] = player_SB
	sorted_points = sorted(range(len(points_list)), key=lambda k: (-points_list[k], -SB_list[k], lastnames[k][0]))
	for (player, data) in tournament_results.items():
		sorted_result = [data['Results'][idx] for idx in sorted_points]
		tournament_results[player]['Results'] = sorted_result
	player_ranks = ['1']
	for rank in range(1, len(players)):
		cur_player_idx = sorted_points[rank]
		one_pos_better_player_idx = sorted_points[rank - 1]
		if points_list[cur_player_idx] == points_list[one_pos_better_player_idx] and SB_list[cur_player_idx] == SB_list[one_pos_better_player_idx]:
			player_ranks.append('')
		else:
			player_ranks.append(str(rank + 1))
	col_spacer = 2
	delimitter = ' '
	col_delimitter = col_spacer * delimitter
	width_rank_col = len(str(len(players)))
	width_player_col = max([len('Player'), len(max(players, key=len))])
	width_score_col = width_rank_col + 1
	width_pts_col = len(str(int(points_list[sorted_points[0]]))) + 2
	width_SB_col = len(str(int(SB_list[sorted_points[0]]))) + 3
	score_line = score_string(range(1, len(players) + 1), width_score_col)
	col_width = [width_rank_col, col_spacer, width_player_col, 1, len(score_line), col_spacer, width_pts_col, col_spacer, width_SB_col]
	total_width = sum(col_width)
	SB_string = f"{'SB':{' '}{'^'}{width_SB_col}}".rstrip()
	header = f"{'#':{' '}{'>'}{width_rank_col}}" + col_delimitter + f"{'Player':{' '}{'<'}{width_player_col}}" + ' ' + score_line + col_delimitter + f"{'Pts':{' '}{'^'}{width_pts_col}}" + col_delimitter + SB_string + '\n'
	separator_line = total_width * '=' + '\n'
	totalString = header + separator_line
	for idx in range(len(players)):
		namePlayer = players[sorted_points[idx]]
		rankPlayer = player_ranks[idx]
		scorePlayer = tournament_results[namePlayer]['Results']
		ptsPlayer = f"{tournament_results[namePlayer]['Pts']:{' '}{'>'}{width_pts_col}.1f}"
		SBPlayer = f"{tournament_results[namePlayer]['SB']:{' '}{'>'}{width_SB_col}.2f}"
		playerString = f"{rankPlayer:{' '}{'>'}{width_rank_col}}" + col_delimitter + f"{namePlayer:{' '}{'<'}{width_player_col}}" + ' ' + score_string(scorePlayer, width_score_col) + col_delimitter + ptsPlayer + col_delimitter + SBPlayer
		if idx < len(players) - 1:
			playerString += '\n'
		totalString += playerString
	return totalString


def crosstable(players, results):
	scores = [sum((x for x in result if x)) for result in results]
	sb = []
	for (i, result) in enumerate(results):
		s = 0
		for (j, w) in enumerate(result):
			if w is None:
				continue
			s += w * scores[j]
		sb.append(s)
	rank = sorted(list(range(len(players))), key=lambda x: (-scores[x], -sb[x], players[x].split(' ')[1]))
	no_len = len(str(len(players)))
	name_len = max((len(p) for p in players))
	score_len = max((len('{:.1f}'.format(s)) for s in scores))
	sb_len = max((len('{:.2f}'.format(s)) for s in sb))
	r = []
	title = '#'.rjust(no_len) + '  ' + 'Player'.ljust(name_len) + '  '
	for i in range(1, len(players) + 1):
		title += str(i).rjust(no_len) + ' '
	bar = '=' * len(title)
	header_sb = 'SB'.center(sb_len)
	if sb_len % 2 == 1:
		header_sb = header_sb[1:] + ' '
	title += ' ' + 'Pts'.center(score_len) + '  ' + header_sb
	bar += '=' * (score_len + sb_len + 3)
	r.append(title.rstrip())
	r.append(bar)
	prev_sb = None
	for (i, j) in enumerate(rank):
		if prev_sb and prev_sb == (scores[j], sb[j]):
			no = ' '
		else:
			no = str(i + 1)
		line = no.rjust(no_len) + '  ' + players[j].ljust(name_len) + '  ' + ' ' * (no_len - 1)
		mat = []
		for k in rank:
			if j == k:
				mat.append(' ')
			else:
				mat.append({0: '0', 0.5: '=', 1: '1'}[results[j][k]])
		space = ' ' * no_len
		line += space.join(mat) + '  '
		score_j = '{:.1f}'.format(scores[j])
		sb_j = '{:.2f}'.format(sb[j])
		line += score_j.rjust(score_len) + '  ' + sb_j.rjust(sb_len)
		prev_sb = (scores[j], sb[j])
		r.append(line)
	return '\n'.join(r)


import math

def crosstable(players, results):
	scores = {}
	places = []
	for (s, p) in zip(results, players):
		scores[p] = [sum((y for y in s if y != None))]
	c = 0
	for (x, p) in zip(results, players):
		scores[p].append(0)
		for (y, p_2) in zip(x, players):
			if y != None:
				scores[p][1] += scores[p_2][0] * y
		scores[p].append(c)
		c += 1
	del c
	for (x, y) in zip(scores.keys(), scores.values()):
		for (c, z) in enumerate(places):
			if z[0]['score'][0] < y[0] or (z[0]['score'][0] == y[0] and z[0]['score'][1] < y[1]):
				places.insert(c, [{'Player': x, 'score': y}])
				break
			elif z[0]['score'][0] == y[0] and z[0]['score'][1] == y[1]:
				places[c].append({'Player': x, 'score': y})
				break
		else:
			places.append([{'Player': x, 'score': y}])
	for (c, x) in enumerate(places):
		if len(x) > 1:
			places[c] = sorted((y for y in x), key=lambda a: a['Player'].split(' ')[1])
	cur_line = 0
	display_place = True
	longest_name = max((len(x) for x in players)) + 2
	longest_score = max((len('{:0.1f}'.format(x[0])) for x in scores.values()))
	longest_SB = max((len('{:0.2f}'.format(x[1])) for x in scores.values()))
	longest_rank = len(str(len(players) - len(places[-1]) + 1))
	longest_round_number = len(str(len(players)))
	ans = ['', '']
	new_results = []
	ids = []
	for y in places:
		for a in y:
			ids.append(a['score'][2])
	results_in_order = [results[x] for x in ids]
	for x in results_in_order:
		new_results.append([])
		for y in ids:
			new_results[-1].append(x[y])
	for x in places:
		display_place = True
		for y in x:
			line = ''
			if display_place:
				line += ' ' * (longest_rank - len(str(cur_line + 1))) + str(cur_line + 1) + '  '
				display_place = False
			else:
				line += ' ' * longest_rank + '  '
			line += y['Player']
			spaces = longest_name - len(y['Player']) + (longest_round_number - 1)
			line += ' ' * spaces
			for x in new_results[cur_line]:
				if x == None:
					line += ' '
				elif x == 0.5:
					line += '='
				elif x == 1:
					line += '1'
				elif x == 0:
					line += '0'
				line += ' ' * longest_round_number
			line = line[:-longest_round_number]
			len_score = len('{:0.1f}'.format(y['score'][0]))
			len_SB = len('{:0.2f}'.format(y['score'][1]))
			line += ('  ' + ' ' * (longest_score - len_score) + '{:0.1f}' + ' ' * (longest_SB - len_SB + 2) + '{:0.2f}').format(y['score'][0], y['score'][1])
			cur_line += 1
			ans.append(line)
	ans[1] = '=' * len(ans[2])
	ans[0] = ' ' * (longest_rank - 1) + '#  Player' + ' ' * (longest_name - 6 + longest_round_number - 1)
	for x in range(1, len(players) + 1):
		ans[0] += str(x)
		ans[0] += ' ' * (longest_round_number - len(str(x + 1)) + 1)
	ans[0] = ans[0].rstrip()
	ans[0] += '  ' + ' ' * math.floor(longest_score / 3 - 1) + 'Pts' + ' ' * math.ceil(longest_score / 3 - 1)
	ans[0] += '  ' + ' ' * math.floor(longest_SB / 2 - 1) + 'SB'
	return '\n'.join(ans)


def crosstable(players, results):
	playersInputRow = {}
	playersScores = {}
	playersSB = {}
	playersSUM = {}
	longestName = float('-inf')
	for (number, player) in enumerate(players):
		playersInputRow[player] = number
		if len(player) > longestName:
			longestName = len(player)
		playersScores[player] = 0
		for score in results[number]:
			if score != None:
				playersScores[player] += score
	for (number, player) in enumerate(players):
		playersSB[player] = 0
		for (i, score) in enumerate(results[number]):
			if score == 0.5:
				playersSB[player] += 0.5 * playersScores[players[i]]
			if score == 1:
				playersSB[player] += playersScores[players[i]]
	for player in players:
		playersSUM[player] = playersScores[player] * 100 + playersSB[player]
	i = 1
	placments = {}
	while playersSUM:
		placments[i] = []
		maxValue = max(playersSUM.values())
		for player in playersSUM:
			if playersSUM[player] == maxValue:
				placments[i].append(player)
		for player in placments[i]:
			del playersSUM[player]
		i += 1
	placmentLength = len(str(len(placments)))
	playersLength = len(str(len(players)))
	ptsLength = len('{:.1f}'.format(max(playersScores.values())))
	sbLength = len('{:.2f}'.format(max(playersSB.values())))
	string = ''
	spaces = '  '
	i = 1
	for place in placments:
		first = True
		for player in sorted(sorted(placments[place]), key=lambda s: s.split()[1]):
			if first == True:
				string += str(i).rjust(placmentLength) + spaces
				first = False
			else:
				string += ''.rjust(placmentLength) + spaces
			string += player.ljust(longestName) + spaces
			for place2 in placments:
				for player2 in sorted(sorted(placments[place2]), key=lambda s: s.split()[1]):
					result = results[playersInputRow[player]][playersInputRow[player2]]
					if result == None:
						string += ' '.rjust(playersLength) + ' '
					elif result == 1:
						string += '1'.rjust(playersLength) + ' '
					elif result == 0.5:
						string += '='.rjust(playersLength) + ' '
					elif result == 0:
						string += '0'.rjust(playersLength) + ' '
			string += ' '
			string += str('{:.1f}'.format(playersScores[player])).rjust(ptsLength) + spaces
			string += str('{:.2f}'.format(playersSB[player])).rjust(sbLength)
			if place == 1:
				lineLength = len(string)
			if i != len(players):
				string += '\n'
			i += 1
	head = '#'.rjust(placmentLength) + spaces
	head += 'Player'.ljust(longestName) + spaces
	for i in range(1, len(players) + 1):
		head += str(i).rjust(playersLength) + ' '
	head += ' '
	head += 'Pts'.center(ptsLength) + spaces
	head += 'SB'.center(sbLength - 1)
	while head[-1] == ' ':
		head = head[:-1]
	head += '\n'
	head += ''.ljust(lineLength, '=') + '\n'
	head += string
	return head


import re

def crosstable(players, results):
	pts = [sum((r for r in result if r is not None)) for result in results]
	sb = [sum((r * pt for (r, pt) in zip(result, pts) if r is not None)) for result in results]
	order = sorted(sorted(range(len(players)), key=lambda player: players[player].split(' ')[-1]), key=lambda player: (pts[player], sb[player]), reverse=True)
	width_rank = len(str(next((rank + 1 for (rank, player) in enumerate(order) if pts[player] == pts[order[-1]] and sb[player] == sb[order[-1]]))))
	width_player = max((len(player) for player in players))
	width_result = len(str(len(players)))
	width_pts = len(str(f'{pts[order[0]]:0.1f}'))
	width_sb = len(str(f'{max(sb):0.2f}'))
	lines = []
	lines.append(f"{'#':>{width_rank}}  {'Player':<{width_player}}  {' '.join((f'{i + 1:>{width_result}}' for i in range(len(players))))}  {'Pts':^{width_pts}}  " + f"{'SB':^{width_sb}}".rstrip())
	lines.append('=' * (width_rank + width_player + (width_result + 1) * len(players) + width_pts + width_sb + 7))
	for (rank, player) in enumerate(order):
		lines.append(f"{(str(rank + 1) if rank == 0 or pts[order[rank - 1]] != pts[player] or sb[order[rank - 1]] != sb[player] else ''):>{width_rank}}  {players[player]:<{width_player}}  " + ' '.join((f"{ {None: '', 0: '0', 0.5: '=', 1: '1'}[results[player][opponent]]:>{width_result}}" for opponent in order)) + '  ' + f'{pts[player]:>{width_pts}.1f}  {sb[player]:>{width_sb}.2f}')
	return '\n'.join(lines)


def sortme_sensei(name, ls, ref, p):
	ref = [val[0] for val in ref]
	ls = list(zip(ls, p))
	ls.sort(key=lambda x: ref.index(x[1]))
	return [val[0] for val in ls]

def crosstable(players, results):
	(points, i) = ({v: [float(sum([val for val in x if val != None])), 0, x] for (v, x) in zip(players, results)}, 0)
	for n in players:
		indo = results[i]
		for (idx, num) in enumerate(indo):
			if num == 1:
				points[n][1] += points[players[idx]][0]
			elif num == 0.5:
				points[n][1] += points[players[idx]][0] / 2
		i += 1
	D = [(p, pts[0], f'{pts[1]:.2f}') for (p, pts) in list(points.items())]
	(sep_points, playa, ptsz, Sb) = (len(str(len(players))), len(max(players, key=len)) + 2, len(max([str(val[1]) for val in D], key=len)), len(max([str(val[2]) for val in D], key=len)))
	D.sort(key=lambda x: (-x[1], -float(x[2]), x[0].split()[1]))
	points = {k: [v[0], v[1], sortme_sensei(k, v[2], D, players)] for (k, v) in list(points.items())}
	string = '#'.rjust(sep_points) + '  ' + 'Player'.ljust(playa) + ''.join((str(i).rjust(sep_points + 1) if i - 1 else str(i).rjust(sep_points) for i in range(1, len(players) + 1))) + '  ' + 'Pts'.center(ptsz) + '  ' + ('SB'.center(Sb - 1)[:-1] if 'SB'.center(Sb - 1)[-1] == ' ' else 'SB'.center(Sb - 1)) + '\n'
	(tempo, C) = ('', 0)
	for i in range(len(D)):
		tempo += str(i + 1 if D[i - 1][2] != D[i][2] or D[i - 1][1] != D[i][1] else '').rjust(sep_points) + '  ' + D[i][0].ljust(playa)
		for (idx, res) in enumerate(points[D[i][0]][2]):
			if idx:
				tempo += res in [1, 0] and str(int(res)).rjust(sep_points + 1) or (res == 0.5 and '='.rjust(sep_points + 1)) or (sep_points + 1) * ' '
			else:
				tempo += res in [1, 0] and str(int(res)).rjust(sep_points) or (res == 0.5 and '='.rjust(sep_points)) or sep_points * ' '
		tempo += '  ' + str(D[i][1]).rjust(ptsz) + '  ' + str(D[i][2]).rjust(Sb) + ('\n' if i != len(D) - 1 else '')
		if not C:
			string += (len(tempo) - 1) * '=' + '\n'
			C = 1
	return string + tempo