lamasculo has asked for the wisdom of the Perl Monks concerning the following question:

I am trying to enhance a user creation script written in perl; I am using active perl on Win 10 I have attached an excerpt in which I am trying to account for multiple first names in a user, so that the people who use the script do not have to edit the input file to have a single first name The loop I created to create a capitalised and spaced names, does not put in the space that I would expect

use strict; #except to test my code #do the fancy stuff here for multiple Christian names #count how many firstnames #first would have been read in from a text file, but is explicit here my $first = "billy bob allan"; my @nfirst = (); @nfirst = split (/\s/,$first); print "name $first has ".(scalar(@nfirst))." names\n"; #put a value onto number of names my $Numfirst = scalar(@nfirst); #Now I am hopefully be elegantr here and captialise and space them my $newfirst=(); for ( my $i=0;$i < $Numfirst;$i++) { #print "name part is ".ucfirst $nfirst[$i]."\n"; $newfirst .= join(" ",(ucfirst $nfirst[$i]) ); } print "new first name is $newfirst\n"; #doing it as per perldoc my $NewFirst= join(" ", 'Billy', 'Bob', 'Allan'); print "new first name is $NewFirst\n";

Any suggestions as to why my code does not work as expected

David

Replies are listed 'Best First'.
Re: trying to use join in a loop
by Corion (Patriarch) on Jul 30, 2018 at 12:31 UTC

    You don't show us what output you get, and what output you expect, so it's somewhat hard to give you concrete advice. If you post an example and tell us where you think it goes wrong, that makes it much easier for us to address your actual misunderstandings.

    join takes a list, so you don't need a for loop to build up the string from @nfirst.

    If you call join with only one parameter, it will not prepend or append a space, so this line makes little sense:

    $newfirst .= join(" ",(ucfirst $nfirst[$i]) );

    A good approach here would be to first convert all first names to ucfirst using map:

    @nfirst = map { ucfirst $_ } @nfirst;

    ... then you can use join directly:

    my $NewFirst = join " ", @nfirst;

    You can also do this in one go:

    my $NewFirst = join " ", map { ucfirst $_ }, split /\s+/, $first ;
Re: trying to use join in a loop
by Eily (Monsignor) on Jul 30, 2018 at 13:23 UTC

    In this particular case, using join and map as proposed in other answers is the most straightforward way to obtain the correct result, but for your information, in other places where you would need a for loop, you can access the values directly rather than use the index. For example:

    for (my $i=0;$i < $Numfirst;$i++) { print "Hello $nfirst[$i]\n"; }
    Can be written as:
    for my $name (@nfirst) { print "Hello $name\n"; }

    And if the content of the loop is a single statement perl allows you to put the for afterwards, by putting the value in the default variable $_:
    print "Hello $_\n" for @nfirst;

    Edit: added missing \n to have three times the same output.

Re: trying to use join in a loop
by haj (Vicar) on Jul 30, 2018 at 12:46 UTC

    An easy (but not the best) way to fix the error is to use a concatenation instead of join:

    # $newfirst .= join(" ",(ucfirst $nfirst[$i]) ); $newfirst .= " " . ucfirst $nfirst[$i];

    There seems to be some confusion between arrays and scalars in your code. $newfirst is a scalar, so initializing it with () doesn't do anything. join takes a separator and a list as arguments and puts the separator between adjacent elements of the list, but you feed it one element at a time so there never are adjacent elements.

    One more idiomatic solution would be to use map instead of looping through the list of names (in fact, there's not many cases where you need to write C-style loops in Perl), like this:

    use strict; #except to test my code #do the fancy stuff here for multiple Christian names #count how many firstnames #first would have been read in from a text file, but is explicit here my $first = "billy bob allan"; my @nfirst = (); @nfirst = split (/\s/,$first); print "name $first has ".(scalar(@nfirst))." names\n"; my @newfirst = map { ucfirst } @nfirst; print "new first name is @newfirst\n";

    You could also do it in one go with a regex, which for the moment I leave as an exercise to the reader :)

Re: trying to use join in a loop
by BillKSmith (Monsignor) on Jul 30, 2018 at 19:24 UTC
    I prefer to make all the edits with a single regexp.
    %type lamasulo.pl use strict; use warnings; use Test::More tests=>1; my $first = "billy bob allan"; $first =~ s/(\w+)/ucfirst($1)/ge ; ok( $first eq 'Billy Bob Allan', 'Caps' ) ; %perl lamasulo.pl 1..1 ok 1 - Caps %
    Bill
Re: trying to use join in a loop
by kcott (Archbishop) on Jul 31, 2018 at 09:42 UTC

    G'day David,

    You can use various features of Perl to write that very succinctly:

    $ perl -E 'say "Name: @{[map ucfirst, split]}" while <>' billy bob allen Name: Billy Bob Allen abc pqr wxyz Name: Abc Pqr Wxyz

    Features (some of which you probably already know about):

    • $_: used in various places here; the first is the line read by while.
    • split: defaults to splitting $_ on whitespace (it's a little more involved than that — see doco link for full details).
    • ucfirst: defaults to operating on $_.
    • Baby cart: that's the @{[...]} part — interpolates a list within a string.
    • $": list separator — defaults to space separation.
    • say: print plus newline; requires 5.10 or later.

    — Ken

Re: trying to use join in a loop
by lamasculo (Novice) on Jul 30, 2018 at 12:42 UTC

    Sorry did not make it clear what the output is. after the loop has completed and line

    my $newfirst=(); for ( my $i=0;$i < $Numfirst;$i++) { #print "name part is ".ucfirst $nfirst[$i]."\n"; $newfirst .= join(" ",(ucfirst $nfirst[$i]) ); } print "new first name is $newfirst\n";

    results in BillyBobAllan whereas code

    my $NewFirst= join(" ", 'Billy', 'Bob', 'Allan'); print "new first name is $NewFirst\n";

    results in Billy Bob Allan

Re: trying to use join in a loop
by lamasculo (Novice) on Jul 30, 2018 at 13:22 UTC

    Thank you to both Haj & Corion for their responses and time; the map solution by Haj gives me exactly what I am looking for as a result. regards David