Refactor and optimize the decode_and_execute(instr) function of this python code.
Use these tips to optimize the function:
1-Combine the vx and vy variables into a single integer by converting the second and third characters of the instruction string to integer and shifting the value of vx by 4 bits. This avoids the need to manipulate string variables.
2-Replace the large if-else block with a dictionary mapping each instruction code to its corresponding function. This makes the code more concise and easier to read.
3-Use the int() function to convert the instruction string to an integer, rather than extracting the individual hexadecimal digits using string slicing and concatenation. This improves performance and simplifies the code.

Only write the refactored decode_and_execute function as the answer.

import random
import os
import keyboard
from timeit import default_timer as functiontimer

# 8 bit general purpose registers
registers = {f"V{i:x}": 0 for i in range(16)}

# 8 bit flag registers
registers["Vf"] = 0

# 16 bit register (only the lowest 12 bit usually used)
registers["I"] = 0

# Timer registers
timer_registers = {
    "DT": 0, # Delay Timer Register
    "ST": 0  # Sound Timer Register
}

# Keypad
keypad = {f"K{i:x}": 0 for i in range(16)}

# Memory from 0 (0x000) to 4095 (0xFFF)
memory = [0] * 4096

# 64-byte stack
stack = [0] * 64

# 8x64 monochrome framebuffer
framebuffer = [[0 for _ in range(64)] for _ in range(32)]

screen_needs_redraw = True
pc = 0x200 # 16 bit

# Keyboard and screen helpers
def update_keypad():
    global keypad

    def update_keypad_row(row, key_indexes):
        for i, key_index in enumerate(key_indexes):
            if keyboard.is_pressed(row[i]):
                keypad[f"K{key_index}"] = 1

    # First row
    update_keypad_row("1234", [1, 2, 3, 0xC])

    # Second row
    update_keypad_row("qwer", [4, 5, 6, 0xD])

    # Third row
    update_keypad_row("asdf", [7, 8, 9, 0xE])

    # Fourth row
    update_keypad_row("zxcv", [0xA, 0, 0xB, 0xF])

def wait_for_keypress():
    while True:
        keys = [
            ["1234", 0x1, 0x2, 0x3, 0xC],
            ["qwer", 0x4, 0x5, 0x6, 0xD],
            ["asdf", 0x7, 0x8, 0x9, 0xE],
            ["zxcv", 0xA, 0x0, 0xB, 0xF]
        ]

        for row in keys:
            if keyboard.is_pressed(row[0]):
                return row[1 + row.index(True)]

def reset_keypad():
    global keypad
    keypad = {f"K{i:x}": 0 for i in range(16)}

# Function to load the binary at 0+offset in memory
def load_binary(filename, offset):
    global memory

    counter = offset
    with open(filename, "rb") as f:
        while byte := f.read(1):
            memory[counter] = int.from_bytes(byte, byteorder="big")
            counter += 1


def decode_and_execute(instr):
	global memory, stack, pc, registers, timer_registers, framebuffer, keypad, screen_needs_redraw

	did_instruction_branch = False

	##USEFUL PARAMETERS
	nnn = int(instr[1] + instr[2] + instr[3],16)
	vx = 'V' + instr[1]
	vy = 'V' + instr[2]
	kk = int(instr[2] + instr[3],16)

	if instr[0] == '0':
		if instr[1] == '0' and instr[2] == 'e' and instr[3] == '0': #CLS
			framebuffer = [[0 for _ in range(64)] for _ in range(32)]
		elif instr[1] == '0' and instr[2] == 'e' and instr[3] == 'e': #RET
			pc = stack.pop() + 2
			did_instruction_branch = True
		else: #SYS nnn
			pass
	
	elif instr[0] == '1': #'JP 0x0' + nnn
		pc = nnn
		did_instruction_branch = True

	elif instr[0] == '2': # 'CALL 0x0' + nnn
		stack.append(pc)
		pc = nnn 
		did_instruction_branch = True

	#OK
	elif instr[0] == '3': #'SE' + vx_byte
		if registers[vx] == kk:
			pc += 2

	#OK
	elif instr[0] == '4': #'SNE' + vx_byte
		if registers[vx] != kk:
			pc += 2

	#OK
	elif instr[0] == '5': #'SE' + vx_vy
		if registers[vx] == registers[vy]:
			pc += 2

	#TO_TEST
	elif instr[0] == '6': #'LD' + vx_byte
		registers[vx] = kk 

	#OK
	elif instr[0] == '7': #'ADD' + vx_byte
		if registers[vx] + kk > 255:
			registers[vx] = registers[vx] + kk - 256
		else:
			registers[vx] = registers[vx] + kk

	#OK (all 8xxn)
	elif instr[0] == '8':
		if instr[3] == '0': #'LD' + vx_vy
			registers[vx] = registers[vy]
		elif instr[3] == '1': #'OR' + vx_vy
			registers[vx] = registers[vx] | registers[vy]
		elif instr[3] == '2': #'AND' + vx_vy
			registers[vx] = registers[vx] & registers[vy]
		elif instr[3] == '3': #'XOR' + vx_vy
			registers[vx] = registers[vx] ^ registers[vy]
		elif instr[3] == '4': #'ADD' + vx_vy
			if registers[vx] + registers[vy] > 255:
				registers[vx] = registers[vx] + registers[vy] - 256
				registers['Vf'] = 1
			else:
				registers[vx] = registers[vx] + registers[vy]
				registers['Vf'] = 0
		elif instr[3] == '5': #'SUB' + vx_vy
			if registers[vx] > registers[vy]:
				registers['Vf'] = 1
				registers[vx] = registers[vx] - registers[vy]
			else:
				registers['Vf'] = 0
				registers[vx] = 256 + registers[vx] - registers[vy]
		elif instr[3] == '6': #'SHR' + vx_vy_braces
			lsb = registers[vx] & 1
			registers['Vf'] = lsb
			registers[vx] = registers[vx] >> 1 ## we're dividing, no problem
		elif instr[3] == '7': #'SUBN' + vx_vy
			if registers[vy] > registers[vx]:
				registers[vx] = registers[vy] - registers[vx]
				registers['Vf'] = 1
			else:
				registers[vx] = 256 + registers[vy] - registers[vx]
				registers['Vf'] = 0
		elif instr[3] == 'e': #'SHL' + vx_vy_braces
			msb = int("{0:08b}".format(registers[vx])[:1])
			registers['Vf'] = msb
			if registers[vx] << 1 > 255:
				registers[vx] = (registers[vx] << 1) - 256
			else:
				registers[vx] = registers[vx] << 1

	#OK
	elif instr[0] == '9': #'SNE' + vx_vy
		if registers[vx] != registers[vy]:
			pc += 2

	#OK
	elif instr[0] == 'a': #'LD I 0x0' + nnn
		registers['I'] = nnn
 
	#OK
	elif instr[0] == 'b': #'JP V0' + nnn 
		pc = nnn + registers['V0']
		did_instruction_branch = True

	#OK
	elif instr[0] == 'c': #'RND' + vx_byte
		registers[vx] = kk & random.randint(0,255)

	#OK
	elif instr[0] == 'd': #'DRW' + vx_vy + ' 0x' + instr[3]
		screen_needs_redraw = True
		height = int('0x' + instr[3],16)
		base_ptr = registers['I']
		registers['Vf'] = 0
		
		for y_index in range(height):
			sprite_byte = format(int(str(hex(memory[base_ptr + y_index]))[2:].zfill(2),16), '08b')
			for x_index in range(8):
				color = int(sprite_byte[x_index])
				pos_vx = (registers[vx] + x_index) % 64
				pos_vy = ((registers[vy] + y_index) % 32)

				if framebuffer[pos_vy][pos_vx] == 1 and color == 1:
					registers['Vf'] = registers['Vf'] | 1
					color = 0
				elif framebuffer[pos_vy][pos_vx] == 1 and color == 0:
					color = 1

				framebuffer[pos_vy][pos_vx] = color

	elif instr[0] == 'e':
		if instr[2] == '9' and instr[3] == 'e': #return 'SKP' + vx
			updateKeypad()
			if keypad['K'+ str(hex(registers[vx])[-1])] == 1:
				pc += 2
			resetKeypad()
		elif instr[2] == 'a' and instr[3] == '1': #return 'SKNP' + vx
			updateKeypad()
			if keypad['K'+ str(hex(registers[vx])[-1])] == 0:
				pc += 2
			resetKeypad()

	elif instr[0] == 'f':
		#TO_TEST
		if instr[2] == '0' and instr[3] == '7': #'LD' + vx + ', DT'
			registers[vx] = timer_registers['DT']
		#OK
		elif instr[2] == '0' and instr[3] == 'a': #return 'LD' + vx + ', K' 
			registers[vx] = waitForKeypress()
		#OK
		elif instr[2] == '1' and instr[3] == '5': #'LD DT,' + vx
			timer_registers['DT'] = registers[vx]
		#OK
		elif instr[2] == '1' and instr[3] == '8': #'LD ST,' + vx
			 timer_registers['ST'] = registers[vx]
		#OK
		elif instr[2] == '1' and instr[3] == 'e': #'ADD I,' + vx
			registers['I'] = registers['I'] + registers[vx]
		#OK
		elif instr[2] == '2' and instr[3] == '9': #'LD F,' + vx
			registers['I'] = registers[vx] *5
		#OK
		elif instr[2] == '3' and instr[3] == '3': #'LD B,' + vx
			base_ptr = registers['I']
			memory[base_ptr] = registers[vx] // 10**2 % 10
			memory[base_ptr + 1] = registers[vx] // 10**1 % 10
			memory[base_ptr + 2] = registers[vx] // 10**0 % 10
		#OK	
		elif instr[2] == '5' and instr[3] == '5': #'LD [I],' + vx
			base_ptr = registers['I']
			for i in range(int(vx[1:]) +1):
				memory[base_ptr + i] = registers['V'+str(i)] 
		#OK
		elif instr[2] == '6' and instr[3] == '5': #'LD' + vx + ', [I]'
			base_ptr = registers['I']
			for i in range(int(vx[1:],16) +1):
				registers['V'+str(i)] = memory[base_ptr + i]

	#Now we check if we need to increment the program counter or if we are gonna branch
	if did_instruction_branch == False:
		pc += 2
	
def print_mem_as_hex(): #This is a DEBUG function
	global memory
	print('{}'.format('\n'.join('{:02x}'.format(x) for x in memory)))


def update_screen():
	global framebuffer

	output = ''
	os.system('cls')
	for column in framebuffer:
			output += ''.join((str(i) + ' ') for i in column).join(os.linesep)
	print(output.replace('0', ' ').replace('1', '#'))



def main():
	global memory
	global pc
	global screen_needs_redraw

	#loading the fonts in memory
	load_binary('chip8_fonts.bin', 0)

	#loading the rom in memory
	load_binary('maze.ch8', 512)
	#load_binary('bmp.ch8', 512)
	#load_binary('ibm.ch8', 512)
	#load_binary('chip8logo.ch8', 512)
	#load_binary('test_opcode.ch8', 512)
	#load_binary('c8_test.ch8', 512)
	#load_binary('trip.ch8', 512)
	#load_binary('rush_hour.ch8', 512)
	#load_binary('space_invaders.ch8', 512)
	#load_binary('chip8-test-rom.ch8', 512)
	#load_binary('keypad_test.ch8', 512)

	clockcycles = 0
	timer_period = 1.0 / 60 #1/calls per second
	timer_start_time = functiontimer()

	while True:

		if (functiontimer() - timer_start_time) > timer_period:
			if timer_registers['DT'] > 0: ##Decrement sound registers
				timer_registers['DT'] -= 1
			if timer_registers['ST'] > 0:
				timer_registers['ST'] -= 1
			timer_start_time = functiontimer()
		
		if screen_needs_redraw:
			update_screen()
			screen_needs_redraw = False	
		
		instruction = str(hex(memory[pc]))[2:].zfill(2) + str(hex(memory[pc+1]))[2:].zfill(2)

		decode_and_execute(instruction)


if __name__ == '__main__':
	main()