366 Characters - Python 2
i=lambda v,c,m,j:[h(s(v,m)+[j],x) for x in range(c+1)]
h=lambda v,c:hash(','.join(map(str,sorted(v)+[c])))%1001
a=lambda v,c=0:v+[h(v,c)] if t(h(v,c),v,c) else a(v,c+1)
s=lambda v,m:[n for n in v if n!=m ]
w=lambda v,c=0:s(v,o(v,c)[0]) if len(o(v,c))==1 else w(v,c+1)
o=lambda v,c:[m for m in v if h(s(v,m),c)==m]
t=lambda j,v,c:all([m not in i(v,c,m,j) for m in v])
Could probably be golfed more, I didn't spend too much time on it. Call a with a list of integers to add a number, and call w with a shuffled version of the new list to remove that number.
Works by appending a loop counter to the sequence, and choosing as the additional number the hash modulo 1001 the concatenated sequence. If the hash generated results in a sequence where there is only one number that can be removed to generate itself for every possible value of the loop counter up to the current one, then output the sequence. Otherwise increment the loop counter and try again. To decode, remove each item from the sequence and see if the hash value of the remaining sequence+loop counter is the removed value. If only one value matches these criteria, it is the solution, otherwise increment the loop counter and try again. This version will hang if faced with an unencodable sequence due to the entire space being taken up by hash collisions, but a check can trivially be added to the a function to simply output that the sequence is unencodable.
Confirmed to work for at least all sets of 2 or less, and works with decreasing probability for larger sets. Higher probabilities might be reachable with a better hash function but that would require more characters. I did some quick random sampling to see what the probability of being able to encode an input sequence of a given size was via this method (you'll notice the precision going down as I got less and less patient):
Size Probability
5 100.00%
10 100.00%
50 100.00%
100 100.00%
200 98.3%
400 73%
800 8%
Anyone who wants to play with it can use the following version which will output whether a sequence is or is not encodable:
i=lambda v,c,m,j:[h(s(v,m)+[j],x) for x in range(c+1)]
h=lambda v,c:hash(','.join(map(str,sorted(v)+[c])))%1001
a=lambda v,c=0:v+[h(v,c)] if t(h(v,c),v,c) else a(v,c+1) if len(set([n for m in v for n in i(v,c,m,h(v,c))]))<999 else None
s=lambda v,m:[n for n in v if n!=m ]
w=lambda v,c=0:s(v,o(v,c)[0]) if len(o(v,c))==1 else w(v,c+1)
o=lambda v,c:[m for m in v if h(s(v,m),c)==m]
t=lambda j,v,c:all([m not in i(v,c,m,j) for m in v])
if __name__ == "__main__":
from random import shuffle,randrange,sample
from operator import eq
from itertools import combinations
def random_combination(iterable, r):
"Random selection from itertools.combinations(iterable, r)"
pool = tuple(iterable)
n = len(pool)
indices = sorted(sample(xrange(n), r))
return tuple(pool[i] for i in indices)
valid=invalid=0
for z in range(100): #change to test more cases
r=list(sorted(random_combination(range(1001),randrange(1,100)))) #change to test longer sequences
d=a(r)
if d is None:
invalid+=1
continue
shuffle(d)
e=sorted(w(d))
if not all(map(eq,r,e)):
raise Exception("Incorrect",r)
valid+=1
print "%d Valid, %d Invalid"%(valid,invalid)