For encrypting strings this region of chars is given (in this order!):

* all letters (ascending, first all UpperCase, then all LowerCase)
* all digits (ascending)
* the following chars: `.,:;-?! '()$%&"` 

These are 77 chars! (This region is zero-based.)

Write two methods: 
```python
def encrypt(text)
def decrypt(encrypted_text)
```

Prechecks:
1. If the input-string has chars, that are not in the region, throw an Exception(C#, Python) or Error(JavaScript).
2. If the input-string is null or empty return exactly this value!

For building the encrypted string:
1. For every second char do a switch of the case.
2. For every char take the index from the region. Take the difference from the region-index of the char before (from the input text! Not from the fresh encrypted char before!). (Char2 = Char1-Char2)
Replace the original char by the char of the difference-value from the region. In this step the first letter of the text is unchanged.
3. Replace the first char by the mirror in the given region. (`'A' -> '"'`, `'B' -> '&'`, ...)

Simple example:

* Input:  `"Business"`
* Step 1: `"BUsInEsS"`
* Step 2: `"B61kujla"`
  * `B -> U`
    * `B (1) - U (20) = -19`
    * `-19 + 77 = 58`
    * `Region[58] = "6"`
  * `U -> s`
    * `U (20) - s (44) = -24`
    * `-24 + 77 = 53`
    * `Region[53] = "1"`
* Step 3: `"&61kujla"`

This kata is part of the Simple Encryption Series:
Simple Encryption #1 - Alternating Split
Simple Encryption #2 - Index-Difference
Simple Encryption #3 - Turn The Bits Around
Simple Encryption #4 - Qwerty

Have fun coding it and please don't forget to vote and rank this kata! :-)

Source: codewars | Difficulty: EASY
Tags: String algorithms

Fundamentals

Data structures

Starter code: def decrypt(encrypted_text):
	

Solutions:
region = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.,:;-?! '()$%&" + '"'

def decrypt(encrypted_text):
	if not encrypted_text:
		return encrypted_text
	letters = list(encrypted_text)
	letters[0] = region[-(region.index(letters[0]) + 1)]
	for i in range(1, len(letters)):
		letters[i] = region[region.index(letters[i - 1]) - region.index(letters[i])]
	for i in range(1, len(letters), 2):
		letters[i] = letters[i].swapcase()
	return ''.join(letters)

def encrypt(text):
	if not text:
		return text
	letters = list(text)
	for i in range(1, len(letters), 2):
		letters[i] = text[i].swapcase()
	swapped = letters[:]
	for i in range(1, len(letters)):
		letters[i] = region[region.index(swapped[i - 1]) - region.index(swapped[i])]
	letters[0] = region[-(region.index(swapped[0]) + 1)]
	return ''.join(letters)


def encrypt(text):
	import string
	char_filter = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.,:;-?! '()$%&" + '"'
	char_filter = [char for char in char_filter]
	if text == '' or text is None:
		return text
	else:
		for char in text:
			if char not in char_filter:
				raise Exception('text contains invalid character')
		txt_list = [char for char in text]
		for charIndex in range(1, len(txt_list), 2):
			if txt_list[charIndex] in string.ascii_uppercase:
				txt_list[charIndex] = txt_list[charIndex].lower()
			elif txt_list[charIndex] in string.ascii_lowercase:
				txt_list[charIndex] = txt_list[charIndex].upper()
		for i in range(len(txt_list) - 1):
			x = len(txt_list) - 1 - i
			char2 = char_filter.index(txt_list[x])
			char1 = char_filter.index(txt_list[x - 1])
			txt_list[x] = char_filter[char1 - char2]
		char = char_filter.index(txt_list[0])
		txt_list[0] = char_filter[char * -1 - 1]
		text = ''
		for char in txt_list:
			text += char
		return text

def decrypt(text):
	import string
	char_filter = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.,:;-?! '()$%&" + '"'
	char_filter = [char for char in char_filter]
	if text == '' or text is None:
		return text
	else:
		for char in text:
			if char not in char_filter:
				raise Exception('text contains invalid character')
		txt_list = [char for char in text]
		txt_list[0] = char_filter[77 - char_filter.index(txt_list[0]) - 1]
		for i in range(1, len(txt_list)):
			txt_list[i] = char_filter[char_filter.index(txt_list[i - 1]) - char_filter.index(txt_list[i])]
		for charIndex in range(1, len(txt_list), 2):
			if txt_list[charIndex] in string.ascii_uppercase:
				txt_list[charIndex] = txt_list[charIndex].lower()
			elif txt_list[charIndex] in string.ascii_lowercase:
				txt_list[charIndex] = txt_list[charIndex].upper()
		text = ''
		for char in txt_list:
			text += char
		return text


from string import ascii_uppercase, ascii_lowercase, digits
from itertools import chain
r = ascii_uppercase + ascii_lowercase + digits + '.,:;-?! \'()$%&"'
rr = {c: i for (i, c) in enumerate(r)}

def decrypt(t):
	if t is None:
		return None
	if t == '':
		return ''
	o = [r[76 - rr[t[0]]]]
	for c in t[1:]:
		o.append(r[(rr[o[-1]] - rr[c]) % 77])
	return ''.join((c.swapcase() if i % 2 else c for (i, c) in enumerate(o)))

def encrypt(t):
	if t is None:
		return None
	if t == '':
		return ''
	t = ''.join((c.swapcase() if i % 2 else c for (i, c) in enumerate(t)))
	return r[76 - rr[t[0]]] + ''.join((r[(rr[a] - rr[b]) % 77] for (a, b) in zip(t, t[1:])))


REGION = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.,:;-?! '()$%&" + '"'

def decrypt(encrypted_text):
	if not encrypted_text:
		return encrypted_text
	if any([x not in REGION for x in encrypted_text]):
		raise Exception
	three = REGION[::-1][REGION.index(encrypted_text[0])] + encrypted_text[1:]
	two = three[0]
	for i in range(1, len(three)):
		two += REGION[-REGION.index(three[i]) + REGION.index(two[i - 1])]
	return ''.join([x.swapcase() if i % 2 else x for (i, x) in enumerate(two)])

def encrypt(text):
	if not text:
		return text
	if any([x not in REGION for x in text]):
		raise Exception
	one = ''.join([x.swapcase() if i % 2 else x for (i, x) in enumerate(text)])
	two = ''.join([REGION[REGION.index(one[i - 1]) - REGION.index(x)] if i != 0 else x for (i, x) in enumerate(one)])
	return REGION[::-1][REGION.index(two[0])] + two[1:]


import string
reg = string.ascii_uppercase + string.ascii_lowercase + string.digits + '.,:;-?! \'()$%&"'

def decrypt(text):
	if not text:
		return text
	for i in text:
		if i not in reg:
			raise Exception
	dec = [reg[reg[::-1].index(text[0])]]
	for i in range(1, len(text)):
		tem = reg.index(text[i])
		ad = abs(tem - 77) + reg.index(dec[-1])
		if ad < 77:
			dec.append(reg[ad])
		else:
			dec.append(reg[ad - 77])
	for i in range(1, len(dec), 2):
		dec[i] = dec[i].swapcase()
	return ''.join(dec)

def encrypt(text):
	if not text:
		return text
	for i in text:
		if i not in reg:
			raise Exception
	(a, enc) = (list(text), [reg[-(reg.index(text[0]) + 1)]])
	for i in range(1, len(a), 2):
		a[i] = a[i].swapcase()
	for i in range(1, len(a)):
		ind = reg.index(a[i - 1]) - reg.index(a[i])
		enc.append(reg[ind])
	return ''.join(enc)


template_chars_region = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.,:;-?! '()$%&"
template_chars_region += '"'

def decrypt(encrypted_text):
	result = ''
	if not encrypted_text or 0 == len(encrypted_text):
		return encrypted_text
	template_length = len(template_chars_region)
	first_original_char = template_chars_region[template_length - template_chars_region.index(encrypted_text[0]) - 1]
	result = first_original_char
	for (index, single_char) in enumerate(''.join(encrypted_text[1:])):
		previous_template_index = template_chars_region.index(first_original_char)
		difference_template_index = template_chars_region.index(single_char)
		result += template_chars_region[(previous_template_index - difference_template_index + template_length) % template_length]
		first_original_char = result[-1]
	result_copy = ''
	for (index, single_char) in enumerate(result):
		if 0 != index % 2:
			if single_char.islower():
				single_char = single_char.upper()
			else:
				single_char = single_char.lower()
		result_copy += single_char
	result = result_copy
	return result

def encrypt(text):
	result = ''
	if not text or 0 == len(text):
		return text
	text_copy = ''
	template_length = len(template_chars_region)
	for (index, single_char) in enumerate(text):
		char_template_index = template_chars_region.index(single_char)
		if 0 != index % 2 and single_char.isalpha():
			if single_char.islower():
				single_char = single_char.upper()
			else:
				single_char = single_char.lower()
		text_copy += single_char
	for (index, char) in enumerate(text_copy):
		if 0 == index:
			continue
		previous_template_index = template_chars_region.index(text_copy[index - 1])
		current_template_index = template_chars_region.index(text_copy[index])
		result += template_chars_region[(previous_template_index - current_template_index + template_length) % template_length]
	result = template_chars_region[template_length - template_chars_region.index(text[0]) - 1] + result
	return result


from string import ascii_uppercase, ascii_lowercase, digits
s_Base = ascii_uppercase + ascii_lowercase + digits + '.,:;-?! \'()$%&"'

def decrypt(text):
	if not text:
		return text
	text = s_Base[-(s_Base.index(text[0]) + 1)] + text[1:]
	for (index, letter) in enumerate(text[1:]):
		x = s_Base.index(text[index])
		y = s_Base.index(letter)
		diff = x - y if x - y >= 0 else x - y + 77
		text = text[:index + 1] + s_Base[diff] + text[index + 2:]
	return ''.join((letter.swapcase() if index % 2 != 0 else letter for (index, letter) in enumerate(text)))

def encrypt(text):
	if not text:
		return text
	for letter in text:
		if letter not in s_Base:
			raise ValueError
	text = ''.join((letter.swapcase() if index % 2 != 0 else letter for (index, letter) in enumerate(text)))
	for (index, letter) in enumerate(text[1:]):
		if index == 0:
			x = s_Base.index(text[index])
		y = s_Base.index(letter)
		diff = x - y if x - y >= 0 else x - y + 77
		x = y
		text = text[:index + 1] + s_Base[diff] + text[index + 2:]
	text = s_Base[-(s_Base.index(text[0]) + 1)] + text[1:]
	return text


from string import ascii_lowercase as l, ascii_uppercase as u, digits as di
encrypt = lambda s: doall(s)
decrypt = lambda s: doall(s, True)

def doall(s, d=False):
	if not s:
		return s
	all_char = list(u + l + di + ".,:;-?! '()$%&" + '"')
	s = ''.join([j.swapcase() if i & 1 else j for (i, j) in enumerate(s)]) if not d else s
	(new, well) = ([], [all_char[-1 - all_char.index(s[0])]])
	for i in range(len(s) - 1):
		c_ = well[-1] if d else s[i]
		t_ = s[i + 1]
		diff = all_char.index(c_) - all_char.index(t_)
		well.append(all_char[diff])
		new.append(all_char[diff])
	encode = all_char[-1 - all_char.index(s[0])] + ''.join(new)
	decode = ''.join([j.swapcase() if i & 1 else j for (i, j) in enumerate(''.join(well))])
	return [encode, decode][d]


import string
region = string.ascii_uppercase + string.ascii_lowercase + ''.join((str(x) for x in range(10))) + '.,:;-?! \'()$%&"'

def switch_case(char):
	return char.lower() if char.isupper() else char.upper() if char.islower() else char

def decrypt(encrypted_text):
	if encrypted_text is None or encrypted_text == '':
		return encrypted_text
	current_char = region[-region.index(encrypted_text[0]) - 1]
	ret = current_char
	for char in list(encrypted_text)[1:]:
		ret += region[(region.index(ret[-1]) - region.index(char)) % 77]
	ret = ''.join([switch_case(x) if i % 2 != 0 else x for (i, x) in enumerate(ret)])
	return ret

def encrypt(text):
	if text is None or text == '':
		return text
	text = ''.join([switch_case(x) if i % 2 != 0 else x for (i, x) in enumerate(text)])
	(index_char, index_next) = ([region.index(char) for char in text], [region.index(char) for char in text[1:]])
	text = region[-region.index(text[0]) - 1] + ''.join([region[(c - n) % 77] for (c, n) in zip(index_char, index_next)])
	return text