# Introduction 

The ragbaby cipher is a substitution cipher that encodes/decodes a text using a keyed alphabet and their position in the plaintext word they are a part of.

To encrypt the text `This is an example.` with the key `cipher`, first construct a keyed alphabet:
```
c       i       p       h       e       r       a       b       d       f       g       j       k       l       m       n       o       q       s       t       u       v       w       x       y       z
```

Then, number the letters in the text as follows:
```
T       h       i       s               i       s               a       n               e       x       a       m       p       l       e       .
1       2       3       4               1       2               1       2               1       2       3       4       5       6       7        
```

To obtain the encoded text, replace each character of the word with the letter in the keyed alphabet the corresponding number of places to the right of it (wrapping if necessary). 
Non-alphabetic characters are preserved to mark word boundaries.

Our ciphertext is then `Urew pu bq rzfsbtj.`

# Task

Wirate functions `encode` and `decode` which accept 2 parameters:
- `text` - string - a text to encode/decode
- `key` -  string - a key

# Notes

- handle lower and upper case in `text` string
- `key` consists of only lowercase characters

Difficulty: EASY
Tags: String algorithms

Fundamentals

Starter code:
def encode(text, key):
	

Solutions:
from string import ascii_lowercase as aLow
import re

def rotateWord(w, alpha, dct, d):
	lst = []
	for (i, c) in enumerate(w.lower(), 1):
		transChar = alpha[(dct[c] + i * d) % 26]
		if w[i - 1].isupper():
			transChar = transChar.upper()
		lst.append(transChar)
	return ''.join(lst)

def encode(text, key, d=1):
	(remains, alpha) = (set(aLow), [])
	for c in key + aLow:
		if c in remains:
			remains.remove(c)
			alpha.append(c)
	alpha = ''.join(alpha)
	dct = {c: i for (i, c) in enumerate(alpha)}
	return re.sub('[a-zA-Z]+', lambda m: rotateWord(m.group(), alpha, dct, d), text)

def decode(text, key):
	return encode(text, key, -1)


import re
from collections import OrderedDict
from string import ascii_lowercase

def encode(text, key, mul=1):
	i2c = ''.join(OrderedDict.fromkeys(key)) + ''.join((c for c in ascii_lowercase if c not in key))
	c2i = {c: i for (i, c) in enumerate(i2c)}
	return re.sub('[a-z]+', lambda m: ''.join(((str.upper if c.isupper() else str)(i2c[(c2i[c.lower()] + i * mul) % 26]) for (i, c) in enumerate(m.group(), 1))), text, flags=re.I)

def decode(text, key):
	return encode(text, key, -1)


import re
from string import ascii_lowercase as alphabet

def keyed_alphabet(key):
	(char2idx, idx2char) = ({}, {})
	for letter in key + alphabet:
		if letter not in char2idx:
			char2idx[letter] = len(char2idx)
			idx2char[len(idx2char)] = letter
	return (char2idx, idx2char)

def ragbaby(mode, text, key):
	(char2idx, idx2char) = keyed_alphabet(key)
	cipher_word = lambda match: ''.join(([str.upper, str.lower][letter.islower()](idx2char[(char2idx[letter.lower()] + idx * mode) % len(alphabet)]) for (idx, letter) in enumerate(match.group(), 1)))
	return re.sub('[{}]+'.format(alphabet), cipher_word, text, flags=re.I)
(encode, decode) = (lambda *args: ragbaby(1, *args), lambda *args: ragbaby(-1, *args))


from string import ascii_lowercase as aLow
import re

def rotateWord(w, alpha, dct, d):
	return ''.join(((str.lower if w[i - 1].islower() else str.upper)(alpha[(dct[c] + i * d) % 26]) for (i, c) in enumerate(w.lower(), 1)))

def encode(text, key, d=1):
	s = set(aLow)
	alpha = ''.join((s.remove(c) or c for c in key + aLow if c in s))
	dct = {c: i for (i, c) in enumerate(alpha)}
	return re.sub('[a-zA-Z]+', lambda m: rotateWord(m.group(), alpha, dct, d), text)

def decode(text, key):
	return encode(text, key, -1)


def encode(text, key):
	string = 'abcdefghijklmnopqrstuvwxyz'
	string_me = key + string
	periods = []
	spaces = []
	commas = []
	string_me = list(dict.fromkeys(string_me))
	for x in range(0, len(text)):
		if text[x] == '.':
			periods.append(x)
		elif text[x] == ',':
			commas.append(x)
		elif text[x] == ' ':
			spaces.append(x)
	text = text.replace('.', ' ').replace(',', ' ').split()
	word = 0
	new_word = []
	while word < len(text):
		for x in range(0, len(text[word])):
			if text[word][x].isupper():
				if string_me.index(text[word][x].lower()) + x + 1 >= 26:
					number = string_me.index(text[word][x].lower()) + x + 1
					while number >= 26:
						number -= 26
					new_word.append(string_me[number].upper())
				else:
					new_word.append(string_me[string_me.index(text[word][x].lower()) + x + 1].upper())
			elif string_me.index(text[word][x]) + x + 1 >= 26:
				number = string_me.index(text[word][x].lower()) + x + 1
				while number >= 26:
					number -= 26
				new_word.append(string_me[number])
			else:
				new_word.append(string_me[string_me.index(text[word][x].lower()) + x + 1])
		text[word] = ''.join(new_word)
		new_word = []
		word += 1
	text = list(''.join(text))
	newwww = periods + spaces + commas
	newwww.sort()
	if newwww:
		for x in newwww:
			text.insert(x, ' ')
	for x in periods:
		text[x] = '.'
	for x in commas:
		text[x] = ','
	return ''.join(text)

def decode(text, key):
	string = 'abcdefghijklmnopqrstuvwxyz'
	string_me = key + string
	periods = []
	spaces = []
	commas = []
	string_me = list(dict.fromkeys(string_me))
	for x in range(0, len(text)):
		if text[x] == '.':
			periods.append(x)
		elif text[x] == ',':
			commas.append(x)
		elif text[x] == ' ':
			spaces.append(x)
	text = text.replace('.', ' ').replace(',', ' ').split()
	word = 0
	new_word = []
	while word < len(text):
		for x in range(0, len(text[word])):
			if text[word][x].isupper():
				if string_me.index(text[word][x].lower()) - x - 1 >= 26:
					number = string_me.index(text[word][x].lower()) - x - 1
					while number >= 26:
						number -= 26
					new_word.append(string_me[number].upper())
				elif string_me.index(text[word][x].lower()) - x - 1 < 0:
					number = string_me.index(text[word][x].lower()) - x - 1
					while number < 0:
						number += 26
					new_word.append(string_me[number].upper())
				else:
					new_word.append(string_me[string_me.index(text[word][x].lower()) - x - 1].upper())
			elif string_me.index(text[word][x]) - x - 1 >= 26:
				number = string_me.index(text[word][x].lower()) - x - 1
				while number >= 26:
					number -= 26
				new_word.append(string_me[number])
			elif string_me.index(text[word][x].lower()) - x - 1 < 0:
				number = string_me.index(text[word][x].lower()) - x - 1
				while number < 0:
					number += 26
				new_word.append(string_me[number])
			else:
				new_word.append(string_me[string_me.index(text[word][x].lower()) - x - 1])
		text[word] = ''.join(new_word)
		new_word = []
		word += 1
	text = list(''.join(text))
	newwww = periods + spaces + commas
	newwww.sort()
	if newwww:
		for x in newwww:
			text.insert(x, ' ')
	for x in periods:
		text[x] = '.'
	for x in commas:
		text[x] = ','
	return ''.join(text)


from string import ascii_lowercase as AL
import re

def f(op):

	def g(s, key):
		a = ''.join(dict.fromkeys(key + AL))
		d = {x: i for (i, x) in enumerate(a)}

		def f(s):
			(s, r) = (s[0], [])
			for (i, x) in enumerate(s, 1):
				y = a[op(d[x.lower()], i) % len(a)]
				if x.isupper():
					y = y.upper()
				r.append(y)
			return ''.join(r)
		return re.sub('\\w+', f, s)
	return g
encode = f(int.__add__)
decode = f(int.__sub__)


def encode(text, key):
	k = ''
	for c in key + 'abcdefghijklmnopqrstuvwxyz':
		if c not in k:
			k += c
	r = ''
	i = 1
	for c in text:
		if c.isalpha():
			j = (k.index(c.lower()) + i) % 26
			if c.isupper():
				r += k[j].upper()
			else:
				r += k[j]
			i += 1
		else:
			r += c
			i = 1
	return r

def decode(text, key):
	k = ''
	for c in key + 'abcdefghijklmnopqrstuvwxyz':
		if c not in k:
			k += c
		r = ''
	i = 1
	for c in text:
		if c.isalpha():
			j = (k.index(c.lower()) - i) % 26
			if c.isupper():
				r += k[j].upper()
			else:
				r += k[j]
			i += 1
		else:
			r += c
			i = 1
	return r
	return text


def encode(text, key):
	return cipher(text, key, 1)

def decode(text, key):
	return cipher(text, key, -1)

def cipher(text, key, mode):
	U = tuple(dict.fromkeys(key.upper() + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'))
	L = tuple(dict.fromkeys(key.lower() + 'abcdefghijklmnopqrstuvwxyz'))
	output = ''
	i = mode
	for x in text:
		if x in U:
			output += U[(U.index(x) + i) % 26]
			i += mode
		elif x in L:
			output += L[(L.index(x) + i) % 26]
			i += mode
		else:
			output += x
			i = mode
	return output


from string import ascii_lowercase
from operator import add, sub
from itertools import starmap
from re import sub as rsub

def change(text, key, o):
	tmp = dict.fromkeys(key)
	cipher = ''.join(tmp) + ''.join((c for c in ascii_lowercase if c not in tmp))
	D = {c: i for (i, c) in enumerate(cipher)}
	a = lambda i, c: cipher[o(D[c], i) % 26]
	b = lambda i, c: cipher[o(D[c.lower()], i) % 26].upper()
	f = lambda i, c: a(i, c) if c.islower() else b(i, c) if c.isupper() else c
	g = lambda w: ''.join(starmap(f, enumerate(w.group(), 1)))
	return rsub('[a-zA-z]+', g, text)

def encode(text, key):
	return change(text, key, add)

def decode(text, key):
	return change(text, key, sub)


def encode(msg, k):
	k = ''.join(dict.fromkeys(k)) + ''.join((i for i in 'abcdefghijklmnopqrstuvwxyz' if i not in k))
	(r, i) = ('', 1)
	for s in msg:
		x = s.lower()
		if x in k:
			t = k[(k.find(x) + i) % 26]
			r += t.upper() if s.isupper() else t
			i += 1
		else:
			r += s
			i = 1
	return r

def decode(msg, k):
	k = ''.join(dict.fromkeys(k)) + ''.join((i for i in 'abcdefghijklmnopqrstuvwxyz' if i not in k))
	(r, i) = ('', 1)
	for s in msg:
		x = s.lower()
		if x in k:
			t = k[(k.find(x) - i) % 26]
			r += t.upper() if s.isupper() else t
			i += 1
		else:
			r += s
			i = 1
	return r