Довільна довжина хешування


16

Розглянемо, що у вас є хеш-функція яка займає рядки довжиною і повертає рядки довжиною і має приємне властивість, що вона є стійкою до зіткнення , тобто важко знайти дві різні рядки з той же хеш .H2nnssH(s)=H(s)

Тепер ви хочете побудувати нову хеш-функцію яка займає рядки довільної довжини і відображає їх у рядки довжиною , зберігаючи стійкість до зіткнення.Hn

Пощастило вам, вже в 1979 році був опублікований метод, відомий під назвою конструкція Меркле - Дамгор, який досягає саме цього.

Завданням цієї задачі буде реалізація цього алгоритму, тому ми спочатку ознайомимось з офіційним описом конструкції Меркле - Дамґер, перш ніж переглядати покроковий приклад, який повинен показати, що підхід простіший, ніж це може з’явитися спочатку.

Враховуючи деяке ціле число , хеш-функцію як описано вище, і вхідний рядок довільної довжини, нова хеш-функція виконує наступне:n>0HsH

  • Встановитиl=|s|, довжинаs і розділитиs на шматки довжиноюn , заповнивши при необхідності останній відрізок іззадніминулями. Це даєm=lnбагато шматок, які позначаютьсяc1,c2,,cm.
  • Додайте провідний і кінцевий відрізок c0 і cm+1 , де c0 - це рядок, що складається з n нулів, а cm+1 - n двійкових, підкладених ведучими нулями на довжину n .
  • Тепер ітеративно застосуйте H до поточного відрізку ci доданого до попереднього результату ri1 : ri=H(ri1ci) , де r0=c0 . (Цей крок може бути більш зрозумілим, переглянувши приклад нижче.)
  • Вихід H - кінцевий результат rm+1 .

Завдання

Напишіть програму або функцію, яка приймає як вхід додатне ціле число n , хеш-функцію H як чорний ящик і не порожній рядок s і повертає той самий результат, що і H на ті самі входи.

Це , тому найкоротша відповідь на кожній мові виграє.

Приклад

Скажімо, n=5 , тому наша задана хеш-функція H приймає рядки довжиною 10 і повертає рядки довжиною 5.

  • Враховуючи вхід s="Programming Puzzles" , ми отримуємо такі фрагменти: s1="Progr" , s2="ammin" , s3="g Puz" і s4="zles0" . Зауважте, що s4 потрібно прокласти до довжини 5 з одним кінцевим нулем.
  • c0="00000" - це лише рядок з п'яти нулів, аc5="00101" - п'ять у двійковій (101 ), обкладеній двома провідними нулями.
  • Тепер шматки поєднуються з H :
    r0=c0="00000"
    r1=H(r0c1)=H("00000Progr")
    r2=H(r1c2)=H(H("00000Progr")"ammin") r3=H(r2c3)=H(H(H("00000Progr")"ammin")"g Puz")
    r4=H(r3c4)=H(H(H(H("00000Progr")"ammin")"g Puz")"zles0")
    r5=H(r4c5)=H(H(H(H(H("00000Progr")"ammin")"g Puz")"zles0")"00101")
  • r5 - наш вихід.

Давайте подивимось, як виглядатиме цей вихід залежно від варіантів 1 для H :

  • Якщо H("0123456789")="13579" , тобто H просто повертає кожен другий символ, отримуємо:
    r1=H("00000Progr")="00Por"
    r2=H("00Porammin")="0oamn"
    r3=H("0oamng Puz")="omgPz"
    r4=H("omgPzzles0")="mPze0"
    r5=H("mPze000101")="Pe011"
    Отже,"Pe011" повинен бути результатом, якщо такийH задається як функція чорного поля.
  • Якщо H просто повертає перші 5 символів свого введення, вихід H становить "00000" . Аналогічно, якщо H повертає останні 5 символів, вихід "00101" .
  • Якщо H помножує коди символів свого введення і повертає перші п’ять цифр цього числа, наприклад H("PPCG123456")="56613" , то H("Programming Puzzles")="91579" .

1 Для простоти ці H насправді не стійкі до зіткнень, хоча це не має значення для тестування ваших даних.



Треба сказати, що приємно, що в наведеному прикладі останній "повний" хеш - "Пазли OMG!" ефективно omgPzzles0. Добре обраний приклад введення!
LambdaBeta

Чи можемо ми припустити деяку гнучкість у вхідному форматі для H (наприклад, він займає два рядки довжиною n або довший рядок, з яких він вважає лише перші 2n символів)?
Delfad0r

Чи дійсні символи пробілу, наприклад, між "g P"?
гість271314

@ guest271314 Якщо простір є частиною отриманого хешу, його потрібно вивести. Якщо хес насправді "gP", ви можете не виділити пробіл між ними.
Лайконі

Відповіді:


7

Haskell , 91 90 86 байт

n!h|let a='0'<$[1..n];c?""=c;c?z=h(c++take n(z++a))?drop n z=h.(++mapM(:"1")a!!n).(a?)

Спробуйте в Інтернеті!

Пояснення

a='0'<$[1..n]

Просто призначає рядок "00...0"( '0' н разів) доa


c?""=c
c?z=h(c++take n(z++a))?drop n z

Функція ?реалізує рекурсивне застосування h: c- це отриманий нами хеш (довжина н ), zце решта рядка. Якщо zпорожній, то ми просто повертаємось c, інакше беремо перші н символів z(можливо, забивання нулями від a), додаємо cта застосовуємо h. Це дає новий хеш, і тоді ми викликаємо ?рекурсивно цей хеш і інші символи z.


n!h=h.(++mapM(:"1")a!!n).(a?)

Функція !- це та, яка насправді вирішує завдання. Він приймає n, hі s(неявно) в якості вхідних сигналів. Ми робимо обчислення a?s, і все, що нам потрібно зробити, - додати nу двійковій формі та застосувати hще раз. mapM(:"1")a!!nповертає двійкове подання н .


1
letу охороні коротше, ніж використання where: Спробуйте в Інтернеті!
Лайконі

2
Схоже, mapM(\_->"01")aможе бути mapM(:"1")a.
xnor

7

R , 159 154 байт

function(n,H,s,`?`=paste0,`*`=strrep,`/`=Reduce,`+`=nchar,S=0*n?s?0*-(+s%%-n)?"?"/n%/%2^(n:1-1)%%2)(function(x,y)H(x?y))/substring(S,s<-seq(,+S,n),s--n-1)

Спробуйте в Інтернеті!

Гидота! Відповідаючи проблеми в R ніколи не дуже, але це жахливо. Це повчальна відповідь про те, як не написати "звичайний" код R ...

Завдяки nwellnhof за виправлення помилки, вартістю 0 байт!

Завдяки J.Doe за те, що він міняв оператор-псевдонім, щоб змінити пріоритет, добре для -4 байт.

Нижче пояснення стосується попередньої версії коду, але принципи залишаються тими ж.

function(n,H,s,               # harmless-looking function arguments with horrible default arguments 
                              # to prevent the use of {} and save two bytes
                              # then come the default arguments,
                              # replacing operators as aliases for commonly used functions:
 `+`=paste0,                  # paste0 with binary +
 `*`=strrep,                  # strrep for binary *
 `/`=Reduce,                  # Reduce with binary /
 `?`=nchar,                   # nchar with unary ?
 S=                           # final default argument S, the padded string:
  0*n+                        # rep 0 n times
  s+                          # the original string
  0*-((?s)%%-n)+              # 0 padding as a multiple of n
  "+"/n%/%2^(n:1-1)%%2)       # n as an n-bit number
                              # finally, the function body:
 (function(x,y)H(x+y)) /      # Reduce/Fold (/) by H operating on x + y
  substring(S,seq(1,?S,n),seq(n,?S,n))  # operating on the n-length substrings of S

Я думаю, 0*(n-(?s)%%n)що не працює, якщо n ділить s рівномірно. Але 0*-((?s)%%-n)треба працювати.
nwellnhof

@nwellnhof ах, звичайно, дякую, виправлено.
Джузеппе

Незначні зміни, 155 байт
Дж. Дое

1
@ J.Doe приємно! Я утримав байт , так як seqмає в 1якості fromаргументу за замовчуванням.
Джузеппе

3

C (gcc) , 251 байт

#define P sprintf(R,
b(_){_=_>1?10*b(_/2)+_%2:_;}f(H,n,x)void(*H)(char*);char*x;{char R[2*n+1],c[n+1],*X=x;P"%0*d",n,0);while(strlen(x)>n){strncpy(c,x,n);x+=n;strcat(R,c);H(R);}P"%s%s%0*d",R,x,n-strlen(x),0);H(R);P"%s%0*d",R,n,b(n));H(R);strcpy(X,R);}

Спробуйте в Інтернеті!

Не такий чистий, як розчин для башмаків, і надзвичайно неймовірний.

Ця функція fприймає Hяк функцію, яка замінює її рядок вхідним хешем, nяк в описі, і xрядок вводу та вихідний буфер.

Опис:

#define P sprintf(R,     // Replace P with sprintf(R, leading to unbalanced parenthesis
                         // This is replaced and expanded for the rest of the description
b(_){                    // Define b(x). It will return the integer binary expansion of _
                         // e.g. 5 -> 101 (still as integer)
  _=_>1?                 // If _ is greater than 1
    10*b(_/2)+_%2        // return 10*binary expansion of _/2 + last binary digit
    :_;}                 // otherwise just _
f(H,n,x)                 // Define f(H,n,x)
  void(*H)(char*);       // H is a function taking a string
  char*x; {              // x is a string
  char R[2*n+1],c[n+1],  // Declare R as a 2n-length string and c as a n-length string
  *X=x;                  // save x so we can overwrite it later
  sprintf(R,"%0*d",n,0); // print 'n' 0's into R
  while(strlen(x)>n){    // while x has at least n characters
    strncpy(c,x,n);x+=n; // 'move' the first n characters of x into c
    strcat(R,c);         // concatenate c and R
    H(R);}               // Hash R
  sprintf(R,"%s%s%0*d"   // set R to two strings concatenated followed by some zeroes
    R,x,                 // the two strings being R and (what's left of) x
    n-strlen(x),0);      // and n-len(x) zeroes
  H(R);                  // Hash R
  sprintf(R,"%s%*d",R,n, // append to R the decimal number, 0 padded to width n
    b(n));               // The binary expansion of n as a decimal number
  H(R);strcpy(X,R);}     // Hash R and copy it into where x used to be


Я думаю: 227 байт (відходить від коментаря
стельової кішки

3

Рубін , 78 байт

->n,s,g{(([?0*n]*2*s).chop.scan(/.{#{n}}/)+["%0#{n}b"%n]).reduce{|s,x|g[s+x]}}

Спробуйте в Інтернеті!

Як це працює:

([?0*n]*2*s).chop    # Padding: add leading and trailing 
                     # zeros, then remove the last one
.scan(/.{#{n}}/)     # Split the string into chunks
                     # of length n
+["%0#{n}b"%n]       # Add the trailing block
.reduce{|s,x|g[s+x]} # Apply the hashing function
                     # repeatedly


2

Bash , 127-е байт

Z=`printf %0*d $1` R=$Z
while IFS= read -rn$1 c;do R=$R$c$Z;R=`H<<<${R::2*$1}`;done
H< <(printf $R%0*d $1 `bc <<<"obase=2;$1"`)

Спробуйте в Інтернеті!

Це працює як програма / функція / сценарій / фрагмент. H повинен бути застосований до програми або функції, яка буде виконувати хешування. N - аргумент. Приклад виклику:

$ H() {
>   sed 's/.\(.\)/\1/g'
> }
$ ./wherever_you_put_the_script.sh 5 <<< "Programming Puzzles"  # if you add a shebang
Pe011

Опис:

Z=`printf %0*d $1`

Це створює рядок $1нулів. Це працює, зателефонувавши printf та наказавши йому друкувати ціле число, додане до додаткової ширини аргументу . Цей додатковий аргумент, який ми передаємо $1, це аргумент програмі / функції / скрипту, який зберігає n.

R=$Z

Це просто копіює Z, наш нульовий рядок, в R, наш результат, під час підготовки до циклу хешування.

while IFS= read -rn$1 c; do

Це петля над вхідними кодами $1(n) символів, що завантажують прочитані символи в c. Якщо вхід закінчується, то c просто закінчується занадто коротко. Ця rопція гарантує, що будь-які спеціальні символи у вводі не інтерпретуються bash. Це в заголовку - що rне є суворо необхідним, але робить функцію більш точною для введення.

R=$R$c$Z

Це об'єднує n символів, прочитаних від введення до R разом із нулями для заміщення (занадто багато нулів на даний момент).

R=`H<<<${R::2*$1}`;done

Для цього використовується рядок тут як вхід до хеш-функції. Вміст ${R::2*$1}є дещо езотеричною заміною параметрів bash, яка говорить: R, починаючи з 0, лише 2n символів.

Тут петля закінчується і закінчуємо:

H< <(printf $R%0*d $1 `bc <<<"obase=2;$1"`)

Тут той самий форматний рядковий трюк використовується для 0 прокладки номера. bcвикористовується для перетворення його у бінарне, встановивши вихідну базу (obase) на 2. Результат передається хеш-функції / програмі, вихід якої не фіксується і, таким чином, показується користувачеві.


Чому "127-ε"? Чому б не просто "127"?
Соломон Учко

Не знаю. Я був на огорожі про необхідність rпрапора. Я порахував, що 1 байт насправді не має значення, але якщо натиснути, я міг би його побрити.
LambdaBeta

Для readкоманди?
Соломон Учко

Тому що без цього `` на вході буде інтерпретуватися замість ігноруватися, тому їх доведеться уникати.
LambdaBeta

Може додати до цього записку?
Соломон Учко


2

Perl 6 , 79 68 байт

{reduce &^h o&[~],comb 0 x$^n~$^s~$n.fmt("%.{$n-$s.comb%-$n}b"): $n}

Спробуйте в Інтернеті!

Пояснення

{
  reduce         # Reduce with
    &^h o&[~],   # composition of string concat and hash function
    comb         # Split string
      0 x$^n     # Zero repeated n times
      ~$^s       # Append input string s
      ~$n.fmt("  # Append n formatted
        %.       # with leading zeroes,
        {$n             # field width n for final chunk
         -$s.comb%-$n}  # -(len(s)%-n) for padding,
        b")      # as binary number
      :          # Method call with colon syntax
      $n         # Split into substrings of length n
}


1

Python 2 , 126 113 байт

lambda n,H,s:reduce(lambda x,y:H(x+y),re.findall('.'*n,'0'*n+s+'0'*(n-len(s)%n))+[bin(n)[2:].zfill(n)])
import re

Спробуйте в Інтернеті!

-13 завдяки тригернометрії .

Так, це гидота, чому я не можу просто використовувати вбудований, щоб розділити рядок на шматки ...? :-(


codegolf.stackexchange.com/a/173952/55696while петля є кращою вбудованим я міг сподіватися. 104 байти
Стівен Х.

@StevenH. Так, особливо якщо ти фактично зосереджений на самому гольфі. > _>
Ерік Аутгольфер

'0'*~-nзамість '0'*(len(s)%n)коротший (і фактично правильний для коротших входів).
nwellnhof

@nwellnhof Так, але це точно не те саме.
Ерік Аутгольфер

Можливо, я був недостатньо зрозумілий. Ваше рішення дає неправильну відповідь для рядків типу Programming Puzz(16 знаків). Заміна '0'*(len(s)%n)з '0'*~-nвиправленнями , що і економить 7 байт.
nwellnhof

1

Python 2 , 106 102 байт

Одного разу функція перевершує лямбда. -4 байти для простого маніпулювання синтаксисом, завдяки Джо Кінгу.

def f(n,H,s):
 x='0'*n;s+='0'*(n-len(s)%n)+bin(n)[2:].zfill(n)
 while s:x=H(x+s[:n]);s=s[n:]
 return x

Спробуйте в Інтернеті!


Чи не повинен результат бути "Pe011", а не "e011"?
Тригернометрія

Щоб воно мало. Виправлено!
Стівен Х.

Використовуйте напівколонки замість нових рядків. -4 байти
Джо Кінг

Я не розумів, що працював протягом циклів, а також, дякую!
Стівен Х.

1

Japt , 27 байт

òV ú'0 pV¤ùTV)rÈ+Y gOvW}VçT

Спробуй це!

Я не знайшов можливості для Japt приймати функції безпосередньо як вхід, тому для цього потрібна рядок, що інтерпретується як код Japt, і очікує, що вона визначить функцію. Зокрема, OvWбере третій вхід і інтерпретує його як Japt, потім gназиває його. Замінивши це OxWна функцію введення в якості функції Javascript замість цього, або якщо функція (якось) вже збережена у W, вона просто може бути Wта зберегти 2 байти. Наведене вище посилання має приклад, що працюєНякий приймає символи з непарними індексами, тоді як цей - приклад "множити char-коди та взяти 5 найвищих цифр".

Завдяки тому, як Japt приймає матеріали, сбуде U,нбуде V, іН буде W

Пояснення:

òV                             Split U into segments of length V
   ú'0                         Right-pad the short segment with "0" to the same length as the others
       p     )                 Add an extra element:
        V¤                       V as a base-2 string
          ùTV                    Left-pad with "0" until it is V digits long
              r                Reduce...
                        VçT          ...Starting with "0" repeated V times...
               È       }                                                  ...By applying:
                +Y               Combine with the previous result
                   gOvW          And run W as Japt code



0

oK , 41 байт

{(x#48)(y@,)/(0N,x)#z,,/$((x+x!-#z)#2)\x}

Спробуйте в Інтернеті!

{                                       } /x is n, y is H, z is s.
                          (x+x!-#z)       /number of padding 0's needed + x
                         (         #2)\x  /binary(x) with this length
                      ,/$                 /to string
                    z,                    /append to z
             (0N,x)#                      /split into groups of length x
       (y@,)/                             /foldl of y(concat(left, right))...
 (x#48)                                   /...with "0"*x as the first left string
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.