Удалите дубликаты ключей из хэша хэшей и массивов (и убедитесь, что все полученные пустые хэши также удалены) - PullRequest
0 голосов
/ 20 сентября 2018

У меня есть поток данных в формате JSON, к которому мой скрипт обращается с внутреннего веб-сайта.Мой скрипт преобразует JSON в хеш perl с помощью JSON.pm (я использую perl 5.10.1 на RHEL 6.9)

В этом хеше есть несколько вложенных хешей и вложенные массивы, некоторые из которых вложены вдругие хэши / массивы внутри большого хэша.

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

Кроме того, из-за того, как структурированы данные, некоторые вложенные хэши имеют ТОЛЬКО ключи, которые теперь удаляются, оставляя значение для некоторыхключи как пустой хеш.Мне также нужно удалить те ключи, которые имеют пустой хеш для значения

Вот мои данные после преобразования в perl:

$VAR1 = {
          'cat' => 'meow',
          'dog' => [
                     {
                        'a' => {
                                  'husky' => {
                                                'name' => 'fred'
                                             },
                                  'chow' => {
                                               'name' => 'barney'
                                            }
                               },
                     },
                     {
                        'b' => {
                                  'husky' => 'wilma',
                                  'lab' => 'betty'
                               },
                        'c' => 'pebbles' # yes this is intentionally a scalar in the example
                     },
                     {
                        'd' => {
                                  'shihtzu' => 'bambam'
                               },
                     },
                     {
                        'e' => {
                                  'husky' => 'dino'
                               },
                     },
                   ],
        }

Мы хотим удалить все ключи с именем'husky'

Вот как это должно выглядеть:

$VAR1 = {
          'cat' => 'meow',
          'dog' => [
                     {
                        'a' => {
                                  'chow' => {
                                               'name' => 'barney'
                                            }
                               },
                     },
                     {
                        'b' => {
                                  'labrador' => 'betty'
                               },
                        'c' => 'pebbles'
                     },
                     {
                        'd' => {
                                  'shihtzu' => 'bambam'
                               },
                     },
                   ],
        }

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

$VAR1 = {
          'cat' => 'meow',
          'dog' => [
                     {
                        'a' => {
                                  'chow' => {
                                               'name' => 'barney'
                                            }
                               },
                     },
                     {
                        'b' => {
                                  'lab' => 'betty'
                               },
                        'c' => 'pebbles' # yes this is intentionally a scalar in the example
                     },
                     {
                        'd' => {
                                  'shihtzu' => 'bambam'
                               },
                     },
                     {
                        'e' => {},
                     },
                   ]
        }

Я пробовал несколько вариантов, найденных в других местах на SO и perlmonks. keys %$_ == 0, !%$_, чтобы назвать несколько. Но ни один из них не работаетс этим ломтиком хеша.

Код:

use 5.008008;
use strict;
use warnings;
use English; # I know I know, don't use English...
use JSON;
use YAML::Tiny qw(Dump);
# proprietary modules I wrote added here, which themselves load in LWP, HTTP::Cookie and others, and they do the bulk of building and sending the request. They are the back end to this script's front end.

[-snipped a ton of code-]

sub _count_keys
{
    my ($j, $seen) = @ARG;
    my $type = ref $j;
    if ($type eq "ARRAY")
    {
        for (@{$j})
        {
            _count_keys($ARG, $seen);
        }
    }
    elsif ($type eq "HASH")
    {
        while (my ($key, $val) = each %{$j})
        {
            $seen->{$key}++;
            if (ref $val)
            {
                _count_keys($val, $seen);
            }
        }
    }
    return $seen;
}

sub _remove_duplicate_keys
{
    my ($j, $seen) = @ARG;
    $seen //= _count_keys($j, {});

    my $type = ref $j;
    if ($type eq "ARRAY")
    {
        return [ map { _remove_duplicate_keys($ARG, $seen) } @{$j} ];
    }
    elsif ($type eq "HASH")
    {
        my %obj = %{$j};
        delete @obj{grep { $seen->{$ARG} > 1 and $ARG eq 'keyNameToBeExcluded'} keys %obj};
# Here is where I have been putting another delete line but I can't seem to find the right parameters for the grep to make it delete the empty anon hashes. Example of what I tried is the next comment below
#        delete @obj{grep { $seen->{$ARG} > 1 and keys $ARG{assetDetails} == 0 } keys %obj};

        while (my ($key, $val) = each %obj)
        {
            if (ref $val)
            {
                $obj{$key} = _remove_duplicate_keys($val, $seen);
            }
        }
        return \%obj;
    }
    else
    {
        return $j;
    }
}

sub _process_json
{
    my $JSONOUTPUT   = shift;
    my $OPTIONS      = shift;

    # Change true to 1 and false to 0 to prevent blessed objects from appearing in the JSON, which prevents the YAML::Tiny module from barfing
    foreach (@{$JSONOUTPUT})
    {
        s{true(,\n)}{1$1}gxms;
        s{false(,\n)}{0$1}gxms;
    }

    my $JSONPERLOBJ  = JSON->new->utf8->decode(@{$JSONOUTPUT});

# Test code below here; real code not in use while I test getting the output right.
use Data::Dumper;
my $BEFORE = $JSONPERLOBJ;
my $AFTER = _remove_duplicate_keys($JSONPERLOBJ);
#    $JSONPERLOBJ = _remove_duplicate_keys($JSONPERLOBJ);
#print Dumper $BEFORE;
print Dumper $AFTER;
exit 1;
# End test code
}
sub _main
{
    [-snip private code-]
    my @JSONOUTPUT = $RESPONSE->decoded_content;
    my $RC = _process_json(\@JSONOUTPUT, $OPTIONS);

    exit ($RC == 1)?0:1;
}

Ответы [ 2 ]

0 голосов
/ 26 сентября 2018

Я так понимаю, ответ Шона работает для удаления дубликатов, для чего он выглядит хорошо.

Следующая проблема заключается в том, что мы можем получить пустые структуры, которые также должны быть удалены.Но тогда также могут быть структуры, которые содержат только пустые структуры и т. Д., И я предполагаю, что все это необходимо устранить.

Я использую нужный вопрос-hashref из вопроса (из которого я удаляю одну name=>..., чтобы не было дубликатов) и добавляю некоторые пустые проблемы.

use warnings;
use strict;
use feature 'say';
use Data::Dump qw(dd pp);

my $hr = {
    'cat' => 'meow',
    'dog' => [
        {
            'a' => { 'chow' =>  { 'name' => 'barney' } },
        },
        {
            'b' => { 'lab' => 'betty' },
            'c' => 'pebbles'
        },
        {
            'd' => { 'shihtzu' => 'bambam' },
        },
        {   # all of the following need to go, and this hashref
            'e' => { },  
            'f' => { noval => { } },
            'g' => [ { }, { nada => { } }, [ ] ],
        },
    ],  
};
dd $hr; say '';

for my $k (sort keys %$hr) {
    next_level($hr, $k, $hr->{$k}, 'key');
}

# Takes: data structure (reference), key/index at which it is found, 
# its value for it, and description string of which it is, 'key|idx'
sub next_level {
    my ($ds, $index, $val, $kind) = @_;
    my $type = ref $val;

    if ($type eq 'ARRAY') {
        for my $i (0..$#$val) {
            next_level(
                ( $kind eq 'key' ? $ds->{$index} : $ds->[$index] ),
                $i, $val->[$i], 'idx' 
            );
        }

        # Collect indices for and delete elements that are empty
        my @to_delete;
        for my $i (0..$#$val) {
            if ( (ref $val->[$i] eq 'HASH'  and not keys %{$val->[$i]}) or
                 (ref $val->[$i] eq 'ARRAY' and not      @{$val->[$i]})  )
            {
                say "No value/empty for index $i, record for deletion";
                push @to_delete, $i;
            }
        }
        if (@to_delete) { 
            my %ref_idx = map { $_ => 1 } @to_delete;
            @$val = @$val[ grep { not exists $ref_idx{$_} } 0..$#$val ];
        }
    }
    elsif ($type eq 'HASH') {
        for my $k (sort keys %{$val}) {
            my $ds_next_level = 
                ($kind eq 'key') ? $ds->{$index} : $ds->[$index];

            next_level( $ds_next_level, $k, $val->{$k}, 'key' );

            # Delete if empty 
            if ( (ref $val->{$k} eq 'HASH'  and not keys %{$val->{$k}}) or
                 (ref $val->{$k} eq 'ARRAY' and not      @{$val->{$k}})  )
            {
                say "No value/empty for key $k, delete";
                delete $ds_next_level->{$k}
            }
        }
    }
    #elsif (not $type) { say "A value: ", $val }
}
say ''; dd $hr;

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

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

Для исключения «плохих» индексов для эффективности используется ссылочный хеш.Перезапись массива может быть быстрее при использовании map (см. в этом посте ), или не может быть, если срезы допускают определенные (интерпретатор) оптимизации.

Вывод

{
  cat => "meow",
  dog => [
           { a => { chow => { name => "barney" } } },
           { b => { lab => "betty" }, c => "pebbles" },
           { d => { shihtzu => "bambam" } },
           { e => {}, f => { noval => {} }, g => [{}, { nada => {} }, []] },
         ],
}

No value/empty for key e, delete
No value/empty for key noval, delete
No value/empty for key f, delete
No value/empty for key nada, delete
No value/empty for index 0, record for deletion
No value/empty for index 1, record for deletion
No value/empty for index 2, record for deletion
No value/empty for key g, delete
No value/empty for index 3, record for deletion

{
  cat => "meow",
  dog => [
           { a => { chow => { name => "barney" } } },
           { b => { lab => "betty" }, c => "pebbles" },
           { d => { shihtzu => "bambam" } },
         ],
}
0 голосов
/ 20 сентября 2018

Я думаю, что это то, что вы хотите:

#!/usr/bin/perl
use warnings;
use strict;
use feature qw/say/;
use JSON::XS; # Better than JSON; also see JSON::MaybeXS

my $j = <<EOJSON;
{
  "foo": 1,
  "bar": {
      "foo": true,
      "baz": false
      },
  "dog": "woof",
  "cat": [ { "foo": 3 } ]
}
EOJSON

sub count_keys {
  my ($j, $seen) = @_;
  my $type = ref $j;
  if ($type eq "ARRAY") {
    count_keys($_, $seen) for @$j;
  } elsif ($type eq "HASH") {
    while (my ($key, $val) = each %$j) {
      $seen->{$key}++;
      count_keys($val, $seen) if ref $val;
    }
  }
  return $seen;
}

sub remove_dups {
  my ($j, $seen) = @_;
  $seen //= count_keys($j, {});

  my $type = ref $j;
  if ($type eq "ARRAY") {
    return [ map { remove_dups($_, $seen) } @$j ];
  } elsif ($type eq "HASH") {
    my %obj = %$j;
    delete @obj{grep { $seen->{$_} > 1 } keys %obj};
    while (my ($key, $val) = each %obj) {
      $obj{$key} = remove_dups($val, $seen) if ref $val;
    }
    return \%obj;
  } else {
    return $j;
  }
}

my $parsed = decode_json $j;
my $printer = JSON::XS->new->pretty->canonical;
say "Before:";
print $printer->encode($parsed);
say "After:";
my $dedup = remove_dups $parsed;
print $printer->encode($dedup);

производит

Before:
{
   "bar" : {
      "baz" : false,
      "foo" : true
   },
   "cat" : [
      {
         "foo" : 3
      }
   ],
   "dog" : "woof",
   "foo" : 1
}
After:
{
   "bar" : {
      "baz" : false
   },
   "cat" : [
      {}
   ],
   "dog" : "woof"
}

Редактировать для объяснения:

Первый раз remove_dups вызывается наСтруктура данных perl, представляющая значение json (которое не обязательно должно быть объектом json), вызывает count_keys для рекурсивного обхода структуры и создания хэша всех ключей и количества повторений каждого из них.Затем он снова рекурсивно обходит структуру, возвращая глубокую копию без ключей, которые неоднократно появлялись в оригинале.

Эта строка - настоящее волшебство:

delete @obj{grep { $seen->{$_} > 1 } keys %obj};

Используется hash slice для одновременного удаления нескольких ключей, причем бит grep возвращает список ключей, которые появлялись более одного раза. Больше информации на ломтиках.

...