Нечувствительные к регистру уникальные элементы массива в Perl

Я использую функцию uniq, экспортируемую модулем, List::MoreUtils, чтобы найти элементы uniq в массиве. Однако я хочу, чтобы он находил элементы uniq без учета регистра. Как я могу это сделать?

Я сбросил вывод массива, используя Data::Dumper:

#! /usr/bin/perl

use strict;
use warnings;
use Data::Dumper qw(Dumper);
use List::MoreUtils qw(uniq);
use feature "say";

my @elements=<array is formed here>;

my @words=uniq @elements;

say Dumper \@words;

Выход:

$VAR1 = [
          'John',
          'john',
          'JohN',
          'JOHN',
          'JoHn',
          'john john'
        ];

Ожидаемый результат должен быть: john, john john

Только 2 элемента, все остальные должны быть отфильтрованы, так как это одно и то же слово, разница только в случае.

Как я могу удалить повторяющиеся элементы, игнорируя регистр?


person Neon Flash    schedule 25.10.2012    source источник


Ответы (2)


Используйте строчные буквы, lc с map:

my @uniq_no_case = uniq map lc, @elements;

Причина, по которой List::MoreUtils' uniq чувствительна к регистру, заключается в том, что она зависит от характеристик дедупликации хэшей, который также чувствителен к регистру. Код для uniq выглядит так:

sub uniq {
    my %seen = ();
    grep { not $seen{$_}++ } @_;
}

Если вы хотите использовать эту подпрограмму непосредственно в своем собственном коде, вы можете добавить туда lc:

sub uniq_no_case {
    my %seen = ();
    grep { not $seen{$_}++ } map lc, @_;
}

Объяснение того, как это работает:

@_ содержит аргументы подпрограммы, и они передаются оператору grep. Любые элементы, которые возвращают true при прохождении через блок кода, возвращаются оператором grep. Блок кода состоит из нескольких тонкостей:

  • $seen{$_}++ возвращает 0 при первом просмотре элемента. Значение по-прежнему увеличивается до 1, но после этого возвращается (в отличие от ++$seen{$_}, который сначала увеличивает, а затем возвращает).
  • Отрицая результат приращения, мы получаем true для первого ключа и false для каждого последующего такого ключа. Таким образом, список дедупликации.
  • grep в качестве последнего оператора в подпрограмме вернет список, который, в свою очередь, будет возвращен подпрограммой.
  • map lc, @_ просто применяет функцию lc ко всем элементам в @_.
person TLP    schedule 25.10.2012
comment
И это та же уникальная функция, экспортируемая модулем List::MoreUtils? - person Neon Flash; 25.10.2012
comment
Это действительно так. Хотя, поскольку подпрограмма такая простая и короткая, вы можете просто скопировать ее и избавить себя от загрузки модуля. - person TLP; 25.10.2012
comment
Спасибо. Я пойму подпрограмму, а затем буду использовать ее напрямую :) Не могли бы вы немного объяснить синтаксис grep? Хэш %seen использует элементы массива в качестве ключа и проверяет их появление. Но я не уверен, как работает весь этот синтаксис. - person Neon Flash; 25.10.2012
comment
@NeonFlash Добавил объяснение в свой ответ. Я думаю, что это довольно умно написанный саб. - person TLP; 25.10.2012
comment
@NeonFlash Если этот ответ решает вашу проблему к вашему удовлетворению, не забудьте принять его, нажав на галочку. - person TLP; 25.10.2012
comment
Спасибо за объяснение, TLP :) - person Neon Flash; 28.10.2012
comment
Эта версия синтаксиса немного более гибкая: my @uniq_no_case = uniq map {lc $_} @elements; - person HoldOffHunger; 08.06.2017

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

my %seen;
my @unique;
for my $w (@words) {
  next if $seen{lc($w)}++;
  push(@unique, $w);
}
# @unique has the unique words

Обратите внимание, что это сохранит регистр исходных слов.

ОБНОВЛЕНИЕ: Как отмечено в комментариях, неясно, что именно нужно ОП, но я написал решение таким образом, чтобы проиллюстрировать общий метод выбора уникальных представителей из списка при некотором «отношении эквивалентности». В этом случае отношение эквивалентности: слово $a эквивалентно слову $b тогда и только тогда, когда lc($a) eq lc($b).

Большинство отношений эквивалентности можно выразить таким образом, то есть отношение определяется функцией классификатора f(), так что $a эквивалентно $b тогда и только тогда, когда f($a) eq f($b). Например, если мы хотим сказать, что два слова эквивалентны, если они имеют одинаковую длину, то f() будет length().

Итак, теперь вы можете понять, почему я написал алгоритм таким образом — функция-классификатор может не выдавать значения, которые являются частью исходного списка. В случае f = length мы хотим выбрать слова, но f слова — это число.

person ErikR    schedule 25.10.2012
comment
Использование lc внутри хеш-доступа намного лучше, чем другое приведенное решение, поскольку оно сохраняет (первое совпадение) случай из ввода. - person LeoNerd; 26.10.2012
comment
@LeoNerd О чем ты говоришь? Нет никакой разницы между использованием lc до и внутри хеша. - person TLP; 26.10.2012
comment
Я имел в виду, в отличие от карты lc ... решение, данное в другом ответе. Этот лучше, так как он возвращает значения в их исходном регистре, а не в принудительном нижнем регистре. - person LeoNerd; 26.10.2012
comment
Ага, теперь вижу. Однако это не то, что просил ОП. Кроме того, кто сказал, что оригинальный чехол желателен? Обычно имена ucfirst(lc). - person TLP; 26.10.2012
comment
Я уверен, что библиотека uniq() имеет большую поддержку и эффективность, чем эта версия. - person HoldOffHunger; 08.06.2017