Преобразовать массив байтов в base64

10

Ваша миссия - написать функцию / программу, которая преобразует массив байтов (т.е. массив целых чисел от 0 до 255) в base64.

Использование встроенных кодеров base64 запрещено.

Требуемая реализация base64 - RFC 2045. (с использованием "+", "/" и обязательного заполнения с "=")

Самый короткий код (в байтах) побеждает!

Пример:

Вход (массив int): [99, 97, 102, 195, 169]

Вывод (строка): Y2Fmw6k=

XEM
источник
Какой тип соревнования это?
Cilan
Охватывает ли встроенные кодеры base64 только двоичные-текстовые кодеры или функции, также манипулирующие целыми числами?
Деннис
1
Чтобы уточнить: могу ли я использовать функцию, которая возвращает 1 2аргумент 66?
Деннис
1
Существует 9 стандартизированных или 4 нестандартизированных версии base64. Ваша ссылка =на padding сужает до 4. Какой из них вы хотите? Или вы хотите нестандартный вариант, который не имеет максимальной длины строки?
Питер Тейлор
Я предполагаю, что он / она ссылался либо на «стандартную» версию, указанную в RFC 4648, либо на версию, используемую MIME-типами, RFC 2045. Они разные, поэтому пояснение будет очень полезным.
Полу-внешний

Ответы:

4

JavaScript, 177 187 198 персонажи

function(d){c="";for(a=e=b=0;a<4*d.length/3;f=b>>2*(++a&3)&63,c+=String.fromCharCode(f+71-(f<26?6:f<52?0:f<62?75:f^63?90:87)))a&3^3&&(b=b<<8^d[e++]);for(;a++&3;)c+="=";return c}

Для добавления переносов строк \r\nпосле каждого 76-го символа добавьте 23 символа в код:

function(d){c="";for(a=e=b=0;a<4*d.length/3;f=b>>2*(++a&3)&63,c+=String.fromCharCode(f+71-(f<26?6:f<52?0:f<62?75:f^63?90:87))+(75==(a-1)%76?"\r\n":""))a&3^3&&(b=b<<8^d[e++]);for(;a++&3;)c+="=";return c}

Демо-код:

var encode = function(d,a,e,b,c,f){c="";for(a=e=b=0;a<4*d.length/3;f=b>>2*(++a&3)&63,c+=String.fromCharCode(f+71-(f<26?6:f<52?0:f<62?75:f^63?90:87))+(75==(a-1)%76?"\r\n":""))a&3^3&&(b=b<<8^d[e++]);for(;a++&3;)c+="=";return c};

//OP test case
console.log(encode([99, 97, 102, 195, 169])); // outputs "Y2Fmw6k=".

//Quote from Hobbes' Leviathan:
console.log(
 encode(
  ("Man is distinguished, not only by his reason, but by this singular passion from " +
   "other animals, which is a lust of the mind, that by a perseverance of delight " +
   "in the continued and indefatigable generation of knowledge, exceeds the short " +
   "vehemence of any carnal pleasure.")
  .split('').map(function(i){return i.charCodeAt(0)})
 )
);

Томас Лангкаас
источник
Отличное решение! Вы можете сбрить несколько байтов, используя некоторые функции ES6 и удалив некоторые дубликаты: Сокращенный код с комментариями
Крейг Эйр
@CraigAyre, спасибо за конструктивный вклад. ES6 не был доработан и доступен в то время, когда этот вызов был первоначально опубликован. Как предлагается на codegolf.meta , вы можете опубликовать сокращенную версию ES6 и пометить ее как неконкурентную.
Томас Лангкаас
Не беспокойтесь, я виноват в том, что не перепроверил оригинальную дату публикации! Я фанат вашего решения, поэтому я не собираюсь публиковать другое, но спасибо за ссылку. Шаблонная логическая логика, которая убрала дублирование алфавита, может быть преобразована в ES5 за то же количество байтов, что экономит не много, а каждый маленький счет!
Крейг Эйр
@CraigAyre, еще раз спасибо за подсказку, нашел другой способ еще более сжать символы base64 (что сделало его еще более обратно совместимым - теперь должно работать и в старом IE).
Томас Лангкаас
3

32-битная сборка x86, 59 байт

Байт-код:

66 B8 0D 0A 66 AB 6A 14 5A 4A 74 F4 AD 4E 45 0F C8 6A 04 59 C1 C0 06 24 3F 3C 3E 72 05 C0
E0 02 2C 0E 2C 04 3C 30 7D 08 04 45 3C 5A 76 02 04 06 AA 4D E0 E0 75 D3 B0 3D F3 AA C3

Разборка:

b64_newline:
    mov     ax, 0a0dh
    stosw
b64encode:
    push    (76 shr 2) + 1
    pop     edx
b64_outer:
    dec     edx
    je      b64_newline
    lodsd
    dec     esi
    inc     ebp
    bswap   eax
    push    4
    pop     ecx
b64_inner:
    rol     eax, 6
    and     al, 3fh
    cmp     al, 3eh
    jb      b64_testchar
    shl     al, 2     ;'+' and '/' differ by only 1 bit
    sub     al, ((3eh shl 2) + 'A' - '+') and 0ffh
b64_testchar:
    sub     al, 4
    cmp     al, '0'
    jnl     b64_store ;l not b because '/' is still < 0 here
    add     al, 'A' + 4
    cmp     al, 'Z'
    jbe     b64_store
    add     al, 'a' - 'Z' - 1
b64_store:
    stosb
    dec     ebp
    loopne  b64_inner
    jne     b64_outer
    mov     al, '='
    rep     stosb
    ret

Вызовите код b64, если esi указывает на входной буфер, edi указывает на выходной буфер.

Это можно сделать еще меньше, если не использовать перенос строк.

Питер Ферри
источник
1

Perl, 126 байт

читает стандартный вывод, выводит на стандартный вывод

$/=$\;print map{$l=y///c/2%3;[A..Z,a..z,0..9,"+","/"]->[oct"0b".substr$_.0 x4,0,6],$l?"="x(3-$l):""}unpack("B*",<>)=~/.{1,6}/g

ungolfed:

my @x = ('A'..'Z','a'..'z',0..9,'+','/');
my $in = join '', <>;
my $bits = unpack 'B*', $in;
my @six_bit_groups = $bits =~ /.{1,6}/g;
for my $sixbits (@six_bit_groups) {
  next unless defined $sixbits;
  $l=length($sixbits)/2%3;
  my $zero_padded = $sixbits . ( "0" x 4 );
  my $padded_bits = substr( $zero_padded, 0, 6 );
  my $six_bit_int = oct "0b" . $padded_bits;
  print $x[$six_bit_int];
  print "=" x (3 - $l)  if  $l;
}
skibrianski
источник
Вопрос прояснен, так как требует RFC 2045, поэтому вам нужно добавить немного кода, чтобы разбить вывод на куски по 76 символов и соединиться с ними \r\n.
Питер Тейлор
1

Perl, 147 байт

sub b{$f=(3-($#_+1)%3)%3;$_=unpack'B*',pack'C*',@_;@r=map{(A..Z,a..z,0..9,'+','/')[oct"0b$_"]}/.{1,6}/g;$"='';join"\r\n",("@r".'='x$f)=~/.{1,76}/g}

Функция принимает список целых чисел в качестве входных данных и выводит строку в кодировке base64.

Пример:

print b(99, 97, 102, 195, 169)

печать

Y2Fmw6kA

Ungolfed:

Версия, которая также визуализирует промежуточные шаги:

sub b {
    # input array: @_
    # number of elements: $#_ + 1 ($#_ is zero-based index of last element in 
    $fillbytes = (3 - ($#_ + 1) % 3) % 3;
      # calculate the number for the needed fill bytes
      print "fillbytes:       $fillbytes\n";
    $byte_string = pack 'C*', @_;
      # the numbers are packed as octets to a binary string
      # (binary string not printed)
    $bit_string = unpack 'B*', $byte_string;
      # the binary string is converted to its bit representation, a string wit
      print "bit string:      \"$bit_string\"\n";
    @six_bit_strings = $bit_string =~ /.{1,6}/g;
      # group in blocks of 6 bit
      print "6-bit strings:   [@six_bit_strings]\n";
    @index_positions = map { oct"0b$_" } @six_bit_strings;
      # convert bit string to number
      print "index positions: [@index_positions]\n";
    @alphabet = (A..Z,a..z,0..9,'+','/');
      # the alphabet for base64
    @output_chars = map { $alphabet[$_] } @index_positions;
      # output characters with wrong last characters that entirely derived fro
      print "output chars:    [@output_chars]\n";
    local $" = ''; #"
    $output_string = "@output_chars";
      # array to string without space between elements ($")
      print "output string:   \"$output_string\"\n";
    $result = $output_string .= '=' x $fillbytes;
      # add padding with trailing '=' characters
      print "result:          \"$result\"\n";
    $formatted_result = join "\r\n", $result =~ /.{1,76}/g;
      # maximum line length is 76 and line ends are "\r\n" according to RFC 2045
      print "formatted result:\n$formatted_result\n";
    return $formatted_result;
}

Вывод:

fillbytes:       1
bit string:      "0110001101100001011001101100001110101001"
6-bit strings:   [011000 110110 000101 100110 110000 111010 1001]
index positions: [24 54 5 38 48 58 9]
output chars:    [Y 2 F m w 6 J]
output string:   "Y2Fmw6J"
result:          "Y2Fmw6J="
formatted result:
Y2Fmw6J=

тесты:

Тестовые строки взяты из примера в вопросе примеры в статье Википедии для Base64 .

sub b{$f=(3-($#_+1)%3)%3;$_=unpack'B*',pack'C*',@_;@r=map{(A..Z,a..z,0..9,'+','/')[oct"0b$_"]}/.{1,6}/g;$"='';join"\r\n",("@r".'='x$f)=~/.{1,76}/g}

sub test ($) {
   print b(map {ord($_)} $_[0] =~ /./sg), "\n\n";
}

my $str = <<'END_STR';
Man is distinguished, not only by his reason, but by this singular passion from
other animals, which is a lust of the mind, that by a perseverance of delight
in the continued and indefatigable generation of knowledge, exceeds the short
vehemence of any carnal pleasure.
END_STR
chomp $str;

test "\143\141\146\303\251";
test $str;
test "any carnal pleasure.";
test "any carnal pleasure";
test "any carnal pleasur";
test "any carnal pleasu";
test "any carnal pleas";
test "pleasure.";
test "leasure.";
test "easure.";
test "asure.";
test "sure.";

Тестовый вывод:

TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlz
IHNpbmd1bGFyIHBhc3Npb24gZnJvbQpvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2Yg
dGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodAppbiB0aGUgY29udGlu
dWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRo
ZSBzaG9ydAp2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZSO=

YW55IGNhcm5hbCBwbGVhc3VyZSO=

YW55IGNhcm5hbCBwbGVhc3VyZB==

YW55IGNhcm5hbCBwbGVhc3Vy

YW55IGNhcm5hbCBwbGVhc3F=

YW55IGNhcm5hbCBwbGVhcD==

cGxlYXN1cmUu

bGVhc3VyZSO=

ZWFzdXJlLC==

YXN1cmUu

c3VyZSO=
Хайко Обердиек
источник
Вопрос прояснен, так как требует RFC 2045, поэтому вам нужно добавить немного кода, чтобы разбить вывод на куски по 76 символов и соединиться с ними \r\n.
Питер Тейлор
@PeterTaylor: Спасибо, я обновил ответ на RFC 2045.
Heiko Oberdiek
Браво за этот очень полный ответ. Включение обязательных разрывов строк (путем указания «RFC 2045» в OP) фактически было ошибкой, вы можете фактически игнорировать эту часть. Извините :)
xem
1

Питон, 234 символа

def F(s):
 R=range;A=R(65,91)+R(97,123)+R(48,58)+[43,47];n=len(s);s+=[0,0];r='';i=0
 while i<n:
  if i%57<1:r+='\r\n'
  for j in R(4):r+=chr(A[s[i]*65536+s[i+1]*256+s[i+2]>>18-6*j&63])
  i+=3
 k=-n%3
 if k:r=r[:-k]+'='*k
 return r[2:]
Кит Рэндалл
источник
Вопрос прояснен, так как требует RFC 2045, поэтому вам нужно добавить немного кода, чтобы разбить вывод на куски по 76 символов и соединиться с ними \r\n.
Питер Тейлор
@PeterTaylor: исправлено.
Кит Рэндалл,
1

GolfScript, 80 (77) байтов

~.,~)3%:P[0]*+[4]3*\+256base 64base{'+/''A[a{:0'{,^}/=}/{;}P*'='P*]4>76/"\r
":n*

Выше будет соответствовать ровно 76 символов в строке, за исключением последней строки. Все строки заканчиваются CRLF.

Обратите внимание, что в RFC 2045 указана переменная, максимальная длина строки которой составляет 76 символов, поэтому ценой вывода мы можем сохранить 3 дополнительных байта.

~.,~)3%:P[0]*+[4]3*\+256base 64base{'+/''A[a{:0'{,^}/=}/{;}P*'='P*]4>{13]n+}/

Выше будет печатать один символ на строку, за исключением последней строки, которая может содержать 0, 1 или 2 =символа. GolfScript также добавит окончательный LF, который, согласно RFC 2045, должен игнорироваться программным обеспечением для декодирования.

пример

$ echo '[99 97 102 195 169]' | golfscript base64.gs | cat -A
Y2Fmw6k=^M$
$ echo [ {0..142} ] | golfscript base64.gs | cat -A
AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4^M$
OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3Bx^M$
cnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY4=^M$
$ echo '[99 97 102 195 169]' | golfscript base64-sneaky.gs | cat -A
Y^M$
2^M$
F^M$
m^M$
w^M$
6^M$
k^M$
=^M$
$

Как это работает

~          # Interpret the input string.
.,~)3%:P   # Calculate the number of bytes missing to yield a multiple of 3 and save in “P”.
[0]*+      # Append that many zero bytes to the input array.
[4]3*\+    # Prepend 3 bytes to the input array to avoid issues with leading zeros.
256base    # Convert the input array into an integer.
64base     # Convert that integer to base 64.
{          # For each digit:
  '+/'     # Push '+/'.
  'A[a{:0' # Push 'A[a{:0'.
  {        # For each byte in 'A[a{:0':
    ,      # Push the array of all bytes up to that byte.
    ^      # Take the symmetric difference with the array below it.
  }/       # Result: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
  =        # Retrieve the character corresponding to the digit.
}/         #
{;}P*'='P* # Replace the last “P” characters with a string containing that many “=” chars.
]          # Collect all bytes on the stack into an array.
4>         # Remove the first four, which correspond to the 3 prepended bytes.
76/        # Collect all bytes on the stack into an array and split into 76-byte chunks.
"\r\n":n*  # Join the chunks with separator CRLF and save CRLF as the new line terminator.
Деннис
источник
1

PHP , 200 байт

<?foreach($g=$_GET as$k=>$v)$b[$k/3^0]+=256**(2-$k%3)*$v;for(;$i<62;)$s.=chr($i%26+[65,97,48][$i++/26]);foreach($b as$k=>$v)for($i=4;$i--;$p++)$r.=("$s+/=")[count($g)*4/3<$p?64:($v/64**$i)%64];echo$r;

Попробуйте онлайн!

Вы можете заменить строку ("$s+/=")на массивarray_merge(range(A,Z),range(a,z),range(0,9),["+","/","="])

Только для сравнения, какое количество байтов может быть достигнуто с недопустимым встроенным

PHP , 45 байт

<?=base64_encode(join(array_map(chr,$_GET)));

Попробуйте онлайн!

Йорг Хюльсерманн
источник
0

JavaScript (ES6), 220В

f=a=>{for(s=a.map(e=>('0000000'+e.toString(2)).slice(-8)).join(p='');s.length%6;p+='=')s+='00';return s.match(/.{6}/g).map(e=>'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'[parseInt(e,2)]).join('')+p}

Если ваш браузер не поддерживает ES6, вы можете попробовать эту версию (262B):

function f(a){for(s=a.map(function(e){return ('0000000'+e.toString(2)).slice(-8)}).join(p='');s.length%6;p+='=')s+='00';return s.match(/.{6}/g).map(function(e){return 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'[parseInt(e,2)]}).join('')+p}

f([99, 97, 102, 195, 169])возвращается "Y2Fmw6k=".

Майкл М.
источник
Где код, чтобы разделить его на 76-символьные куски, к которым присоединяются \r\n?
Питер Тейлор
0

Python - 310333

def e(b):
  l=len;c="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";r=p="";d=l(b)%3
  if d>0:d=abs(d-3);p+="="*d;b+=[0]*d
  for i in range(0,l(b)-1,3):
    if l(r)%76==0:r+="\r\n"
    n=(b[i]<<16)+(b[i+1]<<8)+b[i+2];x=(n>>18)&63,(n>>12)&63,(n>>6)&63,n&63;r+=c[x[0]]+c[x[1]]+c[x[2]]+c[x[3]]
  return r[:l(r)-l(p)]+p

Немного не одураченный

def e( b ):
    c = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
    r = p = ""
    d = len( b ) % 3

    if d > 0:
        d = abs( d - 3 )
        p = "=" * d
        b + = [0] * d

    for i in range( 0, len( b ) - 1, 3 ):
        if len( r ) % 76 == 0:
            r += "\r\n"

        n = ( b[i] << 16 ) + ( b[i + 1] << 8 ) + b[i + 2]
        x = ( n >> 18 ) & 63, ( n >> 12 ) & 63, ( n >> 6) & 63, n & 63
        r += c[x[0]] + c[x[1]] + c[x[2]] + c[x[3]]

    return r[:len( r ) - len( p )] + p

Пример :

Встроенный в Python модуль base64 используется только в этом примере, чтобы гарантировать, что eфункция имеет правильный вывод, сама eфункция не использует его.

from base64 import encodestring as enc

test = [ 99, 97, 102, 195, 169 ]
str  = "".join( chr( x ) for x in test )

control = enc( str ).strip()
output = e( test )

print output            # => Y2Fmw6k=
print control == output # => True
Тони Эллис
источник
Вопрос прояснен, так как требует RFC 2045, поэтому вам нужно добавить немного кода, чтобы разбить вывод на куски по 76 символов и соединиться с ними \r\n.
Питер Тейлор
@PeterTaylor исправлено.
Тони Эллис
0

Желе , 38 байт

s3z0Zµḅ⁹b64‘ịØb)FṖ³LN%3¤¡s4z”=Z;€“ƽ‘Ọ

Попробуйте онлайн!

Поскольку (почти) каждый второй ответ покрывает требование RFC2045 «не более 76 символов на строку с окончанием строки \r\n», я следовал ему.

Как это работает

s3z0Zµḅ⁹b64‘ịØb)FṖ³LN%3¤¡s4z”=Z;€“ƽ‘Ọ    Monadic main link. Input: list of bytes

s3z0Z    Slice into 3-item chunks, transpose with 0 padding, transpose back
         Equivalent to "pad to length 3n, then slice into chunks"

µḅ⁹b64‘ịØb)    Convert each chunk to base64
 ḅ⁹b64         Convert base 256 to integer, then to base 64
      ‘ịØb     Increment (Jelly is 1-based) and index into base64 digits

FṖ³LN%3¤¡s4z”=Z    Add correct "=" padding
F                  Flatten the list of strings to single string
 Ṗ      ¡          Repeat "remove last" n times, where
  ³LN%3¤             n = (- input length) % 3
         s4z”=Z    Pad "=" to length 4n, then slice into 4-item chunks

;€“ƽ‘Ọ    Add "\r\n" line separator
;€         Append to each line:
  “ƽ‘       Codepage-encoded list [13,10]
      Ọ    Apply `chr` to numbers; effectively add "\r\n"
фонтанчик для питья
источник
Здесь можно использовать базовую декомпрессию, но ṃØbṙ1¤она слишком длинна для простой операции.
user202729
Возможно, стоит попросить Денниса сделать атом декомпрессии с вращением основания.
user202729
Терпит неудачу за 0,0,0.
user202729