Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl Monk, Perl Meditation
 
PerlMonks  

Joining an array

by tangent (Parson)
on Feb 11, 2012 at 00:21 UTC ( [id://953148]=perlquestion: print w/replies, xml ) Need Help??

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

I have searched for an answer to this simple question but unable to find it. Basically I have an array like so:
(name,John,number,7,status,unknown)
and I want to join that array to get a string like:
name=John&number=7&status=unknown
I need to do this as efficiently as possible and preserve the order. I have a feeling it would involve join and splice but don't really know how to combine the two.

Replies are listed 'Best First'.
Re: Joining an array
by roboticus (Chancellor) on Feb 11, 2012 at 00:29 UTC

    tangent:

    The List::MoreUtils library has the function natatime that helps things like this. Example:

    $ cat foo.pl use strict; use warnings; use List::MoreUtils qw(natatime); my @t = qw(name John number 7 status unknown); my @u; my $it = natatime 2, @t; while (my @vals = $it->()) { push @u, join("=",@vals); } print join("&",@u),"\n"; $ perl foo.pl name=John&number=7&status=unknown

    ...roboticus

    When your only tool is a hammer, all problems look like your thumb.

      Thanks roboticus. I have seen that module mentioned a number of times, will look into it. I also remember spending many hours building an iterator a la Mark Jason Dominus in Higher Order Perl -- Chapter 4: Iterators. You have just shown me a use for my labour.
Re: Joining an array
by BrowserUk (Patriarch) on Feb 11, 2012 at 00:37 UTC
    use List::Util qw[ reduce ]; my $ff=1; print reduce{ $a .= ( ( $ff ^= 1 ) ? '&' : '=' ) . $b } qw(name John number 7 status unknown);; name=John&number=7&status=unknown

    With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.

    The start of some sanity?

      I'm going to show that to my girlfriend.

        Dear Sir,

        Please be advised that, in keeping with current trends in social networking site norms, we unilaterally, and bindingly, and in absentis of imprimis auctorita, make the highlighted changes to our terms of service. This change to our entirely one-sided, but legally binding contract1, is made with immediate effect, and will be applied retro-actively since the dawn of time2.

        7. Limitation of Liability

        You understand and agree that BrowserUk and any of its subsidiaries or affiliates shall in no event be liable for any direct, indirect, incidental, consequential, or exemplary damages. This shall include, but not be limited to damages for loss of profits, business interruption, business reputation or goodwill, loss of programs or information, loss of goodwill and/or sexual favours. be they pre-marital, marital or extra-marital, being temporary or permanent, or other intangible loss arising out of the use of or the inability to use the service, or information, or any permanent or temporary cessation of such service or access to information, or the deletion or corruption of any content or information, or the failure to store any content or information. The above limitation shall apply whether or not BrowserUk has been advised of or should have been aware of the possibility of such damages. In jurisdictions where the exclusion or limitation of liability for consequential or incidental damages is not allowed the liability of BrowserUk is limited to the greatest extent permitted by law.

        1Unless you have the cash to take on a corporation with $60 billion in the bank.

        2a.k.a: !991.


        With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        "Science is about questioning the status quo. Questioning authority".
        In the absence of evidence, opinion is indistinguishable from prejudice.

        The start of some sanity?

        Well, then, you're definitely getting lucky tonight!

Re: Joining an array
by tobyink (Canon) on Feb 11, 2012 at 00:44 UTC

    This does the trick...

    use Modern::Perl; my @t = qw(name John number 7 status unknown); my $str = ''; while (@t) { $str .= sprintf('%s=%s', shift @t, shift @t); $str .= '&' if @t; } say $str;

    Though if what you're trying to do is construct a query string for a URI, you want to use URI::Escape, which will escape any special characters in your string. (For example, what if one of the strings in your array already contains an equals sign?)

    use Modern::Perl; use URI::Escape qw/uri_escape/; my @t = qw(name John number 7 status unknown); $t[1] = 'John Smith'; # something that needs escaping my $str = ''; while (@t) { $str .= sprintf( '%s=%s', uri_escape(shift @t), uri_escape(shift @t), ); $str .= '&' if @t; } say $str;

    It's a shame you said that the order matters. If it didn't, then casting your array to a hash would be a neat trick:

    use Modern::Perl; use URI::Escape qw/uri_escape/; my @t = qw(name John number 7 status unknown); my %th = @t; # cast to hash my $str = join '&', map { sprintf '%s=%s', uri_escape($_), uri_escape($th{$_}) } keys %th; say $str;
      Didn't you notice that the keys are in alfabetical order? If you just sort the keys your casting trick will work.

      CountZero

      A program should be light and agile, its subroutines connected like a string of pearls. The spirit and intent of the program should be retained throughout. There should be neither too little or too much, neither needless loops nor useless variables, neither lack of structure nor overwhelming rigidity." - The Tao of Programming, 4.1 - Geoffrey James

      I take care of the escaping at an earlier stage (I should have put & in there as that is what I actually use but didn't format it properly in the post). The sprintf syntax has always confused me but I can see clearly here how it works. Very nice. Just wondering how concatenation compares with the other methods above performance wise as I'll be doing this up to a thousand times a go. I'll go and benchmark it.

        Generally speaking I prefer sprintf over interpolation ("$foo=$bar") except in very trivial cases.

        When you need to put $foo and $bar into a string, interpolation is fine. But if you need to put $foo->[1]{name} and encode_entities($bar->get_url("print")) into a string, sprintf looks much better:

        sprintf( '<a href="%s">%s (printable version)</a>', encode_entities($bar->get_url("print")), $foo->[1]{name}, );

        Bear in mind that if you're outputting this URL in HTML, there are two types of escaping you need; and you need to do them both at the appropriate stage.

        use Modern::Perl; use URI::Escape qw/uri_escape/; use HTML::HTML5::Entities qw/encode_entities/; my @t = qw(name John number 7 status unknown); my %th = @t; # cast to hash my $url = 'show_person.cgi?' . join '&', map { sprintf '%s=%s', uri_escape($_), uri_escape($th{$_}) } keys %th; printf( '<a href="%s">%s</a>', encode_entities($url), encode_entities($th{name}), );

        That is, URI escaping (which deals with things like space being encoded as %20) needs to be done to each component of the URI, but not the URI as a whole. And HTML entity encoding (which deals with things like "&" becoming "&amp;") needs to be done to all strings used in the HTML.

        That assumes you're building your HTML using string processing, which is what many people do. If you're instead building it using a DOM library (e.g. XML::LibXML, HTML::HTML5::Builder, etc) then the library should take care of the HTML entity encoding, but it won't attempt URI escaping.

Re: Joining an array
by ww (Archbishop) on Feb 11, 2012 at 01:47 UTC

    So, "why?" /me said to myself, "Why can't Perl chew on this?"

    my $i = 0; my $str = "$arr[$i]=$arr[++$i]" x($#arr/2);

    or this?

    my $i = 0; my $str = "$arr[$i]=$arr[++$i]" x($#arr/2) . "&";

    Now, of course the first snippet left the question of inserting the ampersands un-resolved, but that's OK, because Perl wants an int after the x operator and 5/2 isn't likely to ever be an int. Then, the second suffers the same defect ... and inserts the ampersand only AFTER printing the second name=John; a 'gotcha' for this nutty idea that I don't see a way around (in this approach).

    But, the idea perked on, leading to this, just because TIMTOWTDI:

    #!/usr/bin/perl use strict; use warnings; use 5.014; # desired: name=John&number=7&status=unknown my @arr = qw(name John number 7 status unknown); my $i = 0; my $str = "$arr[$i]=$arr[++$i]"; $str .= "&$arr[++$i]=$arr[++$i]"; $str .= "&" . "$arr[++$i]=$arr[++$i]"; say $str;

    Output? As specified.

      Thanks ww. I should have mentioned though that there are a variable number of elements in the array.
        Variable number of elements is not a problem; it's just a circumstance that demands a little more code....

        So, let's see... . o O

        We know how to count the elements, $#arr... so that means we'll need to repeat the next-to-last line $#arr/2 times.

        Oops. Not an integer. Well, we knew that already too, but after we've printed the first pair, we should need to repeat either of the next two lines ( ($#arr/2) -1 ) times. Or we could start with ( ($#arr +1)/2 ). Either way, Shazam! The problem of an unknown number of pairs in the array is (almost) solved, except for the trivial act of actually writing the code.

        Do you smell a loop coming? I do.

        Again, this is a case of TIMTOWTDI that's NOT worth pursuing, except for the mental exercise (or maybe, an obfuscation contest). The solutions with a module that's designed to do just what you want are clearly the way to go in the real world.

        It does, however, have a smidgen of value for readers here in the Monastery: it emphasizes the need for precision and completeness in problem statements.

Re: Joining an array
by johngg (Canon) on Feb 11, 2012 at 13:37 UTC

    Another splice method with two joins, one in a map, and an on-the-fly subroutine.

    knoppix@Microknoppix:~$ perl -E ' > @arr = qw{ > name > John > number > 7 > status > unknown > }; > $str = > join q{&}, > map { join q{=}, @$_ } > sub { > push @ret, [ splice @_, 0, 2 ] while @_; > return @ret; > }->( @arr ); > say $str;' name=John&number=7&status=unknown knoppix@Microknoppix:~$

    Cheers,

    JohnGG

Re: Joining an array
by JavaFan (Canon) on Feb 11, 2012 at 17:24 UTC
    sub FETCH {${$_[0]}++%2?"=":"&"} sub TIESCALAR {bless\my$i} tie $", "main"; my @array = qw[name John number 7 status unknown]; say "@array"; __END__ name=John&number=7&status=unknown
      How this code works? $" is the separator used when arrays are extrapolated in certain contexts (like in say "@array"), tie-ing it to 'main' means that it will be implemented in the scope of the main program. I'm not sure about bless\my$i and what ${$_[0]} is referring to when invoked in FETCH.
      Give a man a fish and you feed him for a day. Give him a fishnet and you feed him for a lifetime. Ooops! Wait a minute! What if the fishnet brakes down? He will ask for an other fishnet or worse starve to death. Better yet to teach him how to make a fishnet. But what if he forgets how to make a fishnet? It is better to teach him where to look for knowledge on how to make a fishnet. But this can also go wrong. So ultimately it is best just telling him: 'Help yourself, stupid!'
        When you tie a variable, the TIE* method, in this case TIESCALAR, has to return an object. An object is nothing more than a blessed reference. \my $i is a reference to the variable $i. bless blesses its argument. If bless is given just one argument, it blesses the reference to the current package.

        The first argument to the FETCH method is the object returned from bless. $_[0] is the first argument of a method, and ${EXPRESSION THAT RETURNS A SCALAR REFERENCE} is a way to dereference a scalar reference. So, you get back the $i.

        tie, perltie

        Update: What happens is that the main package is (ab)used as a class implementing a tied scalar for $". Whenever perl wants to use the value of the tied scalar $", it calls the object method FETCH instead, in this case, main::FETCH. And that method returns either "=" or "&", depending on how often it was called before. The modulo operator % switches between the two values with every call. To construct the object on which FETCH will be invoked, tie calls the TIESCALAR class method that returns a blessed reference to a scalar value, that value is used to count the calls inside FETCH.

        Alexander

        --
        Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)
Re: Joining an array
by trizen (Hermit) on Feb 11, 2012 at 11:36 UTC
    Here is a splice + join version...
    my @t = qw(name John number 7 status unknown); my $i = -1; foreach my $x (1 .. $#t) { splice(@t, $i += 2, 0, $x % 2 ? '=' : '&'); } print join '', @t;
      A faster way:
      my @array = qw(name John number 7 status unknown); my $i = -2; my $x = $#array - 1; my $str = ''; while (1) { if ($i + 3 < $x) { $str .= $array[$i += 2] . '=' . $array[$i + 1] . '&'; } else { $str .= $i + 3 == $x ? $array[-3] . '=' . $array[-2] . '&' . $arra +y[-1] : $i + 2 == $x ? $array[-2] . '=' . $array[-1] : $array[-1]; last; } } print "$str\n";
Re: Joining an array
by nemesdani (Friar) on Feb 11, 2012 at 20:09 UTC
    An idea, if you construct the array in the first place: Maybe you should consider using a hash instead of an array. Makes this problem much more easy.
      Yes I am using a hash at the moment and it works fine. But as my dataset has grown performance has become an issue, and I notice I am constantly looping over hashes and arrays. EDIT: looking again I see am using both a hash and an array - the array purely to keep the order.
      The OP said he wants to preserver order. How do you suggest to do that using a hash, which considering the efficiency request from the OP?
Re: Joining an array
by Anonymous Monk on Feb 11, 2012 at 07:40 UTC

    Batman!

    #!/usr/bin/perl -- use strict; use warnings; use CGI; use URI; print join "\n", CGI->new( { @$_ } )->query_string, Yo( @$_ ), Yuri( @$_ ), , "\n" foreach [qw( name John number 7 status unknown )], [qw( a >&< q ];[ arf =&= )]; exit 0; sub Yuri { use URI; my $u = URI->new; $u->query_form( @_ ); $u->query ; } sub Yo { use CGI; #~ use CGI qw/ -oldstyle_urls /; local $CGI::USE_PARAM_SEMICOLONS=0; my $q = CGI->new; for( my $i = 0; $i < @_; $i +=2 ){ $q->param( $_[$i], $_[$i+1] ); } $q->query_string; } __END__ number=7;status=unknown;name=John name=John&number=7&status=unknown name=John&number=7&status=unknown a=%3E%26%3C;q=%5D%3B%5B;arf=%3D%26%3D a=%3E%26%3C&q=%5D%3B%5B&arf=%3D%26%3D a=%3E%26%3C&q=%5D%3B%5B&arf=%3D%26%3D
Re: Joining an array
by chessgui (Scribe) on Feb 11, 2012 at 07:42 UTC
    Here is a one liner without any module:
    @array=qw(name John number 7 status unknown); print join('',(map {!($_%2)?"$array[$_]=".$array[$_+1]:$_<$#array?'&': +'';}(0 .. $#array)));
      Improved version:
      @a=qw(name John number 7 status unknown); print join('',(map {$a[$_].($_%2?'&':'=');}(0 .. $#a-1))).$a[$#a];
      Give a man a fish and you feed him for a day. Give him a fishnet and you feed him for a lifetime. Ooops! Wait a minute! What if the fishnet brakes down? He will ask for an other fishnet or worse starve to death. Better yet to teach him how to make a fishnet. But what if he forgets how to make a fishnet? Better yet to teach him where to look for knowledge on how to make a fishnet. But this can also go wrong. So ultimately it is best just telling him: help yourself. If you want to make it sure add 'stupid' and an exclamation mark, like this: 'Help yourself, stupid!'
Re: Joining an array
by Anonymous Monk on Feb 11, 2012 at 14:05 UTC
    #!/usr/bin/perl -- use strict; use warnings; local $\ = $/; my @f = qw(name John number 7 status unknown); print do { my $i = -2; join q/&/, map { $i += 2; join q/=/, $f[$i], $f[ $i + 1 ] } 0 ..( +$#f / 2 ); }; print join q/&/, map { $_ % 2 ? () : join q/=/, $f[$_], $f[ $_ + 1 ] } + 0 .. $#f; print join q/&/, map { $_ % 2 ? () : join q/=/, $f[$_], $f[ $_ + 1 ] } 0 .. $#f; __END__ name=John&number=7&status=unknown name=John&number=7&status=unknown name=John&number=7&status=unknown

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://953148]
Approved by BrowserUk
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others exploiting the Monastery: (4)
As of 2024-04-24 21:56 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found