[perl] subroutine met hash en variabele

Pagina: 1
Acties:

  • Rotjeknor
  • Registratie: April 2001
  • Laatst online: 03-02 15:29
Ik ben nog niet zo ervaren met Perl, dus wellicht is het een makkelijke vraag, maar ik kan het in de boeken die ik hier heb liggen en google enzo niet vinden...

Situatie:
Perl:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# this function checks if the directory is already set in the hash
sub check_dir
{
  # get the required variables;
  my (%directories, $search_dir) = @_;
  my $index;
  
  foreach $index (keys %directories)
  {
    printf('dirindex: '.$directories{$index}."\n");
    printf('search: '.$search_dir."\n");

    if ($directories{$index} eq $search_dir)
    {
      printf("Is in array!\n");
    }
  }
}

  # split the path into directories and files
  @path = split(/\//, $file);

  # walk through the path
  for ($i = 0; $i < @path; $i++)
  {
    # handle the parent dir 
    if (($i == 1) and ($i < @path - 1))
    {
      printf("\n___handle______\n");
      $directory{$id}{'id'}     = $id;
      $directory{$id}{'name'}   = $path[$i];
      $directory{$id}{'subdir'} = 0;
      $id++;
      printf('before: '.$path[$i]."\n");
      check_dir(\%directory,$path[$i]);
    }
    # handle the subdir
    elsif ($i > 1 and $i < @path - 1 and 0)
    {
      $directory{$id}{'id'}     = $id;
      $directory{$id}{'name'}   = $path[$i];
      $directory{$id}{'subdir'} = 1;
      $directory{$id}{'parent'} = $path[$i - 1];
      $id++;
      printf('before: '.$path[$i]."\n");
      check_dir(\%directory,$path[$i]);
    }
  }

Onderste stuk is een deel uit het totale script. In $file komt bv ./dir_name/subdir/bla.txt. Met check_dir wil ik kijken of dir_name (of subdir) al in $directory staat. Wat ik dus doe is de hash meegeven naar de subroutine en ook de huidige dir die ie behandelt (dus bv 'subdir').

Wat gaat er fout:
De hash komt goed binnen, maar search_string is leeg. Dit terwijl ik 'm print (stuk met before:), en het daar wel goed is. Waarschijnlijk is het dus iets met het doorgeven van die variable naar de subroutine, maar hoe of wat ik het moet doen is mij onduidelijk. Ik heb al combinaties verzonnen met \ of *, maar blijkbaar wil dat niet.

Melding die ik nu krijg:
Use of uninitialized value in concatenation (.) or string at line 11.

Ook Knor is aangestoken met het ligfietsvirus!


  • bartware
  • Registratie: Juni 2001
  • Laatst online: 25-03-2023

bartware

@jabber.org

Je kan een hash (of een array) niet zomaar aan een sub meegeven. Alleen een reference.
Die moet je daarna weer dereferencen.
Perl:
1
2
3
4
5
6
7
sub check_dir
{
  # get the required variables;
  my ($directories, $search_dir) = @_;
  my (%directories) =%$directories;
 ...
}

Edit:
Foutje, het ging om $search_dir die niet doorkwam. Misschien eerst de waarde in een scalar zetten?

Maar om te checken of iets in een hash zit is er "exists".
Uit de help:
Perl:
1
2
3
    print "Exists\n"    if exists $hash{$key};
    print "Defined\n"   if defined $hash{$key};
    print "True\n"      if $hash{$key};

A hash or array element can be true only if it's defined, and defined if it exists, but the reverse doesn't necessarily hold true.

Dit geldt ook voor arrays.
HTH.

[ Voor 46% gewijzigd door bartware op 11-05-2004 15:48 ]

Heb ik me begrepen?
Cycle Vision 2020: 17-20 juli Sportpark Sloten & Wheelerplanet Spaarnwoude


  • Soultaker
  • Registratie: September 2000
  • Laatst online: 25-05 20:56
Eigenlijk zit het wat anders. Je kunt in Perl best arrays en hashes doorgeven als functieargumenten, maar de argumenten van een functie zijn altijd een array van scalars. Dat betekent dat arrays en hashes 'platgeslagen' worden.

Misschien was je al bekend dat een hash in Perl gerepresenteerd wordt als een array van scalars die de keys en values voorstellen. Dat is de reden dat je op beide onderstaande manieren een hash kan initialiseren:
Perl:
1
2
%hash = ( 'Foo' => 123, 'Bar' => 456, 'Baz' => 666 );
%hash = ( 'Foo', 123, 'Bar', 456, 'Baz', 666 );
Eigenlijk zijn beide expressies hetzelfde, want je zou in plaats van een hash ook een array met beide expressies kunnen initialiseren.

Als je nu een functieaanroep doet dan komen alle elementen uit de argumenten in je default array variabele terecht. Onderstaande calls zijn dus equivalent:
Perl:
1
2
functie( ("Foo" => 123, "Bar" => 321, "Baz" => 666 ), 'woei' );
functie("Foo", 123, "Bar", 321, "Baz", 666, 'woei' );

Dit is ook precies de situatie die jij nu voor je hebt. Als je in je functie de argumenten weer wilt opsplitsen in een hash gevolgd door een scalar, dan is dat een kwestie van het laatste element van de array in een scalar stoppen en de rest van de array in een hash. Zo dus:
Perl:
1
2
3
4
sub function {
    $scalar = pop;           # "woei"
    %hash = @_;              # ("Foo" => 123, "Bar" => 321, "Baz" => 666 )
}


Aan deze aanpak zitten twee beperkingen. Ten eerste geef je de waarden in de hash door en niet de hashvariabele zelf, dus je kunt de inhoud van de hash buiten de functie niet wijzigen (en dat is meestal ook de bedoeling; dat werkt net zo met scalaire argumenten). Ten tweede werkt dit alleen als je weet hoe je de array met argumenten moet opsplitsen. Het werkt dus in principe alleen als je maximaal 1 niet-scalair argument hebt (en dan ook alleen maar als je geen optionele argumenten hebt). Als deze beperkingen niet acceptabel zijn, moet je het wel oplossen met references.

  • Rotjeknor
  • Registratie: April 2001
  • Laatst online: 03-02 15:29
Tnx beiden, het is opgelost.

@bartware: het ging in eerste instantie inderdaad over de scalar. Ik heb echter de volgorde van die waarden in de functie op aanraden van een collega veranderd, dus toen ging het weer over de hash. Jouw eerste oplossing werkt dus.
Perl:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# this function checks if the directory is already set in the hash
sub dir_exists
{
  # get the required variables;
  my ($search_dir, $directories) = @_;
  my %directories = %$directories;
  my $index;

  foreach $index (keys %directories)
  {
    if ($directories{$index}{'name'} eq $search_dir)
    {
      return 1;
    }
    else
    {
      return 0;
    }
  }
}

Functienaam iets veranderd en functie inhoudelijk ook iets anders. Aanroep:
Perl:
1
dir_exists($path[$i], \%directory);

@Soultaker: Het is helaas bij mij geen optie om dit zo te doen, aangezien de hash die ik erin stop, steeds veranderd. References zijn blijkbaar wel de manier om het op te lossen.
Jouw verhaaltje maakt het echter al een stuk duidelijker hoe het nu eigenlijk zit. tnx.

Ook Knor is aangestoken met het ligfietsvirus!


  • bartware
  • Registratie: Juni 2001
  • Laatst online: 25-03-2023

bartware

@jabber.org

@Soultaker: bedankt voor deze heldere uitleg. Weer wat geleerd.

@Rotjeknor: Als je alleen wilt checken of een waarde voorkomt in de hash, dus in je functie ga je de hash niet aanpassen, dan heb je op zich geen reference nodig.

Als je eerst je searchstring opgeeft en dan je hash (zoals je nu doet, maar dan geen reference), hoef je ook niet eerst die scalar van de lijst te 'poppen' zoals Soultaker aangaf, maar wordt de rest automatisch als hash overgenomen.
Perl:
1
2
3
4
5
($scalar, %hash) = @_    # 'woei', ("Foo" => 123, "Bar" => 321, "Baz" => 666 )
                         # 'woei', "Foo", 123, "Bar", 321, "Baz", 666 

$scalar  is dan 'woei'
%hash is dan ("Foo" => 123, "Bar" => 321, "Baz" => 666 )

Heb ik me begrepen?
Cycle Vision 2020: 17-20 juli Sportpark Sloten & Wheelerplanet Spaarnwoude


  • RolandWitvoet
  • Registratie: Maart 2001
  • Niet online
by reference is ook nog eens een stuk beter voor de performance omdat niet de hele hash gekopieerd wordt maar gebruik gemaakt wordt van de bestaande hash.

NE2000 3-9 augustus, Elburg Open-air lan-party, 5 jaar alweer! Computers, kamperen, kampvuur, activiteiten, schier-eiland, dropping, tap-eiland, lezingen, workshops, bands, gezelligheid. NE2000, de andere Lanparty

Pagina: 1