imputing index number in an array - arrays

I have 7 arrays that are defined in the following manner:
my #array1 = ();
..
my #array7 = ();
and then:
$array1[0] = "text goes here";
..
$array7[0] = "text goes here";
There are about 25 elements) in each of the seven arrays, that is, $array1[0] to $array1[24]. I need to frequently change the content of these arrays in various scripts. And sometimes, because the order of the arrays is essential, I need to rearrange the order of the array index or delete the elements at a position. This is a real pain in the ass, as I need to change the index of all subsequent arrays. Just to make it clear, if I delete array1[12], then I need to change $array1[13] to $array1[12] and that for all 7 arrays and for all subsequent index positions (or move the content of array1[13] to array1[12], etc.)
So my question is, is it possible to impute the index of the arrays so that I could switch around the arrays position without having to correct each array index afterwards? Something like this:
$array1[$_] = "text 1 goes here";
..
$array7[$_] = "other text 1 goes here";
and then:
$array1[$_] = "text 2 goes here";
..
$array7[$_] = "other text 2 goes here";
where $_ would be replaced by 1 for the first index of each of the 7 arrays and by 2 for the next element of each of the 7 arrays... (up to 24 elements).
Is there a solution to this problem other than using an hash and Tie::Hash?
EDIT
ok, let me clarify. I am looking for a script maintenance solution, no for a solution about the output of the script. I need to change the script myself (by hand) frequently and I do not want to change the numbers indexing all 24 positions of all 7 arrays by hand whenever I change something in these arrays. So my question was, is there a way to have the script impute the numbers indexing all positions of all arrays?
Using push as mvp was suggesting would be a proper solution. Are there any other solutions that could involve loops or something rather than using push 7X24 times?

Not quite sure what your question is. You are probably complicating things quite a bit. Are you looking for splice?
my #a = (1 .. 4);
splice #a, 2, 1; # remove 1 element, starting at index 2
# #a is now 1, 2, 4
splice can also insert elements and remove more than one element:
splice ARRAY or EXPR, OFFSET, LENGTH, LIST

You can use this approach:
my #array1;
push #array1, "text 1 goes here";
push #array1, "other text 1 goes here";
# ...
# or you can use loop as well:
for my $i (1..10) {
push #array1, "text 1 instance $i goes here";
}
# do it for another one:
my #array2;
push #array2, "text 2 goes here";
push #array2, "other text 2 goes here";
# ...
You can even do this (little bit nasty because of dynamic variables):
for my $i (1..7) {
for my $j (1..24) {
push #{"array$i"}, "text $i, instance $j goes here";
}
}

Related

Perl: How to delete elements from array without spaces

How to delete the elements of array from index 4 to 10. I am doing like this
#!usr/bin/perl
#array=1..10;
#array[0..3]=#array[5..8];
#array[4..10]=();
$str=(join ",",#array);
print "$str\n";
and getting output as
6,7,8,9,,,,,,,
How to remove extra ","?
You're doing a bit more than trying to delete elements of the array in that code...
Anyways, you want splice to remove a contiguous range of array elements:
splice #array, 4, 6;
I'm assuming this is some kind of homework question, because if it's not you're doing some REALLY strange things with this code.
But let's take the problem at hand - the easiest way to filter an array is with grep
my #no_empty = grep { defined } #array;
print join ( ",", #no_empty )
In this we use grep to filter out any undef array elements. The element is returned if the expression in the braces evaluates as true - $_ is set to the current element, and used in any tests that implicitly work on $_. defined does, as does regular expression matches.
However if you genuinely just want to truncate the array to an arbitrary number, you can just do:
#array = #array[0..4]
You start with this array:
my #array = 1..10;
Now you have an array of 10 elements.
1 2 3 4 5 6 7 8 9 10
In your next step, you perform an array slice then assign that to another array slice:
#array[0..3] = #array[5..8];
This leaves alone the elements from 5 to 8, but does a list assignment of their values to the elements from 0 to 3, so you end up with some duplicated elements:
6 7 8 9 5 6 7 8 9 10
Now you want to get rid of everything after index 4. You try that with another list assignment:
#array[4..10] = ();
However, that just assigns the list on the right to the list on the right. As with any list assignment, the left hand elements get their corresponding elements from the right hand list. If the right hand list doesn't have enough elements, Perl uses undef for the rest of the right hand elements. That's why you still have 10 elements in #array:
6 7 8 9 undef undef undef undef undef undef undef
If you want 4 to be the last index, you can assign to the last index of the array. Whatever number you assign becomes the last index, either shortening or extending the array. Since you wanted to get rid of elements 4 and beyond, you can set the last index to 3 (the index right before the one you want to remove):
$#array = 3;
Now you've truncated the array to four elements:
6 7 8 9
You can also do this with splice, which replaces part of an array:
splice #array, $start_index, $length, #replacement;
You might use the starting index to figure out the length since you want to go all the way to the end. Then, replace that portion of the array with the empty list, which effectively makes the array shorter:
my $start = 4;
splice #array, $start, #array - $start, ();
Leaving off the replacement list is the same as the empty list:
splice #array, $start, #array - $start;
This is much more handy when you want to remove parts in the middle. This removes three elements starting at index 4 (so there will be stuff left over at the end):
splice #array, 4, 3;
Now your array has elements that were at the beginning and end of your array:
6,7,8,9,8,9,10
Without shortening the array
There's another sort of problem. You don't want to change the array, but you don't want to deal with empty fields. You can use a grep to select only the defined elements:
say join ',', grep { defined } #array;
If you have undefined elements in the middle of the array, this might be a problem if you expect columns to line up properly. Removing a column in the middle shifts the other columns. You may not care about that though.
Similarly, you might turn the undefined values into something that makes sense for the problem. A map can inspect the value and decide to pass it through or transform it. In this example, undef values turn into 0:
say join ',', map { defined ? $_ : 0 } #array;
6,7,8,9,0,0,0,0,0,0,0
Or "NULL":
say join ',', map { defined ? $_ : 'NULL' } #array;
6,7,8,9,NULL,NULL,NULL,NULL,NULL,NULL,NULL
Or even just undef as a string:
say join ',', map { defined ? $_ : 'undef' } #array;
6,7,8,9,undef,undef,undef,undef,undef,undef,undef
Sometimes those are handy to see what's going on.
And map can act like grep to filter an array. Use the empty list to pass on no elements from the map:
say join ',', map { defined ? $_ : () } #array;

Use of uninitialized value in array value

I am having an array named #Lst_1. One of my elements is 0 in array.
Whenever I call that element. In example below the value stored in second index of an array is 0.
$Log_Sheet->write(Row,0,"$Lst_1[2]",$Format);
I am getting a warning saying
Use of uninitialized value within #Lst_1 in string.
Please help me do that.
First index of array is 0. Second element will be $List_1[1];
#!/usr/bin/env perl
use v5.22;
use warnings;
my #array = qw(foo bar);
# number of elements in array
say scalar(#array);
# last index of array
say $#array;
# undefined element (warn)
say $array[ $#array + 1];
If you just want to silence the error,
$Log_Sheet->write(Row, 0, $Lst_1[2] // 0, $Format);
This does use a feature of perl 5.10, but that's ancient enough you really should be using a sufficiently new perl to have it. I mean, there's a lot of ancient perl bugs, so it behooves you to be using a newer version.
As far as understanding the issue, no, $Lst_1[2] doesn't contain a 0. It contains an undef, which just happens to be treated mostly like 0 in numeric contexts.
Yes, I did remove the quotes around $Lst_1[2] - that was necessary, because "$Lst_1[2]" treats that undef like a string, so it's become the empty string for the purpose of a "$Lst_1[2]" // 0 test. (The empty string also happens to be treated like 0, so that doesn't change the behavior in a numeric context.)
It's not clear from your short excerpt whether #Lst_1 is less than 3 elements long, or if there's an explicit undef in #Lst_1. You'd need to show a larger excerpt of your code - or possibly even the whole thing and the data it is processing - for us to be able to determine that by looking. You could determine it, however, by adding something like the following in front of the line you gave:
if (#Lst_1 < 3) {
print "\#Lst_1 only has " . #Lst_1 . " elements\n"
} elsif (not defined($Lst_1[2])) {
print "\$Lst_1[2] is set to undef\n";
}
There are two basic ways a list can have an explicit undef element in it. The following code demonstrates both:
my #List = map "Index $_", 0 .. 3;
$List[2] = undef;
$List[5] = "Index 5";
use Data::Dump;
dd #List;
This will output
("Index 0", "Index 1", undef, "Index 3", undef, "Index 5")
The first undef was because I set it, the second was because there wasn't a fifth element but I put something in the sixth slot.

size of array in perl script loop

I am using below commands to find the size of array
$size_x = #x;
or
$size_x = $#x+1 ;
Both work well when I use in simple statement. But when I use them in the loop, the size becomes one number bigger. Why it is happening in that way. Below is example:
for ($i=1;$i<=10;$i++){
if (1**2+2**2>=1){
#x[$i] =2+3;
$size_x = #x;
}
print "my size is $size_x\n";
}
here is results:
my size is 2
my size is 3
my size is 4
my size is 5
my size is 6
my size is 7
my size is 8
my size is 9
my size is 10
my size is 11
The answer should be from 1 to 10 instead of 2 to 11, i think. What is a better way to get size correctly? Thanks.
After reading your code, I honestly can't figure out what you're trying to do here, but if you're trying to create an array with 11 elements and assign all of them to 5 except for the first one, then you've done an excellent job. Perhaps it would help to see a visualization of the array you've created:
[undef, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5]
If that's really what you were hoping for, there are faster/simpler/better ways to do the same thing, but I have to believe you were trying to accomplish something else. At a very basic level, arrays in Perl are 0-indexed:
Normal arrays are ordered lists of scalars indexed by number, starting
with 0.
Having said that, you very rarely see the C-style for loop in Perl code, and that's because it's very rarely necessary. More often, you would see something like this:
for (0 .. 9) {
# do something...
}
That code uses a foreach-style loop and the range operator (..) to generate a sequence (technically, a list of values from left to right, counting up by one). Enough about loops. Let's talk about your strange logic.
12 + 22 will always be 5, which will always be greater than 1. The same goes for 2 + 3. There's nothing dynamic about this code, which is why I have to think that you meant to use the loop iterator in your calculations somehow. Otherwise, you could easily fill a 10-element array with a bunch of 5's in one line:
my #x = (5) x 10;
Now, one last point that applies to this code and all Perl code you write from this point forward. Every single file must include the following two lines at the top:
use strict;
use warnings;
I believe that Perl arrays are zero-based, meaning that the first element is stored and accessed at index 0. Here is your for loop:
for ($i=1; $i<=10; $i++) {
if (1**2+2**2>=1) {
#x[$i] =2+3;
$size_x = #x;
}
print "my size is $size_x\n";
}
When you make the first assignment #x[1] = 2 + 3, the array is considered to already have an (empty) element in position zero, so the size is returned as being 2.
The easiest way to avoid this problem from happening would be to refactor your for loop to start at index 0:
for ($i=0; $i<10; $i++) {
...
}
When you start the index from 1 the size automatically increases by 1. Try starting the index from 0 or
#x[$i-1] =2+3;
Just as a side note:
Array elements in Perl are denoted with a starting $, not an #.
So #x is the whole array, while $x[$i] is the $i'th element of the array #x.
use warnings; should warn about this.
(I know this is more a comment than an answer, but as a SO newbie I'm not yet allowed to comment. Sorry.)

How can I paginate a Perl array?

I don't know if the question is clear or not, anyway, I am reading some data from my google drive spreadsheet using Net::Google::Spreadsheet, and i fetch all the rows in an array as follow my #rows = $worksheet->rows, however, I want to ... let say divide #rows between other arrays, for example #rows has 200 elements, i want to give the first 50 to #array1 and the next 50 to #array2 and so on without using a counter (counter++; if (counter > 50) ...). Or let say i just want to get the elements between 70 and 110 for example.
You could use something like this:
my #array1 = #rows[0 .. 49];
my #array2 = #rows[50 .. 99];
This is referred to as data slicing. Refer to this documentation for more details.
The plain array slice answer is great. I just want to increase visibility of another (class/style of) solution, specifically Data::Page.
It has been said before that this code is "too simple" for CPAN, but I must disagree.
It can seem like overkill but it makes the stuff clean, predictable, and repeatable. When code starts to grow past the one-off stage, it can help quite a lot.
use strictures;
use Data::Page;
my #stuff = ( "a" .. "z" );
my $pager = Data::Page->new;
$pager->total_entries(scalar#stuff);
$pager->entries_per_page(6); # Arbitrary for demo, pick your own page size.
for my $p ( $pager->first_page .. $pager->last_page )
{
$pager->current_page($p);
for my $entry ( $pager->splice(\#stuff) )
{
print $entry, " is on page ", $pager->current_page, $/;
}
}
__END__
a is on page 1
--snip--
z is on page 5

Perl, A hash of arrays: adding and removing keys, adding to an array, all in a while loop

I have a hash which should contain certain keys which are linked to their own arrays. To be more specific, the hash keys are quality values and the arrays are sequence names. If there already is an array for that quality, I'd want to add the sequence name to the array that is linked to the quality in question. If there isn't one, I want to create one and add the sequence name to it. All this is done in a while loop, going through all the sequences one by one.
I've tried to do things like in Perl How do I retrieve an array from a hash of arrays? but I can't seem to get it right.
I just get these error messages:
Scalar value #{hash{$q} better written as ${hash{$q} at asdasd.pl line 69.
Global symbol "#q" requires explicit package name asdasd.pl line 58.
And some others, too.
Here is an example of what I've tried:
my %hash;
while (reading the sequences) {
my $q = "the value the sequence has";
my $seq = "the name of the sequence";
if (exists $hash{$q}) {
push (#{$hash{$q}}, $seq);
} else {
$hash{$q} = \#q;
$hash{$q} = [$seq];
next;
}
}
This obviously shouldn't be a very complicated problem but I'm new to perl and this kind of a problem feels difficult. I've googled this from various places but there seems to be something I just don't realize, and it might be really obvious, too.
You can use what perl calls autovivification to make this quite easy. Your code doesn't need that central if-statement. You can boil it down to:
push #{ $hash{$q} }, $seq;
If the particular key doesn't yet exist in the hash, perl will autoviv it, since it can infer that you wanted an array reference here.
You can find further resources on autovivification by Googling it. It's a unique enough word that the vast majority of the hits seem relevant. :-)
You are actually pretty close, a few notes though:
In your else block you assign a reference to #q into your hash then immediately overwrite it with [$seq], only the last operation on the hash will hold
You don't need next at the end of your loop, it will automatically go to the next iteration if there are no more statements to execute in the loop body.
Everything else seems to work fine, here are my revisions and the test data I used (since I don't know anything about DNA sequences I just used letters I remember from high school Biology)
Input file:
A 1
T 2
G 3
A 3
A 2
G 5
C 1
C 1
C 2
T 4
Code:
use strict;
use warnings FATAL => 'all';
# open file for reading
open(my $fh, '<', 'test.txt');
my %hash;
while ( my $line = <$fh> ) { # read a line
# split the line read from a file into a sequence name and value
my ($q, $seq) = split(/\s+/, $line);
if( exists $hash{$q} ) {
push #{ $hash{$q} }, $seq;
}
else {
$hash{$q} = [$seq];
}
}
# print the resulting hash
for my $k ( keys %hash ) {
print "$k : ", join(', ', #{$hash{$k}}), "\n";
}
# prints
# A : 1, 3, 2
# T : 2, 4
# C : 1, 1, 2
# G : 3, 5

Resources