# 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`?

## Hint

This Kata is an extension of the earlier ones in this series. You should complete those first.

## 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 the shortest route from one letter to the next
* The cursor wraps, so as it moves off one edge it will 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`-`aA`-OK-`A`-`B`-`C`-OK = 6
* o => `C`-`B`-`A`-`aA`-OK-`u`-`v`-`w`-`x`-`y`-`t`-`o`-OK = 12
* d => `o`-`j`-`e`-`d`-OK = 4
* e => `d`-`e`-OK = 2
* space => `e`-`d`-`c`-`b`-`SP`-OK = 5
* W => `SP`-`aA`-OK-`SP`-`V`-`W`-OK = 6
* a => `W`-`V`-`U`-`aA`-OK-`a`-OK = 6
* r => `a`-`f`-`k`-`p`-`q`-`r`-OK = 6
* s => `r`-`s`-OK = 2

Answer = 6 + 12 + 4 + 2 + 5 + 6 + 6 + 6 + 2 = 49



*Good Luck!
DM.*



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

Difficulty: introductory

Starter code:

def tv_remote(words):
	

Solutions:
import re

H, W     = 6, 8
KEYBOARD = "abcde123fghij456klmno789pqrst.@0uvwxyz_/* "
MAP      = {c: (i//W, i%W) for i,c in enumerate(KEYBOARD)}


def manhattan(*pts):
    dxy = [abs(z2-z1) for z1,z2 in zip(*pts)]
    return 1 + sum( min(dz, Z-dz) for dz,Z in zip(dxy, (H,W)) )

def toggle(m):
    ups, end = m.groups()
    return f'*{ups.lower()}*{end}'                    # Toggle Shift ON if uppercase presents, and then OFF if lowercase after (or end of the string)


def tv_remote(words):
    reWords = re.sub(r'([A-Z][^a-z]*)([a-z]?)', toggle, words).rstrip('*')                # Strip any useless toggle OFF at the end
    return sum( manhattan(MAP[was], MAP[curr]) for was,curr in zip('a'+reWords, reWords))


from collections import namedtuple

Position = namedtuple('Position', 'y x')


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', '_', '/',
        '', ' '
    )
    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 += calc_presses(prev, shift)
            prev, shift_pressed = shift, not shift_pressed

        cur = Position(*divmod(remote.index(letter.lower()), 8))
        button_presses += calc_presses(prev, cur)
        prev = cur

    return button_presses


def calc_presses(pos1: Position, pos2: Position):
    dif_y, dif_x = abs(pos1.y - pos2.y), abs(pos1.x - pos2.x)
    return min(dif_y, 6 - dif_y) + min(dif_x, 8 - dif_x) + 1

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, shift = moves + distance(current, caps), caps, 1 - shift
        moves, current = moves + distance(current, target), target
    return moves

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):
    d0, d1 = abs(pos2[0] - pos1[0]), abs(pos2[1] - pos1[1])
    return 1 + min(d0, 8 - d0) + min(d1, 6 - d1)

def navigate(s,e):
    a = 'abcde123fghij456klmno789pqrst.@0uvwxyz_/*&'
    d = {i: [a.index(i)//8, a.index(i)%8] for i in a}
    x=0
    y=0
    i=0
    j=0
    rowdis=0
    coldis=0
    for k,v in d.items():
        if k == s:
            i = v[0]
            j = v[1]
        if k == e:
            x = v[0]
            y = v[1]
        if abs(i-x) > 2.5:
            if i<x:
                rowdis = 6+i-x
            else : rowdis = 6-i+x
        else : rowdis = abs(i-x)
        if abs(j-y) > 3.5:
            if j<y:
                coldis = 8+j-y
            else : coldis = 8-j+y
        else : coldis = abs(j-y)
    return rowdis + coldis
def tv_remote(word):
    ans = 0
    cursor = 'a'
    shift = False
    for c in word:
        if c.isspace():        
            ans += navigate(cursor,'&') + 1
            cursor = '&'
            continue
        if c in 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ':
            if c.isupper():
                if not shift:
                    ans += navigate(cursor,'*') + 1
                    shift = True
                    cursor = '*'
            if c.islower():
                if shift:
                    ans += navigate(cursor,'*') + 1
                    shift = False
                    cursor = '*'
        ans += navigate(cursor,c.lower()) + 1
        cursor = c.lower()
    return ans

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', ' '],
             ]

    vertical_inversion = {0:0, 1:1, 2:2, 3:3, 4:2, 5:1}
    horizontal_inversion = {0:0, 1:1, 2:2, 3:3, 4:4, 5:3, 6:2, 7:1}
    actions = ([0, 0],)
    upper_mode = False
    press_ok = 0
    func = lambda x, y: (vertical_inversion[abs(x[0]-y[0])] + horizontal_inversion[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

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

mind = lambda d, mx: min(d, mx - d)
move = lambda t, lastr, lastc, r, c: (t + mind(abs(r - lastr), 6) + mind(abs(c - lastc), 8) + 1, r, c)

def tv_remote(words):

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

SHIFT = 'aA'
syms = {
    x: (i, j)
    for i, row in enumerate([
        'abcde123',
        'fghij456',
        'klmno789',
        'pqrst.@0',
        'uvwxyz_/',
    ])
    for j, x in enumerate(row)
}
syms[' '] = (5, 1)
syms[SHIFT] = (5, 0)


def tv_remote(words):
    r = c = 0
    n = 0
    lower = True
    def press(x):
        nonlocal n, r, c, lower
        r1, c1 = syms[x]
        n += min(abs(r1-r), 6 - abs(r1-r)) + min(abs(c1-c), 8 - abs(c1-c)) + 1
        r, c = r1, c1
        if x == SHIFT:
            lower = not lower
        
    for ch in words:
        if ch.isalpha() and ch.islower() != lower:
            press(SHIFT)
        press(ch.lower())
    return n

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(r'((?:^|[a-z])[^A-Z]*)([A-Z])', r'\1⇧\2', words)
    words = re.sub(r'([A-Z][^a-z]*)([a-z])', r'\1⇧\2', words)
    words = words.lower()
    return sum(
        min(abs(letters[c1][0] - letters[c2][0]),8-abs(letters[c1][0] - letters[c2][0])) +
        min(abs(letters[c1][1] - letters[c2][1]),6-abs(letters[c1][1] - letters[c2][1])) + 1
        for c1, c2 in zip("a" + words, words))

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

def manhattan_dist(*points): 
    print('points md: ', points)
    return  [abs(z2-z1) for z1,z2 in zip(*points)]

def wrap_manhattan_dist(*points):
    dist = manhattan_dist(*points)
    wdist = [min(dist[0],6-dist[0]), min(dist[1],8-dist[1])]
    return sum(wdist)

import re
def tv_remote(words):
    words = re.sub('([A-Z])',r'*\1*',words).lower()
    words = re.sub('\*([\d.@_/ ]*)(\*|$)',r'\1',words)
    words = re.sub('\*$','',words)
    words = re.sub('\*([0-9.@_/ ]+)([^\*])',r'\1*\2',words)
    words = 'a' + words
    moves = sum([wrap_manhattan_dist(D[words[i]], D[words[i+1]])+1 for i in range(len(words)-1)])
    return moves

def tv_remote(words:str):
    screen_keyboard_symbols = "abcde123fghij456klmno789pqrst.@0uvwxyz_/* "
    symbols_map = {c: (i // 8, i % 8) for i, c in enumerate(screen_keyboard_symbols)}
    kb_dims = (len(screen_keyboard_symbols) // 8 + 1, 8)

    def dist(pos1, pos2):
        return sum(min(abs(p1-p2), dim - abs(p1-p2)) for p1, p2, dim in zip(pos1, pos2, kb_dims))

    button_presses = 0
    cur_position, next_position = (0, 0), (0, 0)
    caps_on = False

    words_to_type = []
    for ch in words:
        if ch.islower() and caps_on or ch.isupper() and not caps_on:
            words_to_type.append("*" + ch.lower())
            caps_on = not caps_on
        else:
            words_to_type.append(ch.lower())
    words_to_type = "".join(words_to_type)

    for ch in words_to_type:
        if ch in screen_keyboard_symbols:
            next_position = symbols_map[ch]
            button_presses += 1 + dist(cur_position, next_position)
            cur_position = next_position
        else:
            return -1
    return button_presses