Найдите слово с наибольшим количеством общих букв с другими словами

Я хочу, чтобы Perl (5.8.8) определял, какое слово имеет больше всего общих букв с другими словами в массиве, но только те буквы, которые находятся в одном и том же месте. (И желательно без использования libs.)

Возьмите этот список слов в качестве примера:

  • ПЕКАРЬ
  • ПРОДАВЕЦ
  • БАЛЕР
  • СПЕЦИАЛИСТ
  • РУФФР

Ее БАЛЕР — это слово, которое имеет больше всего общих букв с другими. Он соответствует BAxER в BAKER, xALER в SALER, xAxER в CARER и xxxxR в RUFFR.

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

Что я пробовал до сих пор

На данный момент не так много сценария:

use strict;
use warnings; 
my @wordlist = qw(BAKER SALER MALER BARER RUFFR);
foreach my $word (@wordlist) {
    my @letters = split(//, $word);
    # now trip trough each iteration and work magic...
}

Там, где комментарий, я пробовал несколько видов кода, насыщенных циклами for и переменными ++. До сих пор ни одна из моих попыток не сделала то, что мне нужно.

Итак, чтобы лучше объяснить: мне нужно проверить слово в слово по списку, для каждой позиции буквы, чтобы найти слово, которое имеет больше всего общих букв с другими в списке, в позиции этой буквы.< /сильный>

Один из возможных способов может состоять в том, чтобы сначала проверить, какое слово (слова) имеет больше всего общего в позиции буквы 0, затем проверить позицию буквы 1 и так далее, пока вы не найдете слово, которое в сумме имеет больше всего общих букв с другие слова в списке. Затем я хотел бы напечатать список в виде матрицы с оценками для каждой позиции букв плюс общий балл для каждого слова, мало чем отличающийся от того, что предлагает DavidO.

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

Цель программы

Хе-хе, я мог бы также сказать: программа предназначена для взлома терминалов в игре Fallout 3. :D Я думаю, что это отличный способ выучить Perl, одновременно получая удовольствие от игры.

Вот одно из руководств по взлому терминала Fallout 3, которое я использовал для исследования: FALLOUT 3: Hacking FAQ v1.2, и я уже сделал программу для сокращения списка слов, например:

#!/usr/bin/perl
# See if one word has equal letters as the other, and how many of them are equal
use strict;
use warnings; 

my $checkword = "APPRECIATION"; # the word to be checked
my $match = 4; # equal to the match you got from testing your checkword
my @checkletters = split(//, $checkword); #/

my @wordlist = qw(
    PARTNERSHIPS
    REPRIMANDING
    CIVILIZATION
    APPRECIATION
    CONVERSATION
    CIRCUMSTANCE
    PURIFICATION
    SECLUSIONIST
    CONSTRUCTION
    DISAPPEARING
    TRANSMISSION
    APPREHENSIVE
    ENCOUNTERING
);

print "$checkword has $match letters in common with:\n";

foreach my $word (@wordlist) {
    next if $word eq $checkword;
    my @letters = split(//, $word);
    my $length = @letters; # determine length of array (how many letters to check)

    my $eq_letters = 0; # reset to 0 for every new word to be tested
    for (my $i = 0; $i < $length; $i++) {
        if ($letters[$i] eq $checkletters[$i]) {
            $eq_letters++;
        }
    }
    if ($eq_letters == $match) {
        print "$word\n";
    }
}
# Now to make a script on to find the best word to check in the first place...

Этот скрипт даст в результате CONSTRUCTION и TRANSMISSION, как и в FAQ по игре. Однако трюк с исходным вопросом (и то, что мне не удалось выяснить самостоятельно) заключается в том, как найти лучшее слово, чтобы попробовать в первую очередь, то есть APPRECIATION.

Хорошо, теперь я предложил собственное решение, основанное на вашей помощи, и считаю эту тему закрытой. Большое-большое спасибо всем участникам. Вы очень помогли, и по пути я многому научился. :D


person Kebman    schedule 10.07.2011    source источник
comment
Можете ли вы показать нам свой сценарий, чтобы у нас было что продолжить?   -  person Flimzy    schedule 10.07.2011
comment
Возможно, самый простой способ решить эту проблему — вычислить расстояние Хэмминга между словами. Однако мне интересно, может ли он сравнивать только два и два слова...   -  person Kebman    schedule 04.05.2012
comment
Я просмотрел FAQ по хакерству, и мне кажется, что это prolog задача, применимая к perl.   -  person Patrick J. S.    schedule 05.10.2014


Ответы (8)


В качестве отправной точки вы можете эффективно проверить, сколько букв они имеют вместе:

$count = ($word1 ^ $word2) =~ y/\0//;

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

use strict;
use warnings;
my @words = qw/
    BAKER
    SALER
    BALER
    CARER
    RUFFR
/;

# you want a hash to indicate which letters are present how many times in each position:

my %count;
for my $word (@words) {
    my @letters = split //, $word;
    $count{$_}{ $letters[$_] }++ for 0..$#letters;
}

# then for any given word, you get the count for each of its letters minus one (because the word itself is included in the count), and see if it is a maximum (so far) for any position or for the total:

my %max_common_letters_count;
my %max_common_letters_words;
for my $word (@words) {
    my @letters = split //, $word;
    my $total;
    for my $position (0..$#letters, 'total') {
        my $count;
        if ( $position eq 'total' ) {
            $count = $total;
        }
        else {
            $count = $count{$position}{ $letters[$position] } - 1;
            $total += $count;
        }
        if ( ! $max_common_letters_count{$position} || $count >= $max_common_letters_count{$position} ) {
            if ( $max_common_letters_count{$position} && $count == $max_common_letters_count{$position} ) {
                push @{ $max_common_letters_words{$position} }, $word;
            }
            else {
                $max_common_letters_count{$position} = $count;
                $max_common_letters_words{$position} = [ $word ];
            }
        }
    }
}

# then show the maximum words for each position and in total: 

for my $position ( sort { $a <=> $b } grep $_ ne 'total', keys %max_common_letters_count ) {
    printf( "Position %s had a maximum of common letters of %s in words: %s\n",
        $position,
        $max_common_letters_count{$position},
        join(', ', @{ $max_common_letters_words{$position} })
    );
}
printf( "The maximum total common letters was %s in words(s): %s\n",
    $max_common_letters_count{'total'},
    join(', ', @{ $max_common_letters_words{'total'} })
);
person ysth    schedule 10.07.2011
comment
Мне очень понравилось просеивать логику и видеть работающий пример Lingua::EN::Inflect. Но у меня есть вопрос. Теперь, когда вы знаете, сколько общих букв имеет каждое слово, как выяснить, какое слово соответствует большему количеству строк в каждой позиции столбца? Разве вам не нужно вести совокупную оценку того, сколько строк соответствует каждому столбцу? (Возможно, я слишком усложняю спецификацию). - person DavidO; 10.07.2011
comment
Lingua::EN::Inflect упрощает множественное число; более сложный пример: print inflect("NUM($_) PL_N(nation) PL_V(endorses) but PL_V(isn't) endorsed") for 0..2 - person ysth; 10.07.2011
comment
Мне очень нравится это, и особенно первая строка, так как это элегантный способ сравнить два слова. Теперь, чтобы сравнить список слово за словом, чтобы найти одно слово, которое имеет больше всего общих букв в одной и той же буквенной позиции, то есть слово, которое имеет больше всего общего с другими словами на позиции 0, затем на позиции 1 и так далее. на. Библиотека не работает на моем Mac, хотя ... - person Kebman; 10.07.2011
comment
Библиотека Lingua::EN::Inflect? Здесь это совсем не обязательно, но интересно, что значит не работает? - person ysth; 10.07.2011
comment
Вау, да! Большое спасибо! :D Единственная проблема сейчас в том, что я не понимаю половины того, что ты сделал... Я такой нуб! Библиотека просто не установлена, но опять же, это к лучшему, если вам не нужно устанавливать что-то, чтобы скрипт работал. - person Kebman; 11.07.2011
comment
@Kebman: просмотрите его по одному биту за раз; выгружать структуры данных, например. print Data::Dumper::Dumper(\%max_common_letters_words) чтобы увидеть, какие данные он собирает; спросите здесь, если какой-то конкретный бит ставит вас в тупик - person ysth; 11.07.2011
comment
Re: установка, также лучше, если вы используете проверенный код других людей вместо того, чтобы переписывать вещи (или часто, в случае правильного множественного числа, просто оставляя свой сценарий немного неправильным) :) - person ysth; 11.07.2011
comment
Использование ($word1 ^ $word2) — это изящный хак, но он будет работать только для символов ASCII. Как только ваши данные включают многобайтовые символы Unicode (например, гласные с ударением), символы в сравниваемых словах оказываются смещенными. - person Grant McLean; 11.07.2011
comment
@Grant McLean: неверно (пока Perl знает, что это символьные данные) - person ysth; 11.07.2011
comment
@ysth: я пытаюсь учиться на вашем коде (молодец), я пытался использовать HoH в своем ответе, но вы устранили каскадный цикл for (я думаю, используя HoHoA?). (1)Является ли push @{ $max_common... просто способом использовать push для хэша (это правильно «срез»)? (2) Я постоянно «терялся», думая о своей структуре данных и «где я был» (особенно во время сортировки), вы просто поправляетесь с опытом (или есть советы)? БЛАГОДАРНОСТЬ! - person Jon; 11.07.2011
comment
push @{ ... } добавляет к массиву, ссылка на который указана в блоке (который в данном случае является значением из HoH, что дает HoHoA). Он также может автооживляться, но в этом коде никогда не будет. Так что, нет, здесь нет никакого среза. Это помогает твердо помнить о структуре данных при просмотре кода; пошаговое выполнение с помощью отладчика и проверка по мере продвижения могут помочь. - person ysth; 11.07.2011
comment
@ysth: Извиняюсь, прежде чем утверждать, что трюк xor не работает с данными, отличными от ASCII, я проверил его. К сожалению, в моем тесте использовалось '\x{101}' вместо "\x{101}" — упс. - person Grant McLean; 14.07.2011

Вот один из способов. Перечитав вашу спецификацию пару раз, я думаю, что это то, что вы ищете.

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

use 5.012;
use strict;
use warnings;
use List::Util 'max';

my @words = qw/
    BAKER
    SALER
    BALER
    CARER
    RUFFR
/;

my @scores;
foreach my $word ( @words ) {
    my $score;
    foreach my $comp_word ( @words ) {
        next if $comp_word eq $word;
        foreach my $pos ( 0 .. ( length $word ) - 1 ) {
            $score++ if substr( $word, $pos, 1 ) eq substr( $comp_word, $pos, 1);
        }
    }
    push @scores, $score;
}
my $max = max( @scores );
my ( @max_ixs ) = grep { $scores[$_] == $max } 0 .. $#scores;

say "Words with most matches:";
say for @words[@max_ixs];

Это решение подсчитывает, сколько раз в столбце букв буквы каждого слова совпадают с другими словами. Так, например:

Words:     Scores:       Because:
ABC        1, 2, 1 = 4   A matched once,  B matched twice, C matched once.
ABD        1, 2, 1 = 4   A matched once,  B matched twice, D matched once.
CBD        0, 2, 1 = 3   C never matched, B matched twice, D matched once.
BAC        0, 0, 1 = 1   B never matched, A never matched, C matched once.

Это дает вам победителей ABC и ABD, каждый из которых набрал четыре позиционных матча. То есть совокупное количество раз, когда первый столбец, первая строка совпали со столбцом, первой строкой, второй, третьей и четвертой, и так далее для последующих столбцов. Возможно, его можно будет дополнительно оптимизировать и переформулировать, чтобы он был короче, но я старался, чтобы логика была довольно легко читаемой. Наслаждаться!

ОБНОВЛЕНИЕ / РЕДАКТИРОВАТЬ Я подумал об этом и понял, что, хотя мой существующий метод делает именно то, что требовал ваш первоначальный вопрос, он сделал это за время O (n ^ 2), что сравнительно медленно. Но если мы используем хеш-ключи для букв каждого столбца (одна буква на ключ) и подсчитываем, сколько раз каждая буква появляется в столбце (в качестве значения хэш-элемента), мы могли бы выполнять наши суммирования за O (1). ) раз, а наш обход списка — за время O(n*c) (где c — количество столбцов, а n — количество слов). Также есть время на настройку (создание хэша). Но у нас все еще есть большое улучшение. Вот новая версия каждой техники, а также сравнение каждой из них.

use strict;
use warnings;
use List::Util qw/ max sum /;
use Benchmark qw/ cmpthese /;

my @words = qw/
    PARTNERSHIPS
    REPRIMANDING
    CIVILIZATION
    APPRECIATION
    CONVERSATION
    CIRCUMSTANCE
    PURIFICATION
    SECLUSIONIST
    CONSTRUCTION
    DISAPPEARING
    TRANSMISSION
    APPREHENSIVE
    ENCOUNTERING
/;


# Just a test run for each solution.
my( $top, $indexes_ref );

($top, $indexes_ref ) = find_top_matches_force( \@words );
print "Testing force method: $top matches.\n";
print "@words[@$indexes_ref]\n";

( $top, $indexes_ref ) = find_top_matches_hash( \@words );
print "Testing hash  method: $top matches.\n";
print "@words[@$indexes_ref]\n";



my $count = 20000;
cmpthese( $count, {
    'Hash'  => sub{ find_top_matches_hash( \@words ); },
    'Force' => sub{ find_top_matches_force( \@words ); },
} );


sub find_top_matches_hash {
    my $words = shift;
    my @scores;
    my $columns;
    my $max_col = max( map { length $_ } @{$words} ) - 1;
    foreach my $col_idx ( 0 .. $max_col ) {
        $columns->[$col_idx]{ substr $_, $col_idx, 1 }++ 
            for @{$words};
    }
    foreach my $word ( @{$words} ) {
        my $score = sum( 
            map{ 
                $columns->[$_]{ substr $word, $_, 1 } - 1
            } 0 .. $max_col
        );
        push @scores, $score;
    }
    my $max = max( @scores );
    my ( @max_ixs ) = grep { $scores[$_] == $max } 0 .. $#scores;
    return(  $max, \@max_ixs );
}


sub find_top_matches_force {
    my $words = shift;
    my @scores;
    foreach my $word ( @{$words} ) {
        my $score;
        foreach my $comp_word ( @{$words} ) {
            next if $comp_word eq $word;
            foreach my $pos ( 0 .. ( length $word ) - 1 ) {
                $score++ if 
                    substr( $word, $pos, 1 ) eq substr( $comp_word, $pos, 1);
            }
        }
        push @scores, $score;
    }
    my $max = max( @scores );
    my ( @max_ixs ) = grep { $scores[$_] == $max } 0 .. $#scores;
    return( $max, \@max_ixs );
}

Результат:

Testing force method: 39 matches.
APPRECIATION
Testing hash  method: 39 matches.
APPRECIATION
        Rate Force  Hash
Force 2358/s    --  -74%
Hash  9132/s  287%    --

Я понимаю, что ваша первоначальная спецификация изменилась после того, как вы увидели некоторые из других предоставленных опций, и в какой-то степени это своего рода инновация, но головоломка все еще была жива в моей голове. Как видите, мой метод хеширования на 287% быстрее исходного метода. Больше удовольствия за меньшее время!

person DavidO    schedule 10.07.2011
comment
Вы абсолютно правы! Вот такие матчи мне нужны. Однако мне бы очень хотелось, чтобы версия работала с Perl версии 5.8.8. - person Kebman; 10.07.2011
comment
Удалите строку, которая говорит use 5.012;. Замените два оператора say операторами print и поставьте новую строку \n, например: print "Words with most matches:\n"; print "$_\n" for @words[@max_ixs];. Теперь у вас есть версия, которая работает на 5.8.8! Надеюсь, вы найдете ему интересное применение. Я не понял, какую проблему вы решаете с его помощью, но было забавным развлечением выяснить логику. - person DavidO; 11.07.2011
comment
Большое спасибо! :D Вот что я хочу решить: gamefaqs.com/pc/ 918428-fallout-3/faqs/54644 Теперь мне просто интересно, можете ли вы покончить с библиотекой? Или он используется по умолчанию в большинстве установок Perl? - person Kebman; 11.07.2011
comment
Единственное, что делается с помощью List::Utils, — это поиск макс. Вы можете сделать то же самое, поместив в код следующие строки (и удалив строку my $max = max(...): Прямо перед my @scores; поместите my $max = 0; Прямо перед push @scores... поместите $max = ( $score > $max ) ? $score : $max;. Наконец, удалите строку use List::Utils... Что-нибудь еще, прежде чем мой ответ может удовлетворить ваши потребности? Развлекайся. - person DavidO; 11.07.2011
comment
Нет, чувак. Это круто! :D Теперь мне просто нужно попробовать разные способы сделать это, чтобы узнать, как это работает. Думаю, я бы не написал здесь, если бы не то, что мне еще предстоит многому научиться. Еще раз спасибо! :) - person Kebman; 11.07.2011
comment
Интересно, можно ли сделать это еще более эффективным с помощью троичного дерева поиска? - person Kebman; 04.08.2011

Вот полный сценарий. Он использует ту же идею, которую упомянул ysth (хотя она была у меня независимо). Используйте побитовое исключающее ИЛИ, чтобы объединить строки, а затем подсчитайте количество NUL в результате. Пока ваши строки являются ASCII, это скажет вам, сколько совпадающих букв было. (Это сравнение чувствительно к регистру, и я не уверен, что произойдет, если строки будут UTF-8. Вероятно, ничего хорошего.)

use strict;
use warnings;
use 5.010;

use List::Util qw(max);

sub findMatches
{
  my ($words) = @_;

  # Compare each word to every other word:
  my @matches = (0) x @$words;

  for my $i (0 .. $#$words-1) {
    for my $j ($i+1 .. $#$words) {
      my $m = ($words->[$i] ^ $words->[$j]) =~ tr/\0//;

      $matches[$i] += $m;
      $matches[$j] += $m;
    }
  }

  # Find how many matches in the best word:
  my $max = max(@matches);

  # Find the words with that many matches:
  my @wanted = grep { $matches[$_] == $max } 0 .. $#matches;

  wantarray ? @$words[@wanted] : $words->[$wanted[0]];
} # end findMatches

my @words = qw(
    BAKER
    SALER
    BALER
    CARER
    RUFFR
);

say for findMatches(\@words);
person cjm    schedule 10.07.2011

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

totals = new map #e.g. an object to map :key => :value

for each word a
  for each word b
    next if a equals b

    totals[a] = 0
    for i from 1 to a.length
      if a[i] == b[i]
        totals[a] += 1
      end
    end
  end
end

return totals.sort_by_key.last

Извините за отсутствие Perl, но если вы закодируете это на Perl, это должно работать как шарм.

Небольшое примечание о времени выполнения: это будет выполняться за время number_of_words^2 * length_of_words, поэтому для списка из 100 слов, каждое из которых имеет длину 10 символов, это будет выполняться за 100 000 циклов, что достаточно. для большинства приложений.

person ghayes    schedule 10.07.2011
comment
Прохладный! Я думаю, что это подход, который я использовал в своей собственной попытке ответить на вопрос. Однако, прочитав эту тему, я теперь задаюсь вопросом, как вы могли превратить шаблон в поиск в стиле троичного дерева? - person Kebman; 04.08.2011
comment
Я уверен, что есть масса способов сделать это, и я был бы рад изучить их. Как простой вопрос к вам: какова величина ввода, на который вы смотрите? Какого уровня эффективности вы надеетесь достичь? - person ghayes; 05.08.2011
comment
Я понимаю, что масштаб ЭТОГО проекта того не стоит, но для удовольствия, скажем, МНОГО! - person Kebman; 06.08.2011

Вот версия, основанная на перестановке слов для подсчета одинаковых символов. Я использовал слова из вашего оригинального сравнения, а не код.

Это должно работать со словами любой длины и списком любой длины. Выход:

Word    score
----    -----
BALER   12
SALER   11
BAKER   11
CARER   10
RUFFR   4

Код:

use warnings;
use strict;

my @w = qw(BAKER SALER BALER CARER RUFFR);
my @tword = t_word(@w);

my @score;
push @score, str_count($_) for @tword;
@score = t_score(@score);

my %total;

for (0 .. $#w) {
    $total{$w[$_]} = $score[$_];
}

print "Word\tscore\n";
print "----\t-----\n";
print "$_\t$total{$_}\n" for (sort { $total{$b} <=> $total{$a} } keys %total);

# transpose the words
sub t_word {
    my @w = @_;
    my @tword;
    for my $word (@w) {
        my $i = 0;
        while ($word =~ s/(.)//) {
            $tword[$i++] .= $1;
        }
    }
    return @tword;
}

# turn each character into a count
sub str_count {
    my $str = uc(shift);
    while ( $str =~ /([A-Z])/ ) {
        my $chr = $1;
        my $num = () = $str =~ /$chr/g;
        $num--;
        $str =~ s/$chr/$num /g;
    }
    return $str;
}

# sum up the character counts
# while reversing the transpose
sub t_score {
    my @count = @_;
    my @score;
    for my $num (@count) {
        my $i = 0;
        while( $num =~ s/(\d+) //) {
            $score[$i++] += $1;
        }
    }
    return @score;
}
person TLP    schedule 10.07.2011

Вот моя попытка ответить. Это также позволит вам увидеть каждый отдельный матч, если вам это нужно. (т.е. BALER соответствует 4 символам в BAKER). РЕДАКТИРОВАТЬ: теперь он перехватывает все совпадения, если между словами есть связь (я добавил "CAKER" в список для проверки).

#! usr/bin/perl

use strict;
use warnings;

my @wordlist = qw( BAKER SALER BALER CARER RUFFR CAKER);

my %wordcomparison;

#foreach word, break it into letters, then compare it against all other words
#break all other words into letters and loop through the letters (both words have same amount), adding to the count of matched characters each time there's a match
foreach my $word (@wordlist) {
    my @letters = split(//, $word);
    foreach my $otherword (@wordlist) {
        my $count;
        next if $otherword eq $word;
        my @otherwordletters = split (//, $otherword);
        foreach my $i (0..$#letters) {
            $count++ if ( $letters[$i] eq $otherwordletters[$i] );
        }
        $wordcomparison{"$word"}{"$otherword"} = $count;
    }
}

# sort (unnecessary) and loop through the keys of the hash (words in your list)
# foreach key, loop through the other words it compares with
#Add a new key: total, and sum up all the matched characters.
foreach my $word (sort keys %wordcomparison) {
    foreach ( sort keys %{ $wordcomparison{$word} }) {
        $wordcomparison{$word}{total} += $wordcomparison{$word}{$_};
    }
}

#Want $word with highest total

my @max_match = (sort { $wordcomparison{$b}{total} <=> $wordcomparison{$a}{total} } keys %wordcomparison );

#This is to get all if there is a tie:
my $maximum = $max_match[0];
foreach (@max_match) {
print "$_\n" if ($wordcomparison{$_}{total} >= $wordcomparison{$maximum}{total} )
}

Результат прост: ПЕЧИК БАЛЕР и ПЕКАРЬ.

Хэш %wordcomparison выглядит так:

'SALER'
        {
          'RUFFR' => 1,
          'BALER' => 4,
          'BAKER' => 3,
          'total' => 11,
          'CARER' => 3
        };
person Jon    schedule 10.07.2011

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

Пример регулярного выражения:

(?:(C(?{ $c++ }))|.)(?:(A(?{ $c++ }))|.)(?:(R(?{ $c++ }))|.)(?:(E(?{ $c++ }))|.)(?:(R(?{ $c++ }))|.)

Это может быть или не быть быстрым.

use 5.12.0;
use warnings;
use re 'eval';

my @words = qw(BAKER SALER BALER CARER RUFFR);

my ($best, $count) = ('', 0);
foreach my $word (@words) {
    our $c = 0;
    foreach my $candidate (@words) {
    next if $word eq $candidate;

    my $regex_str = join('', map {"(?:($_(?{ \$c++ }))|.)"} split '', $word);
    my $regex = qr/^$regex_str$/;

    $candidate =~ $regex or die "did not match!";
    }
    say "$word $c";
    if ($c > $count) {
    $best = $word;
    $count = $c;
    }
}

say "Matching: first best: $best";

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

person Alex    schedule 10.07.2011
comment
Здесь нет необходимости в решении O (N ** 2) (которым также был бы xor); Я думаю, что двойной счет считается, если буква в вашем слове соответствует нескольким кандидатам. Кроме того, ^ должен отлично работать на utf8. - person ysth; 10.07.2011
comment
Перечитывая вопрос, он должен учитываться несколько раз, если буква в слове соответствует нескольким другим словам, извините. - person ysth; 10.07.2011
comment
Да, в любом случае это была миленькая головоломка. Мне нравится лучший хэш-подход, и я действительно должен был сначала попробовать его. - person Alex; 10.07.2011

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

#!/usr/bin/perl
use strict;
use warnings; 

# a list of words for testing
my @list = qw( 
BAKER
SALER
BALER
CARER
RUFFR
);

# populate two dimensional array with the list, 
# so we can compare each letter with the other letters on the same row more easily 
my $list_length = @list;
my @words;

for (my $i = 0; $i < $list_length; $i++) {
    my @letters = split(//, $list[$i]);
    my $letters_length = @letters;
    for (my $j = 0; $j < $letters_length; $j++) {
        $words[$i][$j] = $letters[$j];
    }
}
# this gives a two-dimensionla array:
#
# @words = (    ["B", "A", "K", "E", "R"],
#               ["S", "A", "L", "E", "R"],
#               ["B", "A", "L", "E", "R"],
#               ["C", "A", "R", "E", "R"],
#               ["R", "U", "F", "F", "R"],
# );

# now, on to find the word with most letters in common with the other on the same row

# add up the score for each letter in each word
my $word_length = @words;
my @letter_score;
for my $i (0 .. $#words) {
    for my $j (0 .. $#{$words[$i]}) {
        for (my $k = 0; $k < $word_length; $k++) {
            if ($words[$i][$j] eq $words[$k][$j]) {
                $letter_score[$i][$j] += 1; 
            }
        }
        # we only want to add in matches outside the one we're testing, therefore
        $letter_score[$i][$j] -= 1;
    }
}

# sum each score up
my @scores;
for my $i (0 .. $#letter_score ) {
    for my $j (0 .. $#{$letter_score[$i]}) {
        $scores[$i] += $letter_score[$i][$j];
    }
}

# find the highest score
my $max = $scores[0];
foreach my $i (@scores[1 .. $#scores]) {
    if ($i > $max) {
        $max = $i;
    }
}

# and print it all out :D
for my $i (0 .. $#letter_score ) {
    print "$list[$i]: $scores[$i]";
    if ($scores[$i] == $max) {
        print " <- best";
    }   
    print "\n";
}

При запуске скрипт выдает следующее:

BAKER: 11
SALER: 11
BALER: 12 <- best
CARER: 10
RUFFR: 4
person Kebman    schedule 16.07.2011