Зачем мне возвращать хеш или хеш-ссылку в Perl?

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

Файл a.pl

my %hash = build_hash();
# Do stuff with hash using $hash{$key}
sub build_hash
{
    # Build some hash
    my %hash = ();
    my @k = qw(hi bi no th xc ul 8e r);
    for ( @k )
    {
        $hash{$k} = 1;
    }

    # Does this return a copy of the hash??
    return %hash;
}

Файл b.pl

my $hashref = build_hash();
# Do stuff with hash using $hashref->{$key}
sub build_hash
{
    # Build some hash
    my %hash = ();
    my @k = qw(hi bi no th xc ul 8e r);
    for ( @k )
    {
        $hash{$k} = 1;
    }

    # Just return a reference (smaller than making a copy?)
    return \%hash;
}

Файл c.pl

my %hash = %{build_hash()};
# Do stuff with hash using $hash{$key}
# It is better, because now we don't have to dereference our hashref each time using ->?

sub build_hash
{
    # Build some hash
    my %hash = ();
    my @k = qw(hi bi no th xc ul 8e r);
    for ( @k )
    {
        $hash{$k} = 1;
    }

    return \%hash;
}

Ответ 1

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

my $value = build_hash()->{$key};

Научитесь любить хеш-ссылки, вы будете видеть их много, как только вы начнете использовать объекты.

Ответ 2

Почему бы не вернуть оба? Контекст - очень мощная функция Perl, позволяющая вашим функциям "делать то, что вы имеете в виду". Часто решение о том, что это лучшее возвращаемое значение, зависит от того, как код вызова планирует использовать значение, и именно поэтому Perl имеет встроенный wantarray.

sub build_hash {
    my %hash;
    @hash{@keys} = (1) x @keys;
    wantarray ? %hash : \%hash
}

my %hash = build_hash;  # list context, a list of (key => value) pairs
my $href = build_hash;  # scalar context, a hash reference

Ответ 3

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

Ответ 4

То, что вы ищете, это хэш-фрагмент:

# assigns the value 1 to every element of the hash

my %hash;                                   # declare an empty hash
my @list = qw(hi bi no th xc ul 8e r);      # declare the keys as a list
@hash{@list} =                              # for every key listed in @list,
                (1) x @list;                # ...assign to it the corresponding value in this list
                                            # which is (1, 1, 1, 1, 1...)  (@list in scalar context
                                            #   gives the number of elements in the list)

Оператор x описан в perldoc perlop.

См. perldoc perldsc и perldoc perlreftut для учебники по структурам данных и ссылкам (как для чтения, так и для начинающих и экспертов). Сами кусочки хэшей упоминаются в perldoc perldata.

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

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

use strict; use warnings;
use Data::Dumper;

function foo
{
    return qw(key1 value1 key2 value2);
}

my @list = foo();
my %hash = foo();

print Dumper(\@list);
print Dumper(\%hash);

дает:

$VAR1 = [
          'key1',
          'value1',
          'key2',
          'value2'
        ];

$VAR1 = {
          'key2' => 'value2',
          'key1' => 'value1'
        };

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

Ответ 5

a.pl и c.pl требуется копия хеша (и хэш, внутренний для функции, помечен как свободная память). b.pl, с другой стороны, строит хэш только один раз и требует немного дополнительной памяти для возврата ссылки, с которой вы можете работать. Таким образом, b.pl, скорее всего, будет наиболее эффективной формой этих трех, как в пространстве, так и во времени.

Ответ 6

Я собираюсь пойти против зерна и о том, что говорят все остальные, и сказать, что я предпочитаю, чтобы мои данные возвращались как хэш (ну, как список размером по размеру, который, вероятно, будет интерпретироваться как хэш). Я работаю в среде, где мы склонны делать такие вещи, как следующий фрагмент кода, и гораздо проще комбинировать и сортировать, а также кусочки и кусочки, когда вам не нужно разыгрывать каждую другую строку. (Также приятно знать, что кто-то не может повредить ваш hashref, потому что вы передали всю вещь по значению. изменить:, если у вас нет ссылок на другие объекты/хэши/массивы в хэш-значениях, то у вас все равно есть проблемы).

my %filtered_config_slice = 
   hashgrep { $a !~ /^apparent_/ && defined $b } (
   map { $_->build_config_slice(%some_params, some_other => 'param') } 
   ($self->partial_config_strategies, $other_config_strategy)
);

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

(Да, у нас есть хорошие инструменты, такие как hashgrep и hashmap и lkeys, которые полезны для хэшей. $a и $b получают ключ и значение каждого элемента в списке соответственно). (Да, у нас есть люди, которые могут программировать на этом уровне. Наем является неприятным, но у нас есть качественный продукт.)

Если вы не собираетесь делать что-либо подобное, похожее на функциональное программирование, или если вам нужна большая производительность (вы профилировали?), тогда обязательно используйте hashrefs.

Ответ 7

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

Мне придется не согласиться с Эфиром здесь. Было время, когда я занял эту позицию, но быстро обнаружил, что я спускаюсь в ад, чтобы вспомнить, какие sub возвратили хэши и которые вернули hashrefs, что было довольно серьезным препятствием для того, чтобы просто заставить код работать. Важно стандартизировать либо всегда возвращать хэш/массив, либо всегда возвращать hashref/arrayref, если вы не хотите постоянно спотыкаться о себе.

Что касается стандартизации, я вижу несколько преимуществ для ссылок со ссылками:

  • Когда вы возвращаете хэш или массив, то, что вы на самом деле возвращаете, является списком, содержащим сплющенную копию исходного хэша/массива. Точно так же, как передача параметров hash/array в sub, это имеет тот недостаток, что вы можете отправлять только один список за раз. Разумеется, вам не часто нужно возвращать несколько списков значений, но это происходит, поэтому зачем выбирать стандартизацию при выполнении действий таким образом, чтобы это исключало?

  • Преимущества (обычно незначительные) производительности/памяти для возврата одного скаляра, а не потенциально гораздо большего объема данных.

  • Он поддерживает согласованность с кодом OO, который часто передает объекты (то есть, блаженные ссылки) взад и вперед.

  • Если по какой-либо причине важно, чтобы у вас была новая копия хэша/массива, а не ссылка на оригинал, вызывающий код может легко сделать это, как показано в OP в c.pl. Если вы вернете копию хеша, тем не менее, нет возможности для вызывающего лица превратить это в ссылку на оригинал. (В тех случаях, когда это выгодно, функция может сделать копию и вернуть ссылку на копию, тем самым защищая оригинал, а также избегая "хешей возврата", которые возвращают hashrefs "ад, о котором я упоминал ранее.)

  • Как отметил Шверн, очень приятно иметь возможность my $foo = $obj->some_data->{key}.

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

Ответ 8

Будьте осторожны: a.pl возвращает список с четным числом элементов, а не хешем. Когда вы затем назначаете такой список хеш-переменной, хеш будет построен с элементами с четными индексами в виде ключей и элементами нечетных индексов в качестве значений. [EDIT: Так я всегда видел дело, но sub { ... %hash } действительно ведет себя немного иначе, чем sub { ... @list }. ]

По той же причине построение хэша, как вы описываете, так же просто, как:

my %hash = map { $_ => 1 } qw(hi bi no th xc ul 8e r);

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

EDIT: (Я больше не могу нажать ссылку "добавить комментарий"?!) Я подумал об этом немного, и я думаю, что передача хеш-ссылок, вероятно, лучше в конце концов из-за того, как мы используем хэш. Вышеприведенный абзац все же сохраняется для массива refs.

Спасибо за ваши комментарии Шверн и Эфир.