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

Hi monks,

I have a script to retrieve some data from database and fill in a excel file:

..... ..... my $retrieve_ref = $sbh->fetchall_arrayref; map {$sheet->Range('A'.$count.':B'.$count)->{'Value'} = $_; $count +++;} @$retrieve_ref;

My question is I'm tired to write 'A'.$count.':B'.$count, I wanna define a variable which value could be changed dynamically according to value of $count.(Yes, I can say:)

map {my $range = 'A'.$count.':B'.$count; $sheet->Range($range)->{'Valu +e'} = $_; $count++;} @$retrieve_ref;
But I think it's also ugly.

Just some wild thought, hope gurus could give insights!


I am trying to improve my English skills, if you see a mistake please feel free to reply or /msg me a correction

Replies are listed 'Best First'.
Re: change value in run time
by kyle (Abbot) on Oct 23, 2008 at 16:08 UTC
    { my $r = sub { my $count = shift; return 'A' . $count . ':B' . $count; }; for ( @{$retrieve_ref} ) { $sheet->Range( $r->( $count++ ) )->{'Value'} = $_; } }

    The main idea here is to make a sub that makes the value you want out of the variable you have. Since the sub is of such little use anywhere else, I've put it in a lexical scoped to a small block. I personally don't like map in void context, so I changed the loop to a for loop. There are, as always, ways to go extra terse here, but I was shooting for a modicum of readability. Gum it up however you like in your own code.

      Thanks!
      I've read Higher-order perl several times, It seems I don't really understand it yet.

      I am trying to improve my English skills, if you see a mistake please feel free to reply or /msg me a correction
Re: change value in run time
by JavaFan (Canon) on Oct 23, 2008 at 16:29 UTC
    Well, you can do it in a sub, and call the sub with $count as an argument, or use a tied variable, or even an overloaded constant. But somewhere, you need to write down the logic anyway, so why not keep it in the loop? Now, I'd use interpolation instead of concatenation. I'd write something like:
    my $retrieve_ref = $sbh->fetchall_arrayref; for (my $count = 0; $count < @$retrieve_ref; $count++) { $sheet->Range("A$count:B$count")->{Value} = $$retrieve_ref[$count] +; }
    Or you could do something whacky:
    { package WakkaWakka; our @ISA = (ref $sheet); sub Range {$_[0]->SUPER::Range("A$_[1]:B$_[1]");} bless $sheet, __PACKAGE__; }
    and then do the inner loop call as:
    $sheet->Range($count)->{Value} = $$retrieve_ref[$count];
Re: change value in run time
by Corion (Patriarch) on Oct 23, 2008 at 19:03 UTC

    If the main job is retrieving a query and exporting it as an Excel file, I'm very fond of Querylet, which mostly does Just That. It has its drawbacks, as it is a source filter, but it can write Excel files through both, Spreadsheet::WriteExcel (which is slow and uses lots of memory, but works without Excel installed) and native Excel through Win32::OLE (which is fast but requires Excle to be installed).

Re: change value in run time
by ikegami (Patriarch) on Oct 23, 2008 at 18:40 UTC

    But I think it's also ugly.

    Then spread it out a bit.

    map { my $range = 'A'.$count.':B'.$count; $count++; $sheet->Range($range)->{'Value'} = $_; } @$retrieve_ref;

    Your useless use of map isn't helping. Let's get rid of it

    for ( @$retrieve_ref ) { my $range = 'A'.$count.':B'.$count; $count++; $sheet->Range($range)->{'Value'} = $_; }

    The latter is even more efficient.

Re: change value in run time
by mje (Curate) on Oct 23, 2008 at 16:21 UTC

    It is not what you asked (I see someone else has stepped in there) but are you sure you want to assign elements of @$retrieve_ref? I thought fetchall_arrayref returned a reference to an array where each element was a reference to an array of row values. e.g., for select * from table where table has 2 fields in it I thought fetchall_arrayref returns:

    [[row1field1, row1field2], [row2field1, row2field2]...]
      but are you sure you want to assign elements of @$retrieve_ref?
      ah, yes. Because you can assign a array reference to a excel Range object like this:
      $sheet->Range('A1:B1')->{'Value'} = [$name, $last_time];

      I am trying to improve my English skills, if you see a mistake please feel free to reply or /msg me a correction
Re: change value in run time
by Tanktalus (Canon) on Oct 23, 2008 at 22:39 UTC

    And, just for some laughs ... another way to do it, using the all-too-magical Contextual::Return:

    #!/usr/bin/perl use strict; use warnings; use Contextual::Return; my $range = do { my $count = 1; ACTIVE STR { RESULT { "A${count}B${count}" }; $count++ } }; for (@$retrieve_ref) { $sheet->Range($range)->{Value} = $_; }
    Though, to be honest, that's a lot of all-upper-case code.