Информация об изменениях

Сообщение Re: Забавный факт про сложность пароля от 28.11.2022 2:29

Изменено 28.11.2022 2:40 vsb

Re: Забавный факт про сложность пароля
Если вдруг кому будет нужна правильная формула, то описание ниже:

Итак постановка задачи: у нас генерируется пароль из m групп символов длиной n символов, m <= n. Группы символов не пересекаются, размер каждой группы x_i символов. К примеру пароль длиной 8 из больших, маленьких букв и цифр, m = 3, n = 8, x = [26, 26, 10]. Также дополнительное условие — в пароле должен быть хотя бы один символ из каждой группы.

Вопрос — сколько таких паролей может быть.

Ответ (без доказательств): рассмотрим выражение (x_1 + x_2 + ... + x_m)^n. Если раскрыть скобки, получится сумма элементов вида C(n,k_1,...,k_n) * x_1^k_1 * x_2^k_2 * ... * x_m^k^m. Ну к примеру (x_1 + x_2)^2 = C(2, 2, 0) * x_1^2 * x_2^0 + C(2, 1, 1)*x_1^1*x_2^1 + C(2, 0, 2)*x_1^0*x_2^2 = x_1^2 + 2*x_1*x_2 + x_2^2. Суммирование идёт для всех сочетаний k_1, ..., k_m таких, что k_i >= 0, k_1 + ... + k_m = n. C(n,k_1,...,k_n) = n! / (k_1! * k_2! * ... * k_n!). Это в принципе известная формула.

Далее в этой формуле в её правом выражении выбрасываем все слагаемые, у которых k_i = 0. Иными словами вместо k_i >= 0 пишем k_i > 0. Вот эта сумма нам и даст ответ на исходный вопрос.

Как это выражение записать компактней, я не придумал. Ниже код на JavaScript, который вычисляет значение этого выражения "в лоб". Но для больших n будет довольно долго работать.

    function calculateTotalPasswordCount(characterGroups, length) {
      const m = characterGroups.length;
      const n = length;
      const x = [];
      for (let i = 1; i <= m; i++) x[i] = BigInt(characterGroups[i - 1].length);

      /*
      * s =
      * \sum_{k_1 + k_2 + \cdots + k_m = n;\ k_1, k_2, ..., k_m > 0 }
      * \frac{n!}{k_1! k_2! \cdots k_m!}
      * \prod_{i=1}^{m}x_i^{k_i}
      */

      const f = []; // f[i] = i!
      f[1] = 1n;
      for (let i = 2; i <= n; i++) f[i] = f[i - 1] * BigInt(i);

      const xp = []; // xp[i][j] = x[i] ** j
      for (let i = 1; i <= m; i++) {
        xp[i] = [];
        xp[i][1] = x[i];
        for (let j = 2; j <= n - m + 1; j++) {
          xp[i][j] = xp[i][j - 1] * x[i];
        }
      }

      let s = 0n
      const k = [];
      for (let j = 1; j <= m - 1; j++) k[j] = 1;
      k[m] = n - m + 1;

      while (true) {
        let fk = 1n; // fk = k_1! k_2! ... k_m!
        for (let i = 1; i <= m; i++) fk *= f[k[i]];

        let sk = 1n; // sk = x_1^{k_1} x_2^{k_2} ... x_m^{k_m}
        for (let i = 1; i <= m; i++) sk *= xp[i][k[i]];

        s += f[n] / fk * sk;

        let j = m - 1;
        while (j > 0 && k[m] === 1) {
          k[m] += k[j] - 1;
          k[j] = 1;
          j--;
        }
        if (j === 0) break;
        k[j]++;
        k[m]--;
      }

      return s;
    }
Re: Забавный факт про сложность пароля
Если вдруг кому будет нужна правильная формула, то описание ниже:

Итак постановка задачи: у нас генерируется пароль из m групп символов длиной n символов, m <= n. Группы символов не пересекаются, размер каждой группы x_i символов. К примеру пароль длиной 8 из больших, маленьких букв и цифр, m = 3, n = 8, x = [26, 26, 10]. Также дополнительное условие — в пароле должен быть хотя бы один символ из каждой группы.

Вопрос — сколько таких паролей может быть.

Ответ (без доказательств): рассмотрим выражение (x_1 + x_2 + ... + x_m)^n. Если раскрыть скобки, получится сумма элементов вида C(n,k_1,...,k_n) * x_1^k_1 * x_2^k_2 * ... * x_m^k^m. Ну к примеру (x_1 + x_2)^2 = C(2, 2, 0) * x_1^2 * x_2^0 + C(2, 1, 1)*x_1^1*x_2^1 + C(2, 0, 2)*x_1^0*x_2^2 = x_1^2 + 2*x_1*x_2 + x_2^2. Суммирование идёт для всех сочетаний k_1, ..., k_m таких, что k_i >= 0, k_1 + ... + k_m = n. C(n,k_1,...,k_n) = n! / (k_1! * k_2! * ... * k_n!). Это в принципе известная формула.

Далее в этой формуле в её правом выражении выбрасываем все слагаемые, у которых k_i = 0. Иными словами вместо k_i >= 0 пишем k_i > 0. Вот эта сумма нам и даст ответ на исходный вопрос.

Как это выражение записать компактней, я не придумал. Ниже код на JavaScript, который вычисляет значение этого выражения "в лоб". Но для больших n будет довольно долго работать.

    function calculateTotalPasswordCount(characterGroups, length) {
      const m = characterGroups.length;
      const n = length;
      const x = [];
      for (let i = 1; i <= m; i++) x[i] = BigInt(characterGroups[i - 1].length);

      /*
      * s =
      * \sum_{k_1 + k_2 + \cdots + k_m = n;\ k_1, k_2, ..., k_m > 0 }
      * \frac{n!}{k_1! k_2! \cdots k_m!}
      * \prod_{i=1}^{m}x_i^{k_i}
      */

      const f = []; // f[i] = i!
      f[1] = 1n;
      for (let i = 2; i <= n; i++) f[i] = f[i - 1] * BigInt(i);

      const xp = []; // xp[i][j] = x[i] ** j
      for (let i = 1; i <= m; i++) {
        xp[i] = [];
        xp[i][1] = x[i];
        for (let j = 2; j <= n - m + 1; j++) {
          xp[i][j] = xp[i][j - 1] * x[i];
        }
      }

      let s = 0n
      const k = [];
      for (let j = 1; j <= m - 1; j++) k[j] = 1;
      k[m] = n - m + 1;

      while (true) {
        let fk = 1n; // fk = k_1! k_2! ... k_m!
        for (let i = 1; i <= m; i++) fk *= f[k[i]];

        let sk = 1n; // sk = x_1^{k_1} x_2^{k_2} ... x_m^{k_m}
        for (let i = 1; i <= m; i++) sk *= xp[i][k[i]];

        s += f[n] / fk * sk;

        let j = m - 1;
        while (j > 0 && k[m] === 1) {
          k[m] += k[j] - 1;
          k[j] = 1;
          j--;
        }
        if (j === 0) break;
        k[j]++;
        k[m]--;
      }

      return s;
    }

    calculateTotalPasswordCount(["abcdefghijklmnopqrstuvwxyz", "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "0123456789"], 12)