Chris Maunder Ответов: 9

Задача кодирования: создание анаграмм.


Сегодняшняя задача - вернуться к струнам.

Учитывая случайную (или не очень случайную) строку, генерируйте уникальные анаграммы.

Бонусные баллы: подключитесь к службе проверки орфографии по вашему выбору (локальная машина, резервная копия в ОС, удаленный веб-сервис), чтобы возвращать только те анаграммы, которые являются реальными словами.

Фокус в том, что это должно быть быстрое решение. Подумайте о том, как уменьшить количество проверок орфографии, необходимых, если вы решите возвращать только фактические слова.

прошлая неделя

Ричард Диминг получает кружку за решение, принятое на прошлой неделе. Свяжитесь с Шоном, и вы можете стать подопытным кроликом для нашего нового дизайна кружки.

Richard Deeming

Я получить кружка, или я являюсь кружка? :Д

И разве это не должно быть так "запеченный в", вместо того чтобы "задом наперед"?

Maciej Los

Как по мне - запеченный.

PIEBALDconsult

Хммм... перестановки символов... в сочетании с деревом проверки орфографии... и у меня уже есть доступ к "базе данных" , содержащей словарь Scrabble...

Jörgen Andersson

Если бы мне разрешили использовать базу данных с индексом на основе функций это определенно можно было бы сделать за доли секунды

PIEBALDconsult

Кто-то должен опубликовать некоторые примеры.

PIEBALDconsult

Пожалуйста, обратите внимание, что words.txt файл содержит эти прелести:
Архитектура
Флуоресцентный раствор
Без надобности

(Но words3.txt но это не так.)

С другой стороны, words3.txt содержит более шестнадцати тысяч дублированных слов.

9 Ответов

Рейтинг:
2

PIEBALDconsult

Дана база данных слов и функция сортировки символов в строке...

SELECT [word] FROM [dictionary] WHERE sort([word])=sort([inputstring])

:смеяться:


Редактировать:

Используя SQL Server, у меня есть список из 216634 слов, с их буквами, предварительно отсортированными. Я еще не добавил индекс. Следующее завершается менее чем за секунду.

DECLARE @i VARCHAR(16) = 'teaser'
DECLARE @s VARCHAR(16) = dbo.SortLetters ( @i )
SELECT [Word] FROM [WordList] WHERE [Letters] = @s AND [Word] != @i ORDER BY [Word]


ARETES
EASTER
EATERS
REATES
RESEAT
SAETER
SEATER
STEARE



Редактировать:

После добавления words.txt и еще words3.txt с сайта, о котором упоминали другие
(и игнорируя "слова", которые не являются строго алфавитными) в общей сложности 463260 слов...

ARETES
ASTEER
EASTER
EASTRE
EATERS
ERASTE
REATES
RESEAT
RESETA
SAETER
SEATER
STAREE
STEARE
TERESA



Редактировать:

Я вспомнил, что в OpenVMS есть список слов, поэтому я извлек его из одной из своих систем и добавил в свой основной список (теперь всего пять списков).

Он содержит 42979 записей, 2973 из которых нет ни в одном из других списков (в основном имена, такие как Боромир), включая эти:

CARDIOVASCULATORY
DJANGOLOGY
DOPPLEGANGER


А эти:

ARBRITRARY
ATTACTIVE
COWRTIERS
DOCTERS
EXPECIALLY


(Я не просматривал их все.)
К сожалению, он не содержит новых анаграмм "тизера".


Редактировать:

После удаления дубликатов слов из моего основного списка у меня есть 466224 слова.
Я вижу, что кто-то разместил анаграммы "предупреждений", так что вот что есть в моей базе данных:

Word   HowMany Which
ALTERS 5       31
ARTELS 4       15    
ESTRAL 4       15
LASTER 4       15
LASTRE 2       12
RASTLE 2       12
RATELS 4       15
RELAST 2       12
RESALT 2       12
SALTER 5       31
SLATER 5       31   
STALER 4       15   
STELAR 4       15   
TALERS 4       15    
TARSEL 1       2


Столбец HowMany указывает, сколько входных списков содержат это слово.
Столбец Which - это битовое сопоставленное значение,указывающее, какие входные списки содержат его.


Graeme_Grant

Ты что, смеешься над моей статистикой?

PIEBALDconsult

Нужно ли мне это делать?

PIEBALDconsult

Ваша статистика настолько уродлива, что имеет нестандартное отклонение.

Ваша статистика настолько глупа, что Сигма отрицательна.


Graeme_Grant

хе-хе...

Graeme_Grant

Разве не будет задержки на диске, чтобы замедлить вас по сравнению с памятью?

PIEBALDconsult

Возможно, но вы можете использовать таблицу в памяти или поместить базу данных на твердотельный накопитель. Меня это не особенно волнует. И кроме того, это все еще производит только однословные анаграммы; реальное решение более сложное.

Рейтинг:
2

Thomas Daniels

Питон


Принимает слово из командной строки и возвращает все анаграммы, которые являются словами.
Как это работает:
  1. Он использует Python itertools чтобы сгенерировать все перестановки. Это, вероятно, самый быстрый способ сделать это.
  2. The fastest way to check if words are real, should be to check their existence in a set loaded into memory. Then you don't need to read the file system and you don't need to send a HTTP request, which can take some time. I included a full word list in the source, from /usr/share/dict/words on my Ubuntu virtual machine. However, that file is ~900kB, which is pretty big. For this reason I did not include the 'raw' words in the source, but I zlib-compressed them and base64-encoded this compression, which is 'only' 333kB (still big, but more acceptable - and speed was a concern, memory and source length were not :) ). That string is put into the source code, and on the startup of the program, it decompresses it and created a Python set, что потенциальные анаграммы проверяются против.


Это и есть код:
Полный код анаграмм для CodeProject coding challenge · GitHub[^]
Код без огромных строку в кодировке base64 :
import sys, base64, zlib, itertools

def main():
    input_word = sys.argv[1]
    perms = set([''.join(x) for x in itertools.permutations(input_word)])
    for w in perms:
        if is_word(w):
            print(w)

word_compressed_base64 = "some huge thing that I'm not going to copy here; see Gist for full code"

word_set = None

def prepare_words_set():
    global word_set
    decoded = base64.standard_b64decode(word_compressed_base64)
    decompressed = zlib.decompress(decoded).decode('utf-8')
    word_set = set(decompressed.split(';'))

def is_word(w):
    return w in word_set

prepare_words_set()
main()


Graeme_Grant

Решение такого размера замедлит загрузку страницы по мере размещения других решений. Таким образом, вы можете обрезать закодированные данные для просмотра (до нескольких строк) и добавить ссылку для загрузки или использования скомпилировать Перл онлайн[^] чтобы разместить полное решение.

Thomas Daniels

да, ты прав. Я поместил полный код в GitHub Gist.

Рейтинг:
2

mrcbitting

С#



Использует локальный список слов (от GitHub - dwyl/english-words: текстовый файл, содержащий 479 тысяч английских слов для быстрого поиска автозавершения / самовнушения.[^]

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

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace anagramGenerate
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            Dictionary<string, int> words = new Dictionary<string, int>();

            //using a local version of:
            //https://raw.githubusercontent.com/dwyl/english-words/master/words.txt

            using (StreamReader sr = File.OpenText("words.txt"))
            {
                string s = String.Empty;
                while ((s = sr.ReadLine()) != null)
                {
                    if (s.Length > 0)
                    {
                        words.Add(s, s.Length);
                    }
                }
            }

            Console.WriteLine("Word you'd like to see anagrams for (" + words.Count + " words loaded): ");

            string newWord;

            do
            {
                newWord = Console.ReadLine();

                if (newWord != null)
                {
                    newWord = newWord.ToLower();

                    int wordLen = newWord.Length;
                    string w1sort = String.Concat(newWord.OrderBy(w => w));
                    Console.WriteLine("Anagrams for " + newWord + ":");
                    foreach (var pair in words)
                    {
                        if (pair.Value == wordLen && pair.Key != newWord)
                        {
                            if (w1sort == String.Concat(pair.Key.OrderBy(w => w)))
                            {
                                Console.WriteLine(pair.Key);
                            }
                        }
                    }
                }
            } while (newWord != null);
        }
    }
}


Скриншот показываю некоторое время. (Мне любопытно, думают ли другие, что это хорошо или плохо):

Обновление 2


Поскольку кажется, что все обновляют свое решение (имейте в виду, что это мой первый вход), Ниже приведено новое, более быстрое решение:

Это использует массив хэш-кода int для выполнения поиска. А ты как думаешь?

using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;

namespace anagramGenerate
{
    internal class Program2
    {
        private static int ssorthash(string str)
        {
            char[] foo = str.ToArray();
            Array.Sort(foo);
            return new string(foo).GetHashCode();
        }

        private static void Main(string[] args)
        {
            //using a local version of:
            //https://raw.githubusercontent.com/dwyl/english-words/master/words.txt
            var s1 = Stopwatch.StartNew();

            StreamReader sr = new StreamReader(@"C:\Users\cbitting\Downloads\words.txt");
            string[] wordlist = sr.ReadToEnd().Split("\r\n".ToCharArray());
            int[] hashes = new int[wordlist.Count()];

            for (int i = 0; i <= wordlist.GetUpperBound(0); i++)
            {
                hashes[i] = ssorthash(wordlist[i]);
            }

            s1.Stop();
            Console.WriteLine("Words loaded in: " + s1.Elapsed.TotalMilliseconds + " ms");
            Console.WriteLine("Word you'd like to see anagrams for (" + wordlist.Count() + " words loaded): ");

            string newWord;

            do
            {
                newWord = Console.ReadLine();

                if (newWord != null)
                {
                    s1.Reset();
                    s1.Start();
                    newWord = newWord.ToLower();
                    StringBuilder output = new StringBuilder();
                    int w1hash = ssorthash(newWord);
                    Console.WriteLine("Anagrams for " + newWord + ":");

                    for (int x = 0; x <= hashes.GetUpperBound(0); x++)
                    {
                        if (hashes[x] == w1hash && wordlist[x] != newWord)
                        {
                            output.AppendLine(wordlist[x]);
                        }
                    }

                    s1.Stop();
                    Console.Write(output.ToString());
                    Console.WriteLine("Anagrams found in: " + s1.Elapsed.TotalMilliseconds + " ms");
                }
            } while (newWord != null);
        }
    }
}


Немного времени:
Words loaded in: 198.8951 ms
Word you'd like to see anagrams for (354985 words loaded):
lions
Anagrams for lions:
insol
isoln
linos
loins
noils
Anagrams found in: 9.4648 ms
bears
Anagrams for bears:
bares
barse
baser
besra
braes
saber
sabre
serab
Anagrams found in: 7.1672 ms


Рейтинг:
2

Thomas Daniels

C++ / Python


Здесь я не собираюсь получать бонусные баллы. Вместо этого я сделал кое-что еще: я сделал полиглота. Эта программа работает как на C++, так и на Python. Он берет слово из командной строки и дает все уникальные перестановки.

Подсветка синтаксиса Python:
#define A 0 // \
from itertools import permutations
#define B 0 // \
from sys import argv

#define C 0 // \
for w in set(permutations(argv[1])):
#define D 0 // \
	joined = ''.join(w)
#define E 0 // \
	print(joined)

#include <string>
#include <iostream>
#include <algorithm>

#define pass int main(int argc, char* argv[])  { std::string input(argv[1]); std::string input_copy(argv[1]); std::cout << input << std::endl; while (std::next_permutation(input.begin(), input.end())) { std::cout << input << std::endl; } while (std::prev_permutation(input_copy.begin(), input_copy.end())) { std::cout << input_copy << std::endl; } }

pass

Подсветка синтаксиса C++ :
#define A 0 // \
from itertools import permutations
#define B 0 // \
from sys import argv

#define C 0 // \
for w in set(permutations(argv[1])):
#define D 0 // \
	joined = ''.join(w)
#define E 0 // \
	print(joined)

#include <string>
#include <iostream>
#include <algorithm>

#define pass int main(int argc, char* argv[])  { std::string input(argv[1]); std::string input_copy(argv[1]); std::cout << input << std::endl; while (std::next_permutation(input.begin(), input.end())) { std::cout << input << std::endl; } while (std::prev_permutation(input_copy.begin(), input_copy.end())) { std::cout << input_copy << std::endl; } }

pass

Как это работает: # указывает на комментарий в Python. Это означает, что мы можем использовать #include и #define просто отлично в нашем C++. Мое первоначальное намерение состояло в том, чтобы использовать #define, чтобы заставить точный код Python работать в C++, но, к сожалению, синтаксис был слишком другим. Поэтому я хотел найти способ закомментировать весь код Python. Если вы сделаете это // комментарий в C++ и в конце линии с \, следующая строка тоже является комментарием. Но потому что // комментарии не работают в Python, мы должны сделать бесполезное #define поэтому Python игнорирует строку с комментарием.

Тогда основной метод C++ также должен быть проигнорирован в Python. Я определил pass как и весь метод. pass это оператор в Python, который ничего не делает (например, он используется в пустых операторах), что идеально подходит для этого полиглота. C++ предварительно обрабатывает 'pass' как основной метод, Python игнорирует его. У нас есть свой полиглот!


Graeme_Grant

Было много жалоб на то, что люди (в частности, я, но и другие тоже) подавали более одного решения... Убивает творчество...

Thomas Daniels

О, неужели? Я скучал по ним. Существует ли официальное правило о множественных решениях?

Graeme_Grant

За последние несколько недель возникли проблемы... Никаких новых правил, насколько мне известно...

Thomas Daniels

Хм, ладно. Лично я не очень согласен с этими жалобами, поэтому, если где-то состоится официальная дискуссия, чтобы предложить изменение консенсуса / правил, я буду рад принять в ней участие, но пока этого нет, я сохраню свое второе решение.

Graeme_Grant

Я с тобой ...

Chris Maunder

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

Graeme_Grant

Голосование тех, кто участвует, - это трудная просьба.

Graeme_Grant

с WCC в разделе Q&A он быстро похоронен с помощью beginner Q&As... Поэтому я чувствую, что он теряется. Нужен ли ему собственный раздел или виджет на боковой панели, чтобы придать ему большую известность?

Chris Maunder

Я как раз собирался спросить вас: "как мы можем сделать это более зрелищным видом спорта?".

Боковая панель или, может быть, это собственная страница, но тогда нам также нужно будет продвигать эту страницу. Много шума, с которым нужно бороться. Я открыт для идей и хотел бы, чтобы это продолжалось и росло.

Graeme_Grant

Может быть, вы проведете опрос по этому поводу... Опрос оказался довольно популярным форумом для его обсуждения...

Thomas Daniels

Может быть, липкий пост с вопросами и ответами? Как липкие посты на форумах. Это должно привлечь еще больше внимания, не беспокоясь о планировке.

Рейтинг:
1

Jörgen Andersson

Вдохновленный решением 2, но значительно быстрее

class Program
{
    private static IEnumerable<string> GetWords()
    {
        //using a local version of:
        //https://raw.githubusercontent.com/dwyl/english-words/master/words.txt

        using (StreamReader sr = File.OpenText("words.txt"))
        {
            string s = String.Empty;
            while ((s = sr.ReadLine()) != null)
            {
                if (s.Length > 0)
                {
                    yield return s;
                }
            }
        }
    }

    static Char[] ToCharArray(string Source)
    {
        char[] CharArray = Source.ToLower().ToCharArray();
        Array.Sort(CharArray);
        return CharArray;
    }

    static void Main(string[] args)
    {
        Stopwatch stopWatch = new Stopwatch();
        stopWatch.Start();

        Dictionary<Char[], List<string>> AllWords = new Dictionary<Char[], List<string>>(new CharArrayEqualityComparer());
        foreach (string word in GetWords())
        {
            List<string> WordList;
            Char[] key = ToCharArray(word);
            if (!AllWords.TryGetValue(key, out WordList))
            {
                WordList = new List<string>();
                AllWords.Add(key, WordList);
            }
            WordList.Add(word);
        }
        //ILookup<UInt16[], string> AllWords = GetWords().ToLookup(s => ToNumericArray(s), new CharArrayEqualityComparer());

        stopWatch.Stop();

        Console.WriteLine(string.Format("Lookup for {0} words loaded in {1}ms)", AllWords.Count, stopWatch.Elapsed.TotalMilliseconds));

        string CompareWord;
        do
        {
            Console.Write(string.Format("{0}Search Word: ", Environment.NewLine));
            CompareWord = Console.ReadLine();

            stopWatch.Reset();
            stopWatch.Start();

            if (CompareWord != null)
            {
                IEnumerable<string> Anagrams = AllWords[ToCharArray(CompareWord)];
                stopWatch.Stop();
                Console.WriteLine(string.Format("Time elapsed (ms): {0}", stopWatch.Elapsed.TotalMilliseconds));

                foreach (string word in Anagrams)
                {
                    Console.WriteLine(word);
                }
            }
        } while (CompareWord != null);
    }
}

internal class CharArrayEqualityComparer : EqualityComparer<Char[]>
{
    private static readonly EqualityComparer<Char> Comparer = EqualityComparer<Char>.Default;
    public override bool Equals(char[] x, char[] y)
    {
        if (x == null) return false;
        if (y == null) return false;

        if (x.Length != y.Length)
        {
            return false;
        }
        for (int i = 0; i < x.Length; i++)
        {
            if(!Comparer.Equals(x[i], y[i]))
            {
                return false;
            }
        }
        return true;
    }

    public override int GetHashCode(char[] obj)
    {
        unchecked
        {
            if (obj == null) return 0;
            int hash = 17;
            foreach (char Item in obj)
            {
                hash = (((hash << 5) - hash) ^ Comparer.GetHashCode(Item));
            }
            return hash;
        }
    }
}

И каковы результаты:
Lookup for 315379 words loaded in 515,3337ms)

Search Word: teaser
Time elapsed (ms): 0,0082
aretes
asteer
easter
eaters
reseat
saeter
seater
staree
teaser

Search Word: alerts
Time elapsed (ms): 0,0082
alerts
alters
artels
estral
laster
lastre
rastle
ratels
relast
resalt
salter
slater
staler
stelar
talers

Я подозреваю, что большая часть отмеренного времени - это запись слов на консоль.
Изменил код для измерения так же, как это делает Грэм

Мне удалось обрезать его немного дальше, пропустив строки и используя массивы int с самодельным EqualityComparer

<edit>поскольку использование поиска упрощает задачу для того, кто имеет самый быстрый алгоритм хэширования.
Я решил посмотреть, как сделать еще один шаг вперед, создавая комбинации слов в виде анаграмм. Они могут быть созданы довольно легко с помощью метода, аналогичного:
public static IEnumerable<IEnumerable<T>> CreateCombinations<T>(this IEnumerable<T> elements, int Ordinal)
{
    return Ordinal == 0 ? new[] { new T[0] } :
      elements.SelectMany((e, i) =>
        elements.Skip(i + 1).CreateCombinations(Ordinal - 1).Select(c => (new[] { e }).Concat(c)));
}
Проблема заключается только во временном аспекте создания таблицы поиска. Если требуется 1 секунда, чтобы создать поиск для N слов, то потребуется N-1 секунда, чтобы создать поиск с комбинацией всего из двух слов, с тремя словами это займет (N-1)*(N-2) секунды и так далее. Поэтому я решил дать ему отдохнуть, пока не придумаю более быстрый способ создания таблицы поиска</edit>

<edit2>Просто чтобы доказать, что нет никакой разницы в производительности поиска, я создал замену dropin для ILookup с помощью словаря. В результате производительность загрузки стала значительно лучше, так как он больше не использует linq.
Я также обрезал equalityComparer, чтобы он больше не содержал никаких умножений. </edit2>

<edit2>понял, что я могу использовать EqualityComparer<char> внутри EqualityComparer, который я использую для словаря.
Теперь мне не нужно копировать массив</edit2>


Graeme_Grant

Нет смысла включать то, что не имеет значения... Я мог бы сократить время еще больше, но это уже очень быстро. ;)

Graeme_Grant

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

stopWatch.Start();
IEnumerable<string> Anagrams = AllWords[ToNumericArray(CompareWord)];
stopWatch.Stop();
Вы повторяете через остаток после вы останавливаете секундомер...

Чтобы справедливо рассчитать сроки, вам нужно сделать следующее:
stopWatch.Start();
var Anagrams = AllWords[ToNumericArray(CompareWord)].ToList();
stopWatch.Stop();
Теперь все элементы возвращаются в пределах времени, а не только первый.

Существует также ошибка расчета времени при загрузке слов с диска:
List<string> Words = GetWords().ToList(); // words loaded here!

Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();

ILookup<UInt16[], string> AllWords = Words.ToLookup(s => ToNumericArray(s), new UInt16ArrayEqualityComparer());
stopWatch.Stop();
Должно быть:
Stopwatch stopWatch = new Stopwatch();

stopWatch.Start();
List<string> Words = GetWords().ToList(); // words loaded here!
ILookup<UInt16[], string> AllWords = Words.ToLookup(s => ToNumericArray(s), new UInt16ArrayEqualityComparer());
stopWatch.Stop();
Возможно, вы захотите пересчитать эти тайминги...

Jörgen Andersson

Просто потому, что что-то реализует IEnumerable, это не значит, что это метод ленивой загрузки итератора.
Угрозу безопасность компьютера и реализуется классом подстановки (https://referencesource.microsoft.com/#System.Core/System/Linq/Parallel/Utils/Lookup.cs,41)
И когда вы делаете поиск против поиска :-), он возвращает Igrouping. Который реализуется группировкой.(https://referencesource.microsoft.com/#System.Core/System/Linq/Enumerable.cs,7bb231e0604c79e3)
Который реализует, подождите его, Илист. Внутренне он реализован с использованием массива, но это относится и к списку.
В любом случае, код на самом деле просто передает ссылку после поиска по словарю, точно так же, как вы делаете это в своем коде.

Что приводит нас к другому, который является простым ой!
Я экспериментировал с некоторыми вещами и забыл вернуть их в прежнее состояние. (Что вы можете найти, если сравните версии решения)
Загрузка исправлений и новых таймингов занимает всего пару минут.

Graeme_Grant

Я понимаю, откуда вы идете, но я провел некоторое исследование и наблюдал за работой кода перед публикацией. Однако, когда вы делаете это изменение, вы можете видеть, что есть разница. Ваши тайминги все еще не рассчитаны правильно.

Jörgen Andersson

Конечно, будет разница, если вы скопируете элементы из IList в новый список. Тот факт, что вы добавляете время, если делаете ненужную работу, ничего не значит.
Итак, объясните правильно, как код измеряет больше ошибок, чем ваш.
Мы оба передаем ссылку, из словаря!

Graeme_Grant

Сдался на милость ILookup и наконец присоединился ко мне с Dictionary класс. Сейчас наши решения выглядят почти одинаково. ;)

Graeme_Grant

Я запустил ваш новый код и обнаружил проблему. Если слово не найдено оно выбрасывает Исключения keynotfoundexception Попробуйте "Логизомеханофобию" ... это то, что есть у моего отца... ;)

Рейтинг:
1

Graeme_Grant

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

using System;
using System.Collections.Generic;
using System.Data.SQLite;
using System.IO;

static class SqLiteLookup
{
    public static IEnumerable<string> Find(string lookupWord)
    {
        var chars = lookupWord.ToCharArray();
        Array.Sort(chars);

        using (var cmd =
            new SQLiteCommand($"SELECT word FROM {tableName} WHERE '" +
                new string(chars) + "' = wordkey", dbConn))
        using (SQLiteDataReader coreReader = cmd.ExecuteReader())
            while (coreReader.Read())
                yield return coreReader[0].ToString();
    }

    static SQLiteConnection dbConn;
    static SQLiteTransaction dbTrans;
    static string wordFile, dbFile = "words.db",
        conn, tableName = "WordLookup",
        createCmd = $"CREATE TABLE {tableName} (wordkey TEXT, word TEXT)",
        insertCmd = $"INSERT INTO {tableName} (wordkey, word) values (@wordkey, @word)";

    public static void InitDB(string file)
    {
        wordFile = file;
        CheckAndOpenDb(dbFile);
        CheckAndCreateTable();
    }

    private static void CheckAndOpenDb(string file)
    {
        conn = $"Data Source={file};Version=3;DateTimeKind=Utc;" +
                "FullUri=file::memory:?cache=shared;";

        if (!File.Exists(file))
            SQLiteConnection.CreateFile(file);

        dbConn = new SQLiteConnection(conn);
        dbConn.Open();
    }

    private static void CheckAndCreateTable()
    {
        if (!TableExists(tableName))
        {
            ExecuteCommand(createCmd);
            LoadWords();
        }
    }

    static void LoadWords()
    {
        string word, key;

        BeginTrans();
        using (StreamReader sr = File.OpenText(wordFile))
        {
            while ((word = sr.ReadLine()) != null)
            {
                if (word.Length > 0 &&
                    !word.Contains("'") &&
                    !word.Contains("\""))
                {
                    key = word.GetKey();
                    ExecuteCommand(insertCmd,
                        new Dictionary<string, object>
                        {
                                { "@wordkey", key },
                                { "@word", word }
                        });
                }
            }
        }
        CommitTrans();
    }


    static bool ExecuteCommand
        (string query, Dictionary<string, object> fields = null)
    {
        bool success = false;
        using (SQLiteCommand cmd = Command(query))
        {
            if (fields != null && fields.Count != 0)
                foreach (var key in fields.Keys)
                    cmd.Parameters.AddWithValue(key, fields[key]);

            cmd.ExecuteNonQuery();
            success = true;
        }
        return success;
    }

    static SQLiteCommand Command(string sql)
        => new SQLiteCommand(sql, dbConn);

    static bool TableExists(string name)
        => Command($"SELECT name FROM sqlite_master WHERE type='table' AND name='{name}'")
               .ExecuteReader()
               .HasRows;

    static void BeginTrans()
    {
        dbTrans = dbConn.BeginTransaction();
    }

    static void CommitTrans()
    {
        dbTrans.Commit();
    }
}
Использование одного и того же генератора ключей:
using System;

static class KeyGen
{
    public static string GetKey(this string word)
    {
        var chars = word.ToCharArray();
        Array.Sort(chars);
        return new string(chars);
    }
}
Код для его запуска:
using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace Anagrams
{
    static class Program
    {
        static string filename = "words.txt";
        static void Main()
        {
            var st = new Stopwatch();
            var elapsed = default(TimeSpan);
            IEnumerable<string> anagrams = null;

            Console.WriteLine("Starting SQLite...");
            st.Start();
            SqLiteLookup.InitDB(filename);
            elapsed = st.Elapsed;
            st.Stop();
            Console.WriteLine($"Time: {elapsed.TotalMilliseconds} ms\r\n");

            while (true)
            {
                st.Reset();
                Console.Write("\r\nSearch Word: ");

                int count = 0;

                var lookupWord = Console.ReadLine().Trim().ToLower();
                if (lookupWord.Length == 0) break;

                st.Restart();
                anagrams = SqLiteLookup.Find(lookupWord);
                elapsed = st.Elapsed;

                foreach (var anagram in anagrams)
                    Console.WriteLine($"{++count,2}: {anagram}");

                Console.WriteLine($"\r\nTime: {elapsed.TotalMilliseconds} ms");
                Console.WriteLine($"{count:N0} anagrams for {lookupWord}");
            }
        }
    }
}
И данные word из того же ресурса: GitHub - dwyl/английский-слова[^]

Выходы:
Starting SQLite...
Time: 193.3068 ms


Search Word: teaser
 1: aretes
 2: asteer
 3: easter
 4: eaters
 5: reseat
 6: saeter
 7: seater
 8: staree
 9: teaser

Time: 0.0003 ms
9 anagrams for teaser


Рейтинг:
0

Graeme_Grant

Как и некоторые другие, я использовал таблицу поиска, но с изюминкой. Один Dictionary с помощью специализированного ключа группировки.

Ключ находится в <Tuple<int, long, long> состоит из длины слов и пары оснований 26 Longs. поскольку в английских словах есть 26 символов, я разделил строчный алфавит на половинки - от a до l (от 0 до 12) до нижнего & m до z (от 13 до 26) до верхнего.

То Find метод запускается в миллисекундах, почти мгновенно, со списком из более чем 350 000 слов. Длина слова не влияет на производительность.


using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using ZeroFormatter;

namespace BuildLookupFile
{
    static class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Anagram Lookup File Builder");
            Console.WriteLine("===========================");

            var helper = new AnagramsHelper();
            var sw = new Stopwatch();

            Console.WriteLine("\r\nLoading & building Lookup Table...");

            sw.Start();
            helper.LoadWords("words.txt");
            var elapsed = sw.Elapsed;

            Console.WriteLine("* Loaded & processed in {0:N6} seconds",
                elapsed.TotalMilliseconds / 1000);
            Console.WriteLine("\r\nStatistics");
            Console.WriteLine("----------");

            Console.WriteLine("* {0:N0} words", helper.count);

            Console.WriteLine("\r\nSaving to file...");

            sw.Restart();
            helper.SaveTable("words.bin");
            elapsed = sw.Elapsed;

            Console.WriteLine("* Write to disk took in {0:N6} seconds",
                elapsed.TotalMilliseconds / 1000);
        }
    }
    public class AnagramsHelper
    {
        public int count { get; private set; }

        private Dictionary<string, List<string>> LookupTable
            = new Dictionary<string, List<string>>();

        public void LoadWords(string filename)
        {
            var wordKeys = new Dictionary<string, int>();
            string word, key;
            List<string> wordList = null;

            using (StreamReader sr = File.OpenText(filename))
            {
                while ((word = sr.ReadLine()) != null)
                {
                    if (word.Length > 0 && !word.Contains("'") && !word.Contains("\""))
                    {
                        key = word.GetKey();

                        if (!LookupTable.ContainsKey(key))
                        {
                            wordList = new List<string>();
                            LookupTable.Add(key, wordList);
                        }
                        else
                        {
                            wordList = LookupTable[key];
                        }

                        count++;
                        wordList.Add(word);
                    }
                }
            }
        }

        internal void SaveTable(string filename)
        {
            using (var fs = File.OpenWrite(filename))
                LookupTable.Serialize(fs);
        }
    }

    public static class HelperExtensions
    {
        public static string GetKey(this string word)
        {
            var chars = word.ToCharArray();
            Array.Sort(chars);
            return new string(chars);
        }

        public static void Serialize(this Dictionary<string, List<string>> data, Stream stream)
        {
            var writer = new BinaryWriter(stream);
            writer.Write(ZeroFormatterSerializer.Serialize(data));
            writer.Flush();
        }
    }
}

Imports System.IO
Imports System.Runtime.CompilerServices
Imports ZeroFormatter
Module Module1

    Sub Main()

        Console.WriteLine("Anagram Lookup File Builder")
        Console.WriteLine("===========================")

        Dim helper = New AnagramsHelper()
        Dim sw = New Stopwatch()

        Console.WriteLine(vbCr & vbLf & "Loading & building Lookup Table...")

        sw.Start()
        helper.LoadWords("words.txt")
        Dim elapsed = sw.Elapsed

        Console.WriteLine("* Loaded & processed in {0:N6} seconds", elapsed.TotalMilliseconds / 1000)
        Console.WriteLine(vbCr & vbLf & "Statistics")
        Console.WriteLine("----------")

        Console.WriteLine("* {0:N0} words", helper.count)

        Console.WriteLine(vbCr & vbLf & "Saving to file...")

        sw.Restart()
        helper.SaveTable("words.bin")
        elapsed = sw.Elapsed

        Console.WriteLine("* Write to disk took in {0:N6} seconds", elapsed.TotalMilliseconds / 1000)
    End Sub

End Module

Public Class AnagramsHelper

    Public Property count() As Integer
    Private LookupTable As New Dictionary(Of String, List(Of String))()

    Public Sub LoadWords(filename As String)

        Dim word As String
        Dim key As String
        Dim wordList As List(Of String)

        Using sr As StreamReader = File.OpenText(filename)
            Do
                word = sr.ReadLine()
                If word?.Length > 0 AndAlso
                   Not word.Contains("'") AndAlso
                   Not word.Contains("""") Then
                    key = word.GetKey()
                    If Not LookupTable.ContainsKey(key) Then
                        wordList = New List(Of String)()
                        LookupTable.Add(key, wordList)
                    Else
                        wordList = LookupTable(key)
                    End If
                    count += 1
                    wordList.Add(word)
                End If
            Loop While word IsNot Nothing
        End Using

    End Sub

    Friend Sub SaveTable(filename As String)
        Using fs = File.OpenWrite(filename)
            LookupTable.Serialize(fs)
        End Using
    End Sub

End Class

Public Module HelperExtensions

    <Extension>
    Public Function GetKey(word As String) As String
        Dim chars = word.ToCharArray()
        Array.Sort(chars)
        Return New String(chars)
    End Function

    <Extension>
    Public Sub Serialize(data As Dictionary(Of String, List(Of String)), stream As Stream)
        Dim writer = New BinaryWriter(stream)
        writer.Write(ZeroFormatterSerializer.Serialize(data))
        writer.Flush()
    End Sub

End Module


И вот каждый выходной:
Ultra-Fast Anagram Generator
============================

Word list supplied by: https://github.com/dwyl/english-words

Loading & building Lookup Table...

Statistics
----------
* 351,096 words
* Loaded & processed in 7.4770 seconds
* Largest group of Anagrams: 15
* Longest word in Lookup Table:
  - dichlorodiphenyltrichloroethane

Search Word: teaser

FOUND 9 anagrams for "teaser"

    aretes
    asteer
    easter
    eaters
    reseat
    saeter
    seater
    staree
    teaser

Time: 0.000072 seconds

Search Word: alerts

FOUND 15 anagrams for "alerts"

    alerts
    alters
    artels
    estral
    laster
    lastre
    rastle
    ratels
    relast
    resalt
    salter
    slater
    staler
    stelar
    talers

Time: 0.000072 seconds

ОБНОВЛЕНИЕ: Я обнаружил улучшение производительности за счет упрощения. Положительная сторона заключается в том, что время загрузки улучшается на 300%, А время отклика-более чем на 200%.

Вот пересмотренные результаты:
Ultra-Fast Anagram Generator
============================

Word list supplied by: https://github.com/dwyl/english-words

Loading & building Lookup Table...

Statistics
----------
* 351,096 words
* Loaded & processed in 2.5293 seconds
* Largest group of Anagrams: 15
* Longest word in Lookup Table:
  - dichlorodiphenyltrichloroethane

Search Word: teaser

FOUND 9 anagrams for "teaser"

    aretes
    asteer
    easter
    eaters
    reseat
    saeter
    seater
    staree
    teaser

Time: 0.000039 seconds

Search Word: alerts

FOUND 15 anagrams for "alerts"

    alerts
    alters
    artels
    estral
    laster
    lastre
    rastle
    ratels
    relast
    resalt
    salter
    slater
    staler
    stelar
    talers

Time: 0.000034 seconds

Обновление #2.1: Теперь, когда поиск выполняется молниеносно, следующее узкое место производительности, которое необходимо устранить, - это загрузка таблицы поиска. PIEBOLDconsult использует базу данных SQL, поэтому я решил предварительно построить таблицу поиска.

Самым большим узким местом, возникшим при сохранении и загрузке двоичной версии таблицы поиска, была платформа .Net BinaryFormatter уик потратил более 61 секунды (лучшее время) на десериализацию двоичного потока. Итак, немного поискав, я нашел ZeroFormatter[^]. Время загрузки уже наступило уменьшено с 2,5293 секунды до а молниеносно на 0,534043 секунды - более чем на 500% быстрее и более 1400% быстрее чем оригинал!

Наконец, тоже сжал бэтэр производительности поиска. Теперь уменьшено с 0,034 МС до 0,013 МС или улучшение на 261%, и более чем на 550% быстрее, чем оригинал!

Вот код для построения двоичного файла:


using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using ZeroFormatter;

namespace BuildLookupFile
{
    static class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Anagram Lookup File Builder");
            Console.WriteLine("===========================");

            var helpe


Jörgen Andersson

Действительно ли арест-это анаграмма для тизера?
Арест имеет два R, а тизер-два E.

Graeme_Grant

ха... нужно посмотреть на эту... небольшую заминку...

Graeme_Grant

Обновленный.

Jörgen Andersson

Вероятно, вы можете немного ускорить решение, приняв 32 буквы в алфавите и используя битовый сдвиг.

Graeme_Grant

Спасибо за предложение. Я действительно думал об этом, но мне нужен граф того же письма. Я думаю о другом способе, который, скорее всего, будет и попробую, когда у меня будет время...

Graeme_Grant

На самом деле я покончил с вычисляемым ключом кортежа и перешел на простое использование хэш - кода-гораздо быстрее! теперь до 0,013 МС с 0,074 МС. Но это на моем 4-летнем MBP. Я уверен, что это будет быстрее на более современном ПК... :)

Jörgen Andersson

Это быстрее на более новом компьютере, мой и ваше решение имеет почти такую же скорость на моем компьютере.
Но они должны были иметь, важные части-это почти тот же самый код.
Не только поиск по словарю, но и сравнение вашего GetKey с первой версией моего решения. Он отличается только частью GetHashCode ().

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

GetHashCode является значение типа int32 кстати, так что если вы проходите долго словаре, оно будет вычислять хэш-код как (непроверенные((тип int)((длинные)m_value)) ^ (int)(m_value >> 32)) вместо того, чтобы просто пропустить его, что было бы в случае с Int32.

Graeme_Grant

Мне пришлось удалить код Linq, чтобы повысить производительность. Long-это то, что осталось от предыдущего кода. изменение его на Int32 удалит больше накладных расходов (хотя и очень незначительных) и еще больше сократит использование памяти и хранилища.

Что касается скорости, то сейчас мы проводим измерения, которые нуждаются в другом методе, чтобы получить лучшую точность. ;)

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

[edit] нашел, где можно было бы сделать еще один выигрыш perf, и теперь он снизился до 0,0011 МС на моем старом MBP. ;)

Рейтинг:
0

Midi_Mick

Прежде всего, я использую Алгоритм кучи[^] чтобы сгенерировать все возможные перестановки введенных случайных букв, и я помещаю эти перестановки в хэш-набор.
Затем, вместо того чтобы проверять каждую перестановку, чтобы увидеть, является ли это словом, я решил, что словарь все равно должен быть загружен, так почему бы не проверить, подходит ли загружаемое слово? Таким образом, я вообще не загружаю словарь в память - я просто повторяю слова в словаре, которые находятся в списке перестановок. Я использовал то же самое words.txt откуда GitHub - dwyl/english-words: текстовый файл, содержащий 479 тысяч английских слов для быстрого поиска автозавершения / самовнушения.[^] используется в решении 2 для моего словаря.

class Program {
    static HashSet<string> _permutations = new HashSet<string>();
    static void GeneratePermutations(int n, char[] letters) {
        if (n == 1)
            _permutations.Add(new string(letters));
        else {
            for (int i = 0; i < n - 1; ++i) {
                GeneratePermutations(n - 1, letters);
                int swapIndex = (n & 1) == 0 ? i : 0;
                char c = letters[swapIndex];
                letters[swapIndex] = letters[n - 1];
                letters[n - 1] = c;
            }
            GeneratePermutations(n - 1, letters);
        }
    }

    static void ListPermutations() {
        Console.WriteLine("\nAll Permutations:");
        foreach (string permutation in _permutations)
            Console.WriteLine(permutation);
    }

    static void FindWords() {
        Console.WriteLine("\nWords:");
        using (StreamReader fp = File.OpenText(Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "words.txt"))) {
            string word;
            while ((word = fp.ReadLine()) != null) {
                if (_permutations.Contains(word)) {
                    Console.WriteLine(word);
                }
            }
        }
    }

    static void Main(string[] args) {
        string letters = null;
        if ((args?.Length ?? 0) > 0)
            letters = args[0];
        char action;
        do {
            while (string.IsNullOrEmpty(letters)) {
                Console.Write("Enter random letters: ");
                letters = Console.ReadLine().ToLower();
                if (letters.Any(c => !char.IsLetter(c)))
                    letters = null;
            }
            _permutations.Clear();
            GeneratePermutations(letters.Length, letters.ToCharArray());
            do {
                Console.WriteLine("1. List all permutations");
                Console.WriteLine("2. Find Words");
                Console.WriteLine("3. Restart");
                Console.WriteLine("0. Exit");
                action = Console.ReadKey(false).KeyChar;
                if (action == '1')
                    ListPermutations();
                else if (action == '2')
                    FindWords();
            } while (action != '3' && action != '0');
            letters = null;
        } while (action != '0');
    }
}


PIEBALDconsult

С помощью хэш-набора вы теряете счет тому, сколько у вас букв, не так ли?

Midi_Mick

Нет = хэш-набор содержит все возможные перестановки, но только по одному разу каждая - он удаляет дубликаты, вызванные удвоением букв. Например, если ввести 5 различных символов, то получится 120 перестановок. Однако если 2 из этих букв одинаковы, то из 120 фактических перестановок различны только 60 - каждая последовательность букв будет сгенерирована дважды (один раз для Буквы в 1-й позиции и один раз для буквы во 2-й позиции). Хэш-набор удаляет эти дубликаты.

PIEBALDconsult

Ах да, мой мозг устал.

Рейтинг:
0

Mohibur Rashid

Баш безумие

string=life
string_list=($(egrep $((eval echo $(for x in $(seq ${#string}); do printf "{"$(echo $string | sed -e 's/\(.\)/\1,/g' | sed -e 's/,$//')"}"; done)) | perl -e 'chomp($line=<>); $line="^$line\$"; $line =~ s/ /\$|\^/g;print "($line)\n";') /usr/share/dict/words))

sorted_string=$(echo $string | grep -o . | sort | tr -d "\n")
for x in ${string_list[@]}; do
    if echo ${x} | grep -o . | sort | tr -d "\n" | grep ${sorted_string} > /dev/null 2>&1; then
      echo ${x}
    fi
done


выход:
feil
fiel
file
lief
life