Подтвердить что ты не робот

Алгоритм группировки слов анаграммы

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

ввод:

man car kile arc none like

выход:

man
car arc
kile like
none

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

Пример: man = > 'm' + 'a' + 'n', но это не даст уникальных значений.

Любое предложение?


Смотрите следующий код на С#:

string line = Console.ReadLine();
string []words=line.Split(' ');
int[] numbers = GetUniqueInts(words);
for (int i = 0; i < words.Length; i++)
{
    if (table.ContainsKey(numbers[i]))
    {
        table[numbers[i]] = table[numbers[i]].Append(words[i]);
    }
    else
    {
        table.Add(numbers[i],new StringBuilder(words[i]));
    }

}

Проблема заключается в том, как разработать метод GetUniqueInts(string []).

4b9b3361

Ответ 1

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

Просто введите хэш из "отсортированного слова" в "список слов для этого отсортированного слова". В LINQ это невероятно просто:

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

class FindAnagrams
{
    static void Main(string[] args)
    {
        var lookup = args.ToLookup(word => SortLetters(word));

        foreach (var entry in lookup)
        {
            foreach (var word in entry)
            {
                Console.Write(word);
                Console.Write(" ");
            }
            Console.WriteLine();
        }
    }

    static string SortLetters(string original)
    {
        char[] letters = original.ToCharArray();
        Array.Sort(letters);
        return new string(letters);
    }
}

Использование примера:

c:\Users\Jon\Test>FindAnagrams.exe man car kile arc none like
man
car arc
kile like
none

Ответ 2

Я использовал схему, вдохновленную Гёдель:

Назначьте простые числа P_1 в P_26 буквам (в любом порядке, но для получения небольших значений хэша лучше всего давать простые буквы небольших простых чисел).

Построена гистограмма букв в слове.

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

Код Python:

primes = [2, 41, 37, 47, 3, 67, 71, 23, 5, 101, 61, 17, 19, 13, 31, 43, 97, 29, 11, 7, 73, 83, 79, 89, 59, 53]


def get_frequency_map(word):
    map = {}

    for letter in word:
        map[letter] = map.get(letter, 0) + 1

    return map


def hash(word):
    map = get_frequency_map(word)
    product = 1
    for letter in map.iterkeys():
        product = product * primes[ord(letter)-97] ** map.get(letter, 0)
    return product

Это умно превращает сложную задачу нахождения субанаграмм в (также известную как сложную) проблему факторизации больших чисел...

Ответ 3

Версия Python для хихиканья:

from collections import defaultdict
res = defaultdict(list)
L = "car, acr, bat, tab, get, cat".split(", ")

for w in L:
    res["".join(sorted(w))].append(w)

print(res.values())

Ответ 4

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

Сумма букв никогда не будет работать, потому что вы не можете отличить "ac" и "bb".

Ответ 5

Вам понадобятся большие целые числа (или бит-векторы на самом деле), но следующее может работать

первое вхождение каждой буквы получает номер бит для этой буквы, второе вхождение получает номер бит для этой буквы + 26.

Например

a # 1 = 1 b # 1 = 2 С# 1 = 4 a # 2 = 2 ^ 26 b # 2 = 2 ^ 27

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

Требования к хранению для значений слова будут следующими:

n * 26 бит

где n - максимальное количество вхождений любой повторяющейся буквы.

Ответ 6

Я бы не использовал хэширование, поскольку он добавляет дополнительную сложность для поиска и добавления. Хеширование, сортировка и умножение будут медленнее, чем простое решение на основе массивов с отслеживанием уникальности. Наихудший случай - O (2n):

// structured for clarity
static bool isAnagram(String s1, String s2)
{
    int[] histogram = new int[256];

    int uniques = 0;

    // scan first string
    foreach (int c in s1)
    {
        // count occurrence
        int count = ++histogram[c];

        // count uniques
        if (count == 1)
        {
            ++uniques;
        }
    }

    // scan second string
    foreach (int c in s2)
    {
        // reverse count occurrence
        int count = --histogram[c];

        // reverse count uniques
        if (count == 0)
        {
            --uniques;
        }
        else if (count < 0) // trivial reject of longer strings or more occurrences
        {
            return false;
        }
    }

    // final histogram unique count should be 0
    return (uniques == 0);
}

Ответ 7

Я реализовал это раньше с помощью простого массива букв, например:

unsigned char letter_frequency[26];

Затем сохраните это в таблице базы данных вместе с каждым словом. Слова, которые имеют одну и ту же букву "подпись", являются анаграммами, а простой SQL-запрос возвращает все анаграммы слова напрямую.

При некоторых экспериментах с очень большим словарем я не нашел ни одного слова, которое превышало бы частоту, равную 9 для любой буквы, поэтому "подпись" может быть представлена ​​как строка чисел 0..9 (размер может быть легко уменьшилось вдвое, упаковав в байты в виде шестнадцатеричного кода, а затем уменьшилось с помощью двоичного кодирования номера, но до сих пор я не беспокоился об этом).

Вот рубиновая функция для вычисления подписи данного слова и сохранения ее в хеш, в то же время отбрасывая дубликаты. Из Hash я позже построил таблицу SQL:

def processword(word, downcase)
  word.chomp!
  word.squeeze!(" ") 
  word.chomp!(" ")
  if (downcase)
    word.downcase!
  end
  if ($dict[word]==nil) 
    stdword=word.downcase
    signature=$letters.collect {|letter| stdword.count(letter)}
    signature.each do |cnt|
      if (cnt>9)
        puts "Signature overflow:#{word}|#{signature}|#{cnt}"
      end
    end
    $dict[word]=[$wordid,signature]
    $wordid=$wordid+1
  end
end

Ответ 8

Назначьте уникальное простое число буквам a-z

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

Сортировка массива по возрастанию продукта.

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

Ответ 9

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

#define BUCKETS 49999

struct bucket {
    char *word;
    struct bucket *next;
};

static struct bucket hash_table[BUCKETS];

static unsigned int hash_word(char *word)
{
    char *p = word;
    unsigned int hash = 0;

    while (*p) {
        if (*p < 97 || *p > 122) {
            return 0;
        }
        hash |= 2 << (*p - 97);
        *p++;
    }

    return hash % BUCKETS;
}

Перегруженные ведра, созданные и добавленные в виде связанного списка, и т.д. Затем просто напишите функцию, которая гарантирует, что слова, которые соответствуют хеш-значению, имеют одинаковую длину и что буквы в каждом имеют от 1 до 1 и возвращают это как совпадение.

Ответ 10

Я создам hasmap, основанный на образцовом слове, и остальные алфавиты, которые мне все равно.

Например, если слово "car" моя хэш-таблица будет выглядеть так: а, 0 б, MAX с, 1 д, MAX е, MAX ... .. г, 2 , В результате любой из них более 3 будет рассматриваться как несоответствие

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

public static HashMap<String, Integer> getHashMap(String word) {
        HashMap<String, Integer> map = new HashMap<String, Integer>();
        String[] chars = word.split("");
        int index = 0;
        for (String c : chars) {
            map.put(c, index);
            index++;
        }
        return map;
    }

    public static int alphaHash(String word, int base,
            HashMap<String, Integer> map) {
        String[] chars = word.split("");
        int result = 0;
        for (String c : chars) {
            if (c.length() <= 0 || c.equals(null)) {
                continue;
            }
            int index = 0;
            if (map.containsKey(c)) {
                index = map.get(c);
            } else {
                index = Integer.MAX_VALUE;
            }
            result += index;
            if (result > base) {
                return result;
            }
        }
        return result;
    }

Основной метод

  HashMap<String, Integer> map = getHashMap(sample);
        int sampleHash = alphaHash(sample, Integer.MAX_VALUE, map);
        for (String s : args) {
                if (sampleHash == alphaHash(s, sampleHash, map)) {
                    System.out.print(s + " ");
                }
            }

Ответ 11

Анаграммы можно найти следующим образом:

  • Длина слова должна совпадать.
  • Выполните добавление каждого символа в терминах целочисленного значения. Эта сумма будет соответствовать, если вы выполните ее на анаграмме.
  • Выполнить умножение каждого символа в терминах целочисленного значения. Оцененное значение будет соответствовать, если вы выполните его на анаграмме.

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


Пример: abc cba

Длина обоих слов равна 3.

Сумма отдельных символов для обоих слов равна 294.

Прод отдельных символов для обоих слов - 941094.

Ответ 12

версия JavaScript. используя хеширование.

Сложность времени: 0 (нм), где n - количество слов, m - длина слова

var words = 'cat act mac tac ten cam net'.split(' '),
    hashMap = {};

words.forEach(function(w){
    w = w.split('').sort().join('');
    hashMap[w] = (hashMap[w]|0) + 1;
});

function print(obj,key){ 
    console.log(key, obj[key]);
}

Object.keys(hashMap).forEach(print.bind(null,hashMap))

Ответ 13

Просто добавьте простое решение python в дополнение к другим полезным ответам:

def check_permutation_group(word_list):
    result = {}

    for word in word_list:
        hash_arr_for_word = [0] * 128  # assuming standard ascii

        for char in word:
            char_int = ord(char)
            hash_arr_for_word[char_int] += 1

        hash_for_word = ''.join(str(item) for item in hash_arr_for_word)

        if not result.get(hash_for_word, None):
            result[str(hash_for_word)] = [word]
        else:
            result[str(hash_for_word)] += [word]

return list(result.values())