http://www.perlmonks.org?node_id=1067029

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

You have a (compound) variable that you want to increment and then modulate (ie. bound with modulus some number):

@a = ( 1 .. 10 );; $a[$_] = ( $a[$_]+1 ) % 10 for 0 .. 9;; print @a;; 2 3 4 5 6 7 8 9 0 1

Instinct wants ++$a[$_] %= 10 for 0 .. 9; to work. It doesn't.

Of course there is $a[$_] = ++$a[$_] % 10 for 0 .. 9;; which works, but is hardly better, especially when $a[$_] might be more like:

$things{ $thing }{$someotherkey}[$someindex]{$somekey}[ $_ ]
.

A better way?


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.

Replies are listed 'Best First'.
Re: A better (ie.more concise) way to write this?
by hdb (Monsignor) on Dec 13, 2013 at 15:53 UTC

    Not sure this is better but I like the look of it:

    ( $a[$_] += 1 ) %= 10 for 0..9;
      maybe better readable with a custom function in case of deeply nested HoHoH...

      DB<132> sub cycle { $_[1]++; $_[1] %= $_[0] } DB<133> cycle 3 => $h{a}{b}[2]{c}; \%h => { a => { b => [undef, undef, { c => 1 }] } } DB<134> cycle 3 => $h{a}{b}[2]{c}; \%h => { a => { b => [undef, undef, { c => 2 }] } } DB<135> cycle 3 => $h{a}{b}[2]{c}; \%h => { a => { b => [undef, undef, { c => 0 }] } }

      unfortunately does Perl have no autoboxing, to allow:

      $h{a}{b}[2]{c}->cycle(3)

      edit

      Ha, it can be emulated with ano-subs! =)

      DB<160> $cycle = sub { $_[0]++; $_[0] %= $_[1] } => sub { "???" } DB<161> $h{a}{b}[2]{c}->$cycle(3); \%h => { a => { b => [undef, undef, { c => 1 }] } } DB<162> $h{a}{b}[2]{c}->$cycle(3); \%h => { a => { b => [undef, undef, { c => 2 }] } } DB<163> $h{a}{b}[2]{c}->$cycle(3); \%h => { a => { b => [undef, undef, { c => 0 }] } }

      Cheers Rolf

      ( addicted to the Perl Programming Language)

      That'll do nicely. Thank you.


      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.

        Why not this:

        for ( @a ) { ++$_; $_ %= 10 }

        And if you really want to process only the first 10, you can still use the slice:

        for ( @a[0..9] ) { ++$_; $_ %= 10 }


        A for will get you from A to Z; a while will get you everywhere.
Re: A better (ie.more concise) way to write this?
by roboticus (Chancellor) on Dec 13, 2013 at 17:13 UTC

    BrowserUk:

    I'd do it like this:

    @a = map { ++$_ % 10 } @a;

    If I'm missing the point and you're explicitly wanting to only alter a subset of the items, a slice'll adapt it:

    @a = map {++$_ % 10 } @a[0-5,7];

    Update: If the point is to modify it in place, I like the AM post above...

    ...roboticus

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

      Watch out, because

      @b = map { ++$_ % 10 } @a;

      modifies both @a and @b.

      You can also do something like this:

      $_ = ($_+1) % 10 for @a[3..7];
      I'd do it like this:@a = map { ++$_ % 10 } @a;

      That pretty much defeats the purpose. Instead of:

      $things{ $thing }{$someotherkey}[$someindex]{$somekey}[ $_ ] = $things +{ $thing }{$someotherkey}[$someindex]{$somekey}[ $_ ] + 1 % 10 for 1 +.. 10;

      You get:

      @{ $things{ $thing }{$someotherkey}[$someindex]{$somekey} }[ 1 .. 10 ] + = map{ ++$_ % 10 } @{ $things{ $thing }{$someotherkey}[$someindex]{$ +somekey} }[ 1 .. 10 ];

      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.
        Then , if you want to modify an existing data structure, what about using the fact that modifying $_ will modify the original array? Something like this, shown under the debugger:
        DB<1> @a = 1..9; DB<2> map{ $_ = ++$_% 10 } @a; DB<3> x @a 0 2 1 3 2 4 3 5 4 6 5 7 6 8 7 9 8 0 DB<4>
        Well, this obviously works, but something like $c = ++$c is, I believe, not defined in C and probably also not in Perl (i.e. the implementor if free to do whatever). It could be changed to:
        map { $_ ++; $_ = $_ % 10} @a;
        This would probably be more secure against any change of implementation.
Re: A better (ie.more concise) way to write this?
by Anonymous Monk on Dec 13, 2013 at 16:38 UTC
    $_ = ($_+1) % 10 for @a;
Re: A better (ie.more concise) way to write this?
by johngg (Canon) on Dec 14, 2013 at 12:47 UTC

    Not as succinct as some of the other answers but possibly of interest.

    $ perl -Mstrict -Mwarnings -E ' my @a = ( 1 .. 10 ); sub transform (&@); @a = transform { ( ++ $_ ) % 10 } @a; say qq{@a}; sub transform (&@) { map $_[ 0 ]->( $_ ), @_[ 1 .. $#_ ] }' 2 3 4 5 6 7 8 9 0 1 $

    Update: Much more concise if you make the transform subroutine operate on $_ by default.

    $ perl -Mstrict -Mwarnings -E ' my @a = ( 1 .. 10 ); sub transform (&); transform { ( ++ $_ ) % 10 } for @a; say qq{@a}; sub transform (&) { $_[ 0 ]->( $_ ) }' 2 3 4 5 6 7 8 9 10 11 $

    Update 2: Just noticed that isn't working properly so will have to investigate why :-(

    Update 3: Got it!

    $ perl -Mstrict -Mwarnings -E ' my @a = ( 1 .. 10 ); sub transform (&); transform { $_ = ( ++ $_ ) % 10 } for @a; say qq{@a}; sub transform (&) { $_[ 0 ]->( $_ ) }' 2 3 4 5 6 7 8 9 0 1 $

    Cheers,

    JohnGG

Re: A better (ie.more concise) way to write this?
by wind (Priest) on Dec 14, 2013 at 00:56 UTC
Re: A better (ie.more concise) way to write this?
by wazat (Monk) on Dec 14, 2013 at 05:00 UTC

    assuming the elements are in the range 0 to 9 to start with you could also do

    ++$_ < 10 || ( $_ -= 10 ) for @a;

    but I see that your original array has 10 as an element

Re: A better (ie.more concise) way to write this?
by sundialsvc4 (Abbot) on Dec 17, 2013 at 00:31 UTC

    Now the dust has settled on this one, I have a question ... yes, a question ...

    My solution to this problem came up more-or-less like this:

    perl -e 'use strict; use warnings; my @a = ( 1..10 ); foreach (@a) { $_ = ( $_ + 1 ) % 10; }; use Data::Dumper; print Data::Dumper->Dump([\@a], ["a"]);' $a = [ 2, 3, 4, 5, 6, 7, 8, 9, 0, 1 ];

    In other words, using the foreach() construct to iterate through the list/array, with $_ being an automatic alias to each entry in succession, rather than actually indexing into the array.   Intuitively, this seems to me that this would be “more efficient.”   Is it?

    Also... among the various solutions that were proffered in this thread, how much difference in timing are we actually talking about here?