# Background

My TV remote control has arrow buttons and an `OK` button.

I can use these to move a "cursor" on a logical screen keyboard to type words...

# Keyboard

The screen "keyboard" layout looks like this


  #tvkb {
    width : 400px;
    border: 5px solid gray; border-collapse: collapse;
  }
  #tvkb td {
    color : orange;
    background-color : black;
    text-align : center;
    border: 3px solid gray; border-collapse: collapse;
  }


abcde123
fghij456
klmno789
pqrst.@0
uvwxyz_/
aASP



* `aA` is the SHIFT key. Pressing this key toggles alpha characters between UPPERCASE and lowercase
* `SP` is the space character
* The other blank keys in the bottom row have no function

# Kata task

How many button presses on my remote are required to type the given `words`?

## Notes

* The cursor always starts on the letter `a` (top left)
* The alpha characters are initially lowercase (as shown above)
* Remember to also press `OK` to "accept" each letter
* Take a direct route from one letter to the next
* The cursor does not wrap (e.g. you cannot leave one edge and reappear on the opposite edge)
* Although the blank keys have no function, you may navigate through them if you want to
* Spaces may occur anywhere in the `words` string.
* Do not press the SHIFT key until you need to. For example, with the word `e.Z`, the SHIFT change happens **after** the `.` is pressed (not before)
 
# Example

words = `Code Wars`

* C => `a`-`f`-`k`-`p`-`u`-`aA`-OK-`U`-`P`-`K`-`F`-`A`-`B`-`C`-OK = 14
* o => `C`-`H`-`M`-`R`-`W`-`V`-`U`-`aA`-OK-`SP`-`v`-`q`-`l`-`m`-`n`-`o`-OK = 16
* d => `o`-`j`-`e`-`d`-OK = 4
* e => `d`-`e`-OK = 2
* space => `e`-`d`-`c`-`b`-`g`-`l`-`q`-`v`-`SP`-OK = 9
* W => `SP`-`aA`-OK-`SP`-`V`-`W`-OK = 6
* a => `W`-`V`-`U`-`aA`-OK-`u`-`p`-`k`-`f`-`a`-OK = 10
* r => `a`-`f`-`k`-`p`-`q`-`r`-OK = 6
* s => `r`-`s`-OK = 2

Answer = 14 + 16 + 4 + 2 + 9 + 6 + 10 + 6 + 2 = 69



*Good Luck!
DM.*



Series
* TV Remote
* TV Remote (shift and space)
* TV Remote (wrap)
* TV Remote (symbols)

Difficulty: EASY
Starter code: def tv_remote(words):
	

Solutions:
import re
KEYBOARD = 'abcde123fghij456klmno789pqrst.@0uvwxyz_/* '
MAP = {c: (i // 8, i % 8) for (i, c) in enumerate(KEYBOARD)}

def manhattan(*pts):
	return 1 + sum((abs(z2 - z1) for (z1, z2) in zip(*pts)))

def toggle(m):
	(ups, end) = (m.group(1), m.group(2))
	off = '*' * bool(end)
	return f'*{ups.lower()}{off}{end}'

def tv_remote(words):
	reWords = re.sub('([A-Z][^a-z]*)([a-z]?)', toggle, words)
	return sum((manhattan(MAP[was], MAP[curr]) for (was, curr) in zip('a' + reWords, reWords)))


raw = 'abcde123fghij456klmno789pqrst.@0uvwxyz_/'
layout = {char: divmod(i, 8) for (i, char) in enumerate(raw)}
layout['SHIFT'] = (5, 0)
layout[' '] = (5, 1)

def distance(a, b):
	return abs(a[0] - b[0]) + abs(a[1] - b[1]) + 1

class Keyboard:

	def __init__(self):
		self.total = 0
		self.cap = False
		self.current = (0, 0)

	def hit_shift(self):
		self.total += distance(self.current, layout['SHIFT'])
		self.cap = not self.cap
		self.current = layout['SHIFT']

	def next_letter(self, letter):
		if letter.isupper() and (not self.cap):
			self.hit_shift()
		elif letter.islower() and self.cap:
			self.hit_shift()
		self.total += distance(self.current, layout[letter.lower()])
		self.current = layout[letter.lower()]

	def solve(self, word):
		for char in word:
			self.next_letter(char)

def tv_remote(words):
	k = Keyboard()
	k.solve(words)
	return k.total


import re

def tv_remote(words):
	letters = {c: (x, y) for (y, row) in enumerate(('abcde123', 'fghij456', 'klmno789', 'pqrst.@0', 'uvwxyz_/', '⇧ ')) for (x, c) in enumerate(row)}
	words = re.sub('((?:^|[a-z])[^A-Z]*)([A-Z])', '\\1⇧\\2', words)
	words = re.sub('([A-Z][^a-z]*)([a-z])', '\\1⇧\\2', words)
	words = words.lower()
	return sum((abs(letters[c1][0] - letters[c2][0]) + abs(letters[c1][1] - letters[c2][1]) + 1 for (c1, c2) in zip('a' + words, words)))


keyboard = ('abcde123', 'fghij456', 'klmno789', 'pqrst.@0', 'uvwxyz_/', '# ')
D = {c: (i, j) for (i, row) in enumerate(keyboard) for (j, c) in enumerate(row)}

def tv_remote(words):
	i = j = up = res = 0
	for c in words:
		if c.isalpha() and c.isupper() != up:
			(k, l) = D['#']
			res += abs(i - k) + abs(j - l) + 1
			(i, j, up) = (k, l, 1 - up)
		(k, l) = D[c.lower()]
		res += abs(i - k) + abs(j - l) + 1
		(i, j) = (k, l)
	return res


def tv_remote(words):
	pad = ['abcde123', 'fghij456', 'klmno789', 'pqrst.@0', 'uvwxyz_/', 'S ']
	index = {c: (x, y) for (x, lst) in enumerate(pad) for (y, c) in enumerate(lst)}
	res = 0
	pos = (0, 0)
	caps = False
	for char in words:
		if char.isalpha() and char.isupper() != caps:
			res += abs(pos[0] - index['S'][0]) + abs(pos[1] - index['S'][1]) + 1
			pos = index['S']
			caps = not caps
		char = char.lower()
		res += abs(pos[0] - index[char][0]) + abs(pos[1] - index[char][1]) + 1
		pos = index[char]
	return res


from collections import namedtuple

def tv_remote(word: str):
	remote = ('a', 'b', 'c', 'd', 'e', '1', '2', '3', 'f', 'g', 'h', 'i', 'j', '4', '5', '6', 'k', 'l', 'm', 'n', 'o', '7', '8', '9', 'p', 'q', 'r', 's', 't', '.', '@', '0', 'u', 'v', 'w', 'x', 'y', 'z', '_', '/', '', ' ')
	Position = namedtuple('Position', 'y x')
	(shift, shift_pressed) = (Position(5, 0), False)
	prev = Position(0, 0)
	button_presses = 0
	for letter in word:
		if letter.isalpha() and letter.isupper() != shift_pressed:
			button_presses += abs(prev.y - shift.y) + abs(prev.x - shift.x) + 1
			(prev, shift_pressed) = (shift, not shift_pressed)
		cur = Position(*divmod(remote.index(letter.lower()), 8))
		button_presses += abs(prev.y - cur.y) + abs(prev.x - cur.x) + 1
		prev = cur
	return button_presses


def tv_remote(word):
	(caps, cases, shift) = (coords['↑'], (str.isupper, str.islower), 0)
	(moves, current) = (0, (0, 0))
	for char in word:
		target = coords[char.lower()]
		if cases[shift](char):
			(moves, current) = (1 + moves + distance(current, caps), caps)
			shift = 1 - shift
		(moves, current) = (moves + distance(current, target), target)
	return moves + len(word)
keyboard = ('abcde123', 'fghij456', 'klmno789', 'pqrst.@0', 'uvwxyz_/', '↑ ')
coords = {char: (line.index(char), y) for (y, line) in enumerate(keyboard) for char in line}

def distance(pos1, pos2):
	return abs(pos2[0] - pos1[0]) + abs(pos2[1] - pos1[1])


def tv_remote(word):
	matrix = [['a', 'b', 'c', 'd', 'e', '1', '2', '3'], ['f', 'g', 'h', 'i', 'j', '4', '5', '6'], ['k', 'l', 'm', 'n', 'o', '7', '8', '9'], ['p', 'q', 'r', 's', 't', '.', '@', '0'], ['u', 'v', 'w', 'x', 'y', 'z', '_', '/'], ['aA', ' ']]
	actions = ([0, 0],)
	upper_mode = False
	press_ok = 0
	func = lambda x, y: abs(x[0] - y[0]) + abs(x[1] - y[1])
	for char in word:
		for i in range(6):
			if char.lower() in matrix[i]:
				if char.isupper() and (not upper_mode):
					actions += ([5, 0],)
					upper_mode = True
					press_ok += 1
					actions += ([i, matrix[i].index(char.lower())],)
					press_ok += 1
				elif char.isupper() and upper_mode:
					actions += ([i, matrix[i].index(char.lower())],)
					press_ok += 1
				elif char.islower() and upper_mode:
					actions += ([5, 0],)
					upper_mode = False
					press_ok += 1
					actions += ([i, matrix[i].index(char.lower())],)
					press_ok += 1
				elif char.islower() and (not upper_mode):
					actions += ([i, matrix[i].index(char.lower())],)
					press_ok += 1
				else:
					actions += ([i, matrix[i].index(char.lower())],)
					press_ok += 1
	return sum([func(i[0], i[1]) for i in list(zip(actions, actions[1:]))]) + press_ok


def tv_remote(s):
	d = {y: (i, x) for (i, j) in enumerate(['abcde123', 'fghij456', 'klmno789', 'pqrst.@0', 'uvwxyz_/', '# *!$%^&']) for (x, y) in enumerate(j)}
	does = lambda a, b: abs(d[a][0] - d[b][0]) + abs(d[a][1] - d[b][1]) + 1
	(status, c) = (False, 0)
	for (i, j) in zip(s, 'a' + s):
		(t, t1) = (i.lower(), j.lower())
		if i.isupper():
			if not status:
				(t1, status, c) = ('#', 1, c + does('#', t1))
		elif i.islower():
			if status:
				(t1, status, c) = ('#', 0, c + does('#', t1))
		c += does(t1, t)
	return c


D = {v: (r, c) for (r, row) in enumerate('abcde123 fghij456 klmno789 pqrst.@0 uvwxyz_/'.split() + ['  ']) for (c, v) in enumerate(row)}

def tv_remote(words):
	(t, lr, lc, lower) = (0, 0, 0, True)
	for e in words:
		if e.isalpha() and lower != e.islower():
			(t, lr, lc) = move(t, lr, lc, 5, 0)
			lower = not lower
		(t, lr, lc) = move(t, lr, lc, *D[e.lower()])
	return t

def move(t, lr, lc, r, c):
	return (t + abs(lr - r) + abs(lc - c) + 1, r, c)