| Bytes | Lang | Time | Link |
|---|---|---|---|
| 130 | Haskell | 241024T150737Z | Antonio |
| 221 | Google Sheets | 241022T143526Z | doubleun |
| nan | 05AB1E | 241024T130840Z | Kevin Cr |
| 139 | Python 3.8 prerelease | 241022T142643Z | NotEvenI |
| 058 | x86 32bit machine code functions | 241024T031145Z | Peter Co |
| 116 | C gcc | 241023T142931Z | jdt |
| 077 | Perl 5 | 241023T160540Z | Xcali |
| 098 | Perl 5 | 241023T154123Z | Kjetil S |
| 024 | Vyxal | 241023T004340Z | lyxal |
| 085 | JavaScript Node.js | 241022T104435Z | Arnauld |
| 038 | Charcoal | 241022T152524Z | Neil |
| 177 | Python 3.8 prerelease | 241022T114928Z | squarero |
Haskell, 130 bytes
f x n=mapM(\_->x)[0..n]
e s=do(x,y)<-zip<*>(0:)$fromEnum<$>s;f"0123456789ABCDEF"1!!mod(x-y)256
d s=[y|y<-[0..]>>=f['\0'..],e y==s]
e is the encoder, and d is the decoder. The output of the decoder is represented as answer:invalid1:invalid2:...:_|_. If this representation is invalid, the decoder may be rewritten as:
d s=head[y|y<-[0..]>>=f['\0'..],e y==s]
for an additional 4 bytes.
Google Sheets, 221 bytes
Encoder, 106 bytes
=sort(let(l,len(A1),a,code(mid(A1,sequence(1,l),1)),b,join(,dec2hex(mod({a,0}-{0,a},256),2)),left(b,2*l)))
Decoder, 115 bytes
=sort(reduce(,hex2dec(mid(B1,sequence(1,len(B1)/2,1,2),2)),lambda(a,c,a&char(mod(iferror(code(right(a)))+c,256)))))

Ungolfed:
=sort(let(
a, code(mid(A1, sequence(1, len(A1)), 1)),
b, join(, dec2hex(mod({ a, 0 } - { 0, a }, 256), 2)),
left(b, len(b) - 2)
))
=sort(let(
v, hex2dec(mid(B1, sequence(1, len(B1) / 2, 1, 2), 2)),
reduce(, v, lambda(a, c, a & char(mod(iferror(code(right(a))) + c, 256))))
))
05AB1E, 21 20 (12+8) bytes
Encoder:
Ç0š¥₁%₁+h€¦J
Input as string (or character-list); output as string.
Try it online or verify all test cases.
Decoder:
2ôHηO₁%ç
Input as string; output as character-list.
Try it online or verify all test cases.
Explanation:
Ç # Convert each char of the (implicit) input to a codepoint-integer
0š # Prepend a 0 to this list
¥ # Pop and take its forward differences
₁% # Modulo-256 each
₁+ # Add 256 to each
h # Convert those integers to hexadecimal triplets
€¦ # Remove the leading "1" from each triplet
J # Join this list of hexadecimal-pairs together
# (after which the result is output implicitly)
2ô # Split the (implicit) input-string into pairs
H # Convert each pair from hexadecimal to a base-10 integer
η # Get the prefixes of this list
O # Sum each inner prefix
₁% # Modulo-256
ç # Convert each integer to an ASCII character with this codepoint
# (after which the result is output implicitly)
Python 3.8 (pre-release), 149 139 bytes
Encoder (72 69 bytes)
Contributors: Arnauld -2 bytes; Lucenaposition -1 byte;
lambda t:"".join(map(lambda a,b:f"{ord(b)-ord(a)&255:02x}","\0"+t,t))
Takes a string as input and returns a string. Can handle empty string.
Decoder (77 70 bytes)
Contributors: squareroot12621 -5 bytes; Lucenaposition -2 bytes;
f=lambda h,l=0:h and chr(int(h[:2],16)+l&255)+f(h[2:],int(h[:2],16)+l)
Takes a string as input and returns a string. Can handle empty string.
Expanded code:
# This is for the original submission
def decoder(hex_string, last_value = 0):
# Stop recursion if end of string is reached
if hex_string == "":
return ""
# Take first character from the hex input
decoded_from_hex = int(hex_string[:2],16)
# Calculate the current character's value:
current_value = decoded_from_hex + last_value
# Recursively evaluate the remaining part of the input string and return decoded message
return chr(current_value % 256) + decoder(hex_string[2:], current_value)
x86 32-bit machine code functions, 58 bytes
Encoder is 27 bytes, using Peter Ferrie's DAS trick for integer to hex.
(Would be 26 in 16-bit mode, with stosw not needing a prefix).
x86 doesn't have a reverse-subtract like ARM's rsb, so I ended up using sub/neg which feels like it could be smaller. (Future x86 with APX will have 3-operand support even for old instructions, but that's 64-bit mode only and IIRC might need a 4-byte EVEX prefix, or at least a 2-byte REX2, so no help here.) Maybe I could have used neg / xadd? But xadd is 3 bytes, same as sub+xchg
NASM listing with line numbers and addresses stripped, just machine code and source
; EDI: char *output ESI: const uint8_t *input ECX: length (of input in bytes)
hexdelta_encode:
31 D2 xor edx,edx ; last = 0
.loop:
AC lodsb ; current = *input++
92 xchg eax, edx ; last(DL)=current , current=old_last
28 D0 sub al, dl
F6 D8 neg al ; AL = old_last - current
D4 10 aam 16 ; AH = AL/16 = high nibble. AL %= 16
3C 0A cmp al, 10
1C 69 sbb al, 69h
2F das ; AL = ASCII hex digit
86 E0 xchg al, ah ; least-significant nibble to AH, giving correct printing order
3C 0A cmp al, 10
1C 69 sbb al, 69h
2F das
66 AB stosw ; 2 bytes. *EDI++ = AX
E2 E8 loop .loop
C3 ret
Decoder is 31 bytes; would be 29 in 16-bit code (both 66 prefix bytes disappear, the 32-bit xchg can use 16-bit operand-size).
I don't know a similar hack for hex->integer decoding so I just did it the obvious way. sub ax, '00' is 4 bytes, same as if I'd done sub al, '0' twice.
; EDI: uint8_t *output ESI: char *input ECX: length (of output in bytes)
;align 16
hexdelta_decode:
31 D2 xor edx, edx
.loop:
66 AD lodsw ; high digit in AL, low digit in AH
; decode to a byte in AL
66 2D 30 30 sub ax, '00'
3C 09 cmp al, 9
76 02 jbe .low_decimal
2C 07 sub al, 7
.low_decimal:
86 E0 xchg al, ah ; reverse from printing order and put the other digit in AL for short-form encodings
3C 09 cmp al, 9
76 02 jbe .high_decimal
2C 07 sub al, 7
.high_decimal:
D5 10 aad 16 ; AL += AH<<4 AH becomes high nibble
00 D0 add al, dl
AA stosb
92 xchg eax, edx ; save the reconstructed value for next time
E2 E4 loop .loop
C3 ret
Tested with 'Hi!' and "Hello World" on Linux, nasm -felf32 / ld -m elf_i386 to link a static executable.
align 16
writebuf:
mov ebx, 1
mov ecx, outbuf
mov eax, 4
int 0x80 ; write(1, outbuf, 8)
ret
global _start
_start:
push 'Hi!'
mov esi, esp
mov edi, outbuf
mov ecx, 3
call hexdelta_encode
mov edx, 6
call writebuf
mov esi, hello_hexdelta
mov edi, outbuf
mov ecx, hello_hexdelta.size / 2
call hexdelta_decode
mov edx, hello_hexdelta.size / 2
call writebuf
mov eax, 1
xor ebx, ebx
int 0x80
section .data
hello_hexdelta: db "481D070003B1371803FAF8BD"
.size: equ $-hello_hexdelta
section .bss
outbuf: resb 1024
If you wanted this to run fast for non-tiny inputs, encode could process a SIMD vector in parallel (two loads offset by 1 byte to feed vpsubb), with AVX-512VBMI2 making conversion to hex possible in a couple single-uop instructions per vector of data. (https://stackoverflow.com/questions/53823756/how-to-convert-a-binary-integer-number-to-a-hex-string)
Decode isn't trivially parallel, but SIMD prefix-sums are a thing, e.g. see this for 4-byte integers, and my comments there for links to other stuff. And vpsadbw against a zeroed vector to sum bytes within each qword of a vector gives fast lookahead which could help ILP.
C (gcc), 116 bytes
Encoder: 50 bytes
f(char*t){*t&&printf("%02X",*t-t[-1]&255)+f(t+1);}
Exploiting a quirk in GCC when run in TIO, where string constants appear to be null-terminated at both the beginning and the end.
Decoder: 66 bytes
l;f(char*h){!*h?l=0:putchar(l+=strtol(strndup(h,2),0,16))+f(h+2);}
Perl 5 77 bytes
Encoder:
Perl 5 -F, 47 bytes
map{printf'%02x',(256-ord($p)+ord)%256;$p=$_}@F
Decoder:
Perl 5 -p, 30 bytes
s/../chr($t=($t+hex$&)%256)/ge
Perl 5, 98 bytes
sub{my$l;pop=~s/./sprintf"%02X",-$l+($l=ord$&)&255/ger} #encoder 55 bytes
sub{my$r;pop=~s/../$r+=hex$&;chr$r%256/ger} #decoder 43 bytes
Vyxal, 24 bytes
Encoder: 16 bytes
C₌h¨p‡$-₈%JH2↳›∑
Decoder: 8 bytes
2ẇH¦₈%C∑
Try a test suite of encode and decode at the same time
Encoder Explained
C₌h¨p‡$-₈%JH2↳›∑
C # Convert input to list of character codes
₌h # Extract the head of that list for later, and also
¨p‡ # to each overlapping pair, reduce by:
$- # subtraction with reversed arguments
₈% # Modulo each value by 256 in the result
J # And put the original head character back
H # Convert each number to hexadecimal
2↳› # Pad with 0s as needed
∑ # and join into a single string
💎
Created with the help of Luminespire.
Decoder Explained
2ẇH¦₈%C∑
2ẇ # Split the input into pairs
H # Convert each pair from hexadecimal
¦ # Cumulative sums
₈% # modulo 256
C # Convert each number to the character corresponding to the character code
∑ # Join into a single string
💎
Created with the help of Luminespire.
JavaScript (Node.js), 85 bytes
Both functions expect and return a string.
Encoder (47 bytes)
s=>Buffer(s).map(c=>-p+(p=c),p=0).toString`hex`
Decoder (38 bytes)
s=>Buffer(s,"hex").map(c=>p+=c,p=0)+""
Charcoal, 38 bytes
Encoder, 20 bytes
⭆θΦ⍘⁺⁵¹²⁻℅ι℅§⁺ψθκ¹⁶μ
Try it online! Link is to verbose version of code. Explanation:
θ Input string
⭆ Map over characters and join
ι Current character
℅ Take the ASCII code
⁻ Subtract
ψ Predefined variable null byte
⁺ Concatenated with
θ Input string
§ Indexed by
κ Current index
℅ Take the ASCII code
⁺ Plus
⁵¹² Literal integer `512`
⍘ Converted to base
¹⁶ Literal integer `16`
Φ Filtered where
μ Not first character
Implicitly print
Decoder, 18 bytes:
F⪪S²℅﹪Σ⊞Oυ↨ι¹⁶¦²⁵⁶
Try it online! Link is to verbose version of code. Explanation:
F⪪S²
Split the input into pairs of hex digits and loop over each pair in turn.
℅﹪Σ⊞Oυ↨ι¹⁶¦²⁵⁶
Convert the hex to decimal and add it to the running total, reducing that modulo 256 and converting back to ASCII.
Python 3.8 (pre-release), 177 bytes
Encoder, 98 bytes
lambda s:bytes([*s]and[ord(s[0])]+[(ord(j)-ord(i))%256for i,j in zip(s[:-1],s[1:])]).hex().upper()
-8 bytes if the .upper() isn't required, and -7 bytes if the empty strings don't have to be handled.
Decoder, 79 bytes
lambda b:''.join(chr(sum(bytes.fromhex(b)[:i+1])%256)for i in range(len(b)//2))
-12 bytes (lambda b:[sum(bytes.fromhex(b)[:i+1])%256for i in range(len(b)//2)]) if the result can be a list of Unicode codepoints.