В этой статье я делюсь найденным кое-как решением бесполезной задачи, поэтому не относитесь к ней очень серьёзно. Но вы можете поучаствовать в поиске решения или поделиться готовыми в комментариях.

Задача

Нужно найти одну или несколько групп по 5 слов из 5 букв, которые отвечают следующим условиям:

  1. Это существительные, в именительном падеже, единственном числе

  2. Состоят из 25 разных букв, то есть одна буква в группе встречается один раз

  3. Слова существуют в русском языке и употребляются в современной речи

Скриншот игры
Скриншот игры

Формулировка задачи собственная, мотивирована игрой "5 букв" из банковского приложения. Суть игры в том, чтобы отгадать какое слово было загадано игрой.

Даётся 6 попыток, поэтому чем больше букв можно проверить за 5 попыток, тем вероятнее, что к шестой придёшь с полным набором букв.

Никакой практической ценности в этом нет, что только усугубило желание найти заветный список слов.

Поиск решения

  1. Гугление не дало результата. Удалось найти похожую на тему по английским словам на Reddit, но русскоязычных статей не нашёл. Это одна из причин почему написана эта.

  2. Нейросети с задачей как-то не справились. Может, надо как-то хитро описать промт и докопаться до нейросети, но человеческое описание задачи не приводило к результатам. Обычно все сдавались либо к третьему, либо к четвёртому слову.

  3. Собственный поиск привёл меня на сайт sanstv.ru, откуда удалось набрать такую пятёрку: блюдо, связь, фрейм, шухна, щипцы. Но последнее слово не подходит под условия и не принимается игрой. Но эта пятёрка слов для меня стала проверочной в последствии.

  4. Комбинаторика, цепи Маркова и другие технически решения, которые я сам бы закодить не смог.

С выходом ChatGPT 5 мне стало интересно решить эту задачу всё-таки финально.

Идеальная симуляция бурной деятельности

Подход попросить ChatGPT по-человечески привёл меня к очень человеческому результату. Нейросеть обещала сделать, но с каждым разом становилась всё дальше от бога и результата:

Фрагменты переписки с ChatGPT
Фрагменты переписки с ChatGPT

С промптами, как видите, я не старался, оставляю этот вариант решения для гуру промптов. Решать задачу таким образом я отказался и пошёл иначе:

  1. С ранее упомянутого сайта скачал все слова из 5 букв, которые есть в их словаре. Получилось 7588 слов. Там не только существительные и совсем редкие и странные слова (например, вы знали что такое "гзымс"?), но всё реально существующие.

  2. Несколько часов пытал нейросеть, чтобы он выдал скрипт, который быстро и корректно отработает.

  3. Вручную проверял получаемые результаты. Самый объёмный результат получился на 407 559 173 строк из групп по 5 слов, размер файла 22.42Гб. Но там быстро нашлись повторы в группах, а моя "проверочная" группа слов не была записана.

И вот я был вознаграждён финальным скриптом:

Скрытый текст
const fs = require("fs");
const path = require("path");

const OUTFILE = process.argv.includes("--output")
  ? process.argv[process.argv.indexOf("--output") + 1]
  : "groups.csv";
const MAX_GROUPS = process.argv.includes("--max")
  ? parseInt(process.argv[process.argv.indexOf("--max") + 1], 10)
  : 0;
const PROGRESS_EVERY = process.argv.includes("--progressEvery")
  ? parseInt(process.argv[process.argv.indexOf("--progressEvery") + 1], 10)
  : 200;

function uniqCount5(s) {
  return new Set(s).size === 5;
}

function buildMasks(words) {
  const charIndex = new Map();
  let next = 0;
  const masks = [];
  for (let w of words) {
    let m = 0n;
    for (let ch of w) {
      let id = charIndex.get(ch);
      if (id === undefined) {
        id = next++;
        charIndex.set(ch, id);
      }
      m |= 1n << BigInt(id);
    }
    masks.push(m);
  }
  return masks;
}

function popcntBigInt(bn) {
  let c = 0n;
  while (bn) { bn &= (bn - 1n); c++; }
  return c;
}

const wordsPath = path.resolve("words.json");
let words = JSON.parse(fs.readFileSync(wordsPath, "utf8"))
  .map(w => String(w).trim().toLowerCase())
  .filter(w => w.length === 5 && uniqCount5(w));

const N = words.length;
console.log(`Слов с 5 уникальными буквами: ${N}`);

const masks = buildMasks(words);

// совместимость — соседи только с бОльшими индексами
const neighbors = Array.from({ length: N }, () => []);
for (let i = 0; i < N; i++) {
  for (let j = i + 1; j < N; j++) {
    if ((masks[i] & masks[j]) === 0n) neighbors[i].push(j);
  }
}

const out = fs.createWriteStream(OUTFILE, { encoding: "utf8" });
out.write("word1,word2,word3,word4,word5\n");

let found = 0;
let buf = [];
const started = Date.now();

function flushBuf() {
  if (buf.length === 0) return true;
  const ok = out.write(buf.join(""));
  buf = [];
  return ok;
}

function printProgress(i) {
  const elapsed = ((Date.now() - started) / 1000).toFixed(1);
  const pct = ((i / Math.max(1, N - 5)) * 100).toFixed(2);
  process.stdout.write(`\rПрогресс i: ${i}/${N - 5} (${pct}%), найдено: ${found}, время: ${elapsed}s`);
}

(async () => {
  const mark = new Uint8Array(N);

  outer: for (let i = 0; i <= N - 5; i++) {
    const ni = neighbors[i];
    if (ni.length < 4) { if (i % PROGRESS_EVERY === 0) printProgress(i); continue; }

    for (const v of ni) mark[v] = 1;
    for (const j of ni) {
      const candK = [];
      for (const v of neighbors[j]) if (v > j && mark[v]) candK.push(v);
      if (candK.length < 3) continue;
      for (const v of candK) mark[v] = 2;

      for (const k of candK) {
        const candL = [];
        for (const v of neighbors[k]) if (v > k && mark[v] === 2) candL.push(v);
        if (candL.length < 2) continue;
        for (const v of candL) mark[v] = 3;

        for (const l of candL) {
          const candM = [];
          for (const v of neighbors[l]) if (v > l && mark[v] === 3) candM.push(v);
          for (const m of candM) {
            const u = masks[i] | masks[j] | masks[k] | masks[l] | masks[m];
            if (popcntBigInt(u) !== 25n) continue;

            buf.push(`${words[i]},${words[j]},${words[k]},${words[l]},${words[m]}\n`);
            found++;
            if (buf.length >= 1000) {
              if (!flushBuf()) await new Promise(res => out.once("drain", res));
            }
            if (MAX_GROUPS && found >= MAX_GROUPS) break outer;
          }
        }

        for (const v of candL) mark[v] = 2;
      }
      for (const v of candK) mark[v] = 1;
    }
    for (const v of ni) mark[v] = 0;

    if (i % PROGRESS_EVERY === 0) printProgress(i);
  }

  flushBuf();
  await new Promise(res => out.end(res));
  printProgress(N - 5);
  const dt = ((Date.now() - started) / 1000).toFixed(1);
  console.log(`\nГотово. Найдено групп: ${found}. Время: ${dt}s. Файл: ${OUTFILE}`);
})();

Консольные команды я не просил и не использовал, но скрипт какой получил, такой и оставил.

Результат

Этот скрипт выдал мне финальный файл на 241 Мб, где было 4 392 707 групп по 5 слов. Всего из первоначальных 7 588 слов получилось 5 131 с уникальными буквами, то есть были исключены слова, типа "масса", где буквы повторяются.

Проверочная группа с неподходящим словом "щипцы" вошла в финальный файл и я смог найти там вариант, где нет множественного числа. А также, ещё одну группу слов, подходящую под условия. Вот обе группы:

  • блюдо, связь, фрейм, цыган, щупик

  • акциз, выгон, судья, тэмбр, шлейф

Убрав прилагательные, редкие слова, множественное число, сократить получилось до 2 013 390 групп, но это явно не предел. Так как свой интерес я удовлетворил и нашёл пару подходящих групп, то дальше раскручивать эту задачу не стал.

Тем не менее, мне интересно было бы узнать, можно ли искать решение "по науке" / дожать нейросеть корректным промтом / закодить задачу в ML или как-то ещё. Поэтому делитесь в комментариях своими идеями.

Комментарии (10)


  1. wees1989
    20.08.2025 12:24

    Интересная задача. Я тоже когда подсел на Словодел, захотел найти комбинацию слов без повторения букв. Но я решил не все буквы проверять, а только самые часто употребляемые. Я придумал 2 слова: икона, супер. Никаких нейросетей или программирования, только ковыряние в носу.


    1. melodyn Автор
      20.08.2025 12:24

      Да, я так тоже придумал себе три слова "право", "зенит", "кумыс" и потом ещё 4, указанных в статье. А потом стало интересно, где же предел. И вот уже 4млн групп.


      1. mcfev
        20.08.2025 12:24

        Я начинаю с «аудио» чтоб проверить максимум гласных


  1. lightln2
    20.08.2025 12:24

    Это стандартная задача на поиск в глубину, таких много на литкоде. Самый простой (и медленный) вариант на питоне:

    from sys import setrecursionlimit
    from textwrap import wrap
    
    setrecursionlimit(100000)
    
    words = open('words.txt').read().split()
    def no_repeats(word): return len(set(word)) == len(word)
    words = [word for word in words if len(word) == 5 and no_repeats(word)]
    
    def dfs(result, index):
        if len(result) >= 25: 
            print(wrap(result, 5))
            return
        if index >= len(words):
            return
        if no_repeats(result+words[index]):
            dfs(result+words[index], index+1)
        dfs(result, index+1)
    
    dfs("", 0)
    


  1. format1981
    20.08.2025 12:24

    Я тоже решал эту задачу, но без ИИ. Что-то мне лень было промпты составлять, интереснее было самому написать.

    В итоге вот такие наборы получились
    ['БОДЯК', 'ВЗМАХ', 'ГНУСЬ', 'ПРИЧТ', 'ШЛЕЙФ'],
    ['ВЗДОХ', 'ГБАЙТ', 'МЯКИШ', 'ПУЛЬС', 'ФРЕНЧ'],
    ['ГНЕЙС', 'ДИЧОК', 'ЗУМПФ', 'ХЛЯБЬ', 'ШВАРТ'],
    ['ЗУМПФ', 'КОРЧА', 'СДВИГ', 'ХЛЯБЬ', 'ШТЕЙН'],
    ['ЗУМПФ', 'РАЧОК', 'СДВИГ', 'ХЛЯБЬ', 'ШТЕЙН'],
    ['ЗУМПФ', 'РОГАЧ', 'СКВИД', 'ХЛЯБЬ', 'ШТЕЙН'],

    Но в итоге пользуюсь четырехсловными наборами. Так у меня остается 2 попытки чтобы угадать слово.


  1. axion-1
    20.08.2025 12:24

    Задачка выглядит интересной, но она чисто алгоритмическая, к ML отношения не имеет. Исходный набор слов можно ещё сократить если убрать анаграммы. Может имеет смысл попробовать с шестью словами, или пятью шестибуквенными, есть шанс что найдутся решения, возможно даже окажется одно уникальное.


    1. melodyn Автор
      20.08.2025 12:24

      Если решать задачу целиком, с условием про распространённые существительные и т.д., то алгоритмом вряд ли это решишь. Если только предварительно не взять словарь таких существительных.


  1. Zara6502
    20.08.2025 12:24

    1. Гугление не дало результата. Удалось найти похожую на тему по английским словам на Reddit, но русскоязычных статей не нашёл. Это одна из причин почему написана эта.

    тема очень старая и много где обсуждалась, на Хабре есть статья и где-то еще была, там даже я ответы писал, типа слова "КОРАН" как основа.


    1. melodyn Автор
      20.08.2025 12:24

      Хорошая статья, спасибо за ссылку!


  1. wataru
    20.08.2025 12:24

    Вы не первый до этой задачи додумались. Поп-математик Мэт Паркер уже популяризировал эту тему. И там это тоже пошло с wordle. Вот видео с его решением. Но он не программист, так что ему в комметариях накидали и ускорили решение в сотни миллионов раз. Так что вот второе видео от него c обзором чужих решений. (Мама, я в телевизоре!)

    Решения через динамическое программирование с использованием битовых масок в интернете заоптимизировали так, что бутылочным горлышком становится ввод/вывод, и для словаря со всеми английскими словами оно работает за миллисекунды.