Удаление и добавление ссылок на Hash и Array в Perl - PullRequest
2 голосов
/ 31 декабря 2010

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

one/two/three
one/two[]/three
one/two/four
one/two[]/four 

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

one => {
     two => {
         three => "",
         four => "",
     }
     two[] => [
         {
             three => "",
             four => "",
         }
     ]
}

Сценарий, который я использую:

# !/usr/bin/perl

use Data::Dumper; 

sub insert {
  my ($ref, $head, @tail) = @_;
  if ( @tail ) { 
    if( $head !~ /^(.*)(\[\])$/ ) {
        insert( \%{$ref->{$head}}, @tail );
    } else {
        my %newhash = ();
        unshift(@{$ref->{$1 . $2}}, %newhash);
        insert( \%{$ref->{$1 . $2}[0]}, @tail );
    }
  } else {
    $ref->{$head} = '';
  }
}

my %hash;
chomp and insert \%hash, split( '/', $_ ) while <>;

print Dumper %hash;

Что я хотел бы сделать, это когда я найду two[], я хочу удалить two и добавить его в массив two[] (если существует two), а затем переименовать ключ two[] до two.

Таким образом, конечный результат будет выглядеть примерно так:

one => {
    two => [
        {
            three => "",
            four => "",
        },
        {
            three => "",
            four => "",
        }
    ]
}

Поэтому я попытался добавить проверки в if else для проверки ключей с суффиксами [] или без них, но я получил диапазон или ошибки, такие как [$variable] is not a valid HASH reference и т. Д. Как можно проверить тип переменной (например, $ref->{$head} is array?) а удаление и переименование ключей хэша эффективно?

Спасибо.

Ответы [ 2 ]

1 голос
/ 31 декабря 2010

Ладно, по всем правилам, это должно быть отстойно, а не делать то, что вы хотите - Но я провел последний час, пытаясь сделать это несколько правильно, поэтому я буду проклят. Каждое «что-нибудь []» представляет собой массив из двух элементов, каждый из которых имеет хэш-ссылку: один для элементов, которые появляются после пустого «чего-либо», а второй для элементов, появляющихся после «чего-нибудь []». Вероятно, мне следовало бы использовать замыкание вместо того, чтобы полагаться на эту дрянную переменную $ is_non_bracket - утром я еще раз посмотрю, когда буду менее отсталым и постыднее писать это.

Я думаю, что он оптимизирован с помощью хвостового вызова (часть goto & SUB). Это также делает (маленькое) использование именованных захватов .

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

sub construct {
    my $node = shift;
    return unless @_;
    my $next           = shift;
    my $is_non_bracket = 1;

    $next .= '[]' and $is_non_bracket-- if exists $node->{ $next . '[]' };
    if ( $next =~ / (?<node>[^\[\]]+) \Q[]/x ) {
        if ( exists $node->{ $+{node} } or not defined( $node->{$next} ) ) {
            push @{ $node->{$next} }, (delete $node->{ $+{node} } // {}); #/
         }
         unshift @_, $node->{$next}->[$is_non_bracket] ||= {};
    }
    else {
        $node->{$next} ||= @_ ? {} : $node->{$next};
        unshift @_, $node->{$next} //= @_ ? {} : ''; #/
    }
    goto &construct;
}


my %hash;

while (<DATA>) {
    chomp;
    construct( \%hash, split m!/! );
}

say Dumper \%hash;

__DATA__
one/two/three
one/two[]/three
one/two[]/three/four
one/two[]/three/four/five[]
one/two[]/three/four/whatever
one/two/ELEVAN
one/three/sixteen
one/three[]/whygodwhy
one/three/mrtest/mruho
one/three/mrtest/mruho[]/GAHAHAH

РЕДАКТИРОВАТЬ: у Regex был дополнительный пробел после кавычки, из-за которой он сломался; Мой плохой.

EDIT2: Хорошо, это утро, отредактированное в версии, которая не так глупа. Нет необходимости в ссылке, так как мы всегда передаем хэш-ссылку; Символы # / предназначены для того, чтобы // не могли выделить подсветку.

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

sub construct {
    my $node = shift;
    return unless @_;
    my $is_bracket = (my $next = shift) =~ s/\Q[]// || 0; 

    if (ref $node->{$next} eq 'ARRAY' or $is_bracket) {
        if ( ref $node->{ $next } ne 'ARRAY' ) {
            my $temp = delete $node->{ $next } || {};
            push @{ $node->{$next} = [] }, $temp;
         }
         unshift @_, $node->{$next}->[$is_bracket] ||= {};
    }
    else {
        $node->{$next} ||= @_ ? {} : $node->{$next};
        unshift @_, $node->{$next} //= @_ ? {} : ''; #/
    }
    goto &construct;
}

EDITNaN: Вот суть того, что он делает: Если аргументов достаточно, мы переключаемся во второй раз и помещаем значение в $ next, которое быстро вытягивается в подстановку, которая забирает его [], если он имеет какие-либо: если это так, подстановка возвращает 1, в противном случае s /// возвращает undef (или забываю пустую строку), поэтому мы используем логическое or или для установки возвращаемого значения в 0; В любом случае, мы устанавливаем $ is_bracket в это.

Впоследствии, если $ node -> {$ next} - это arrayref, или $ next содержит скобки: Если $ node -> {$ next} не был arrayref (поэтому мы попали сюда, потому что $ next имел квадратные скобки, и это было в первый раз), это либо undef, пустая строка, либо hashref; Мы удаляем что бы то ни было и сохраняем его в $ temp. Затем мы устанавливаем пустое $ node -> {$ next} равным arrayref и устанавливаем (push) $ temp в качестве первого элемента - это означает, что, например, если 'two' существовало ранее, а $ next было изначально 'two []', затем 'two' теперь будет указывать на arrayref, а его старое значение будет сохранено в [0]. Как только $ node -> {$ next} является ссылкой на массив (или, если он уже был), мы снимаем хэш-ссылку в индексе, указанном в $ is_backet - 0, если у $ next нет скобок, и 1, если у него есть - to @ _. Если hashref не существует (или потому что он undef для обоих, или, возможно, пустая строка, для 0), мы назначаем ему совершенно новый hashref с логическим или.

Если это был не arrayref, это hashref, поэтому мы делаем то же самое, что и раньше, и возвращаем полученное значение в @ _.

Мы делаем все это без смещения, потому что волшебное goto передает наш текущий @_ функции, которая заменит нас.

1 голос
/ 31 декабря 2010

Я не уверен, по какой логике вы достигнете ожидаемого результата, но могу уточнить следующее:

  • Вы можете проверить тип ссылки , используя функцию ref .
  • В вашем текущем коде $ ref рассматривается как ссылка на хеш в каждом отдельном случае. Я могу сказать, потому что вы разыменовываете его как хеш, используя синтаксис $ ref -> {...} в каждом отдельном предложении ваших операторов if.
  • Если я правильно вас прочитал, $1 . $2 должен совпадать с $head. Я нахожу это более ясным, просто $head.
  • Ваша строка unshift оживляет $ref->{$1 . $2} как ссылку на массив (на явный пустой массив). И следующая строка оживляет свой первый элемент как ссылку на хэш. Первая строка кажется мне излишней, вы получите те же результаты с единственной строкой insert; поэтому я не уверен в намерении.
...