Я использую этот вопрос в интервью и задаюсь вопросом, какое решение лучше всего.
Напишите подгруппу Perl, которая принимает списки п, а затем возвращает 2 списка ^ п-1, сообщающих вам, какие элементы в каких списках; то есть, какие элементы находятся только в первом списке, втором, списке, первом и втором списке и во всех других комбинациях списков. Предположим, что п достаточно мало (меньше 20).
Например:
list_compare([1, 3], [2, 3]);
=> ([1], [2], [3]);
Здесь первый список результатов дает все элементы, которые есть только в списке 1, второй список результатов дает все элементы, которые есть только в списке 2, а третий список результатов дает все элементы, которые находятся в обоих списках.
list_compare([1, 3, 5, 7], [2, 3, 6, 7], [4, 5, 6, 7])
=> ([1], [2], [3], [4], [5], [6], [7])
Здесь первый список содержит все элементы, которые есть только в списке 1, второй список содержит все элементы, которые есть только в списке 2, а третий список содержит все элементы, которые находятся в обоих списках 1 и 2, как в первом примере. Четвертый список содержит все элементы, которые есть только в списке 3, пятый список содержит все элементы, которые есть только в списках 1 и 3, шестой список содержит все элементы, которые есть только в списках 2 и 3, а седьмой список дает все элементы. которые есть во всех 3 списках.
Я обычно задаю эту проблему как продолжение подмножества этой проблемы для п = 2.
Каково решение?
Последующие действия: элементы в списках представляют собой строки. Могут быть дубликаты, но поскольку это просто строки, дубликаты должны быть сжаты на выходе. Порядок элементов в выходных списках не имеет значения, имеет значение порядок самих списков.





Вот мое решение:
Создайте хэш, ключи которого представляют собой объединение всех элементов во входных списках, а значения представляют собой битовые строки, где бит я устанавливается, если элемент присутствует в списке я. Битовые строки строятся с использованием побитового или. Затем создайте выходные списки, перебирая ключи хэша, добавляя ключи в связанный выходной список.
sub list_compare {
my (@lists) = @_;
my %compare;
my $bit = 1;
foreach my $list (@lists) {
$compare{$_} |= $bit foreach @$list;
$bit *= 2; # shift over one bit
}
my @output_lists;
foreach my $item (keys %compare) {
push @{ $output_lists[ $compare{$item} - 1 ] }, $item;
}
return \@output_lists;
}
Обновлено, чтобы включить создание перевернутого списка вывода, предложенное Аристотелем.
Прежде всего, хочу отметить, что ответ nohat просто не работает. Попробуйте запустить его и посмотрите на вывод в Data :: Dumper, чтобы убедиться в этом.
Тем не менее, ваш вопрос не совсем корректный. Похоже, вы используете наборы как массивы. Как вы хотите обрабатывать дубликаты? Как вы хотите обрабатывать сложные структуры данных? В каком порядке вы хотите расположить элементы? Для простоты я предполагаю, что ответы - это дубликаты сквоша, можно структурировать сложные структуры данных, и порядок не имеет значения. В этом случае вполне адекватным ответом будет следующий:
sub list_compare {
my @lists = @_;
my @answers;
for my $list (@lists) {
my %in_list = map {$_=>1} @$list;
# We have this list.
my @more_answers = [keys %in_list];
for my $answer (@answers) {
push @more_answers, [grep $in_list{$_}, @$answer];
}
push @answers, @more_answers;
}
return @answers;
}
Если вы хотите скорректировать эти предположения, вам нужно будет скорректировать код. Например, не раздавить сложные структуры данных и не раздавить дубликаты можно с помощью:
sub list_compare {
my @lists = @_;
my @answers;
for my $list (@lists) {
my %in_list = map {$_=>1} @$list;
# We have this list.
my @more_answers = [@$list];
for my $answer (@answers) {
push @more_answers, [grep $in_list{$_}, @$answer];
}
push @answers, @more_answers;
}
return @answers;
}
Тем не менее, это использование строковой модификации структуры данных для проверки того, существуют ли вещи, существующие в одной, в другой. Чтобы расслабиться в этом состоянии, потребуется немного больше усилий.
Ваше данное решение можно еще немного упростить.
В первом цикле вы можете использовать простое сложение, поскольку вы всегда выполняете операцию ИЛИ только с отдельными битами, и вы можете сузить область действия $bit, перебирая индексы. Во втором цикле вы можете вычесть 1 из индекса вместо создания ненужного 0-го элемента выходного списка, который необходимо отключить shift, и где вы без необходимости повторяете m * n раз (где m - количество выходных списков, а n - количество уникальных элементов), итерация по уникальным элементам уменьшит количество итераций до n (что является значительным преимуществом в типичных случаях использования, когда m намного больше, чем n), и упростит код.
sub list_compare {
my ( @list ) = @_;
my %dest;
for my $i ( 0 .. $#list ) {
my $bit = 2**$i;
$dest{$_} += $bit for @{ $list[ $i ] };
}
my @output_list;
for my $val ( keys %dest ) {
push @{ $output_list[ $dest{ $val } - 1 ] }, $val;
}
return \@output_list;
}
Также обратите внимание, что, если подумать таким образом, процесс сбора результатов может быть очень кратко записан с помощью модуля Список :: Часть:
use List::Part;
sub list_compare {
my ( @list ) = @_;
my %dest;
for my $i ( 0 .. $#list ) {
my $bit = 2**$i;
$dest{$_} += $bit for @{ $list[ $i ] };
}
return [ part { $dest{ $_ } - 1 } keys %dest ];
}
Но учтите, что list_compare - ужасное имя. Что-то вроде part_elems_by_membership было бы намного лучше. Кроме того, необходимо исправить неточности в вашем вопросе, на которые указал Бен Тилли.
Отсутствие побитовой обработки или означает, что решение не сработает, если во входных списках есть дубликаты. Однако инвертирование цикла для генерации выходного списка является значительным улучшением. Спасибо.
Извините, я не понял суть проблемы. Я имел в виду, что каждый выходной список должен содержать элементы, которые являются Только во входном списке (ах), которому соответствует выходной список. Это решение не соответствует результатам в примерах; он дает элементы любой во входных списках (ах), что является гораздо более простой проблемой.