I have an array, and I want to insert a new element inside it, shifting all other elements to the right:
my #a = (2, 5, 4, 8, 1);
# insert 42 into position no. 2
Result expected:
(2, 5, 42, 4, 8, 1);
my #a = (2, 5, 4, 8, 1);
splice(#a, 2, 0, 42); # -> (2, 5, 42, 4, 8, 1)
This means: in array #a position 2 remove 0 elements and add the element 42 (there can be more elements added). For more see splice, specifically this usage:
splice ARRAY or EXPR,OFFSET,LENGTH,LIST
The unshift() function in Perl places the given list of elements at the beginning of an array. Thereby shifting all values in the array by right.
#a=(1,2,3,4);
print("the output after unshift operation:",unshift(#a,5,6,7,8));
O/P: The output after unshift operation:
5 6 7 8 1 2 3 4
It can be easily done by slicing the array in required position.
use strict;
use warnings;
use feature 'say';
use Data::Dumper;
my #arr = (2, 5, 4, 8, 1);
my $pos = 2;
my $val = 42;
say Dumper(\#arr);
#arr = (#arr[0..$pos-1],$val,#arr[$pos..$#arr]);
say Dumper(\#arr);
Output
$VAR1 = [
2,
5,
4,
8,
1
];
$VAR1 = [
2,
5,
42,
4,
8,
1
];
Related
I now have an integer array like,
(1,13,3,5,6,7,11,10,8,2,12)
and I want to get the consecutive sub-regions form this array,
the results of above array should be (1,3), (5,8), (10,13)
The array maybe very large. Do anybody have any ideas?
Thanks a lot.
You can use the Set::IntSpan module:
use strict;
use warnings;
use Data::Dump;
use Set::IntSpan;
my #values = (1, 13, 3, 5, 6, 7, 11, 10, 8, 2, 12);
my $set = Set::IntSpan->new(#values);
my #spans = $set->spans;
dd #spans;
Outputs:
([1, 3], [5, 8], [10, 13])
If a run contains only one number, the lower and upper bounds will be the same, e.g. [42, 42].
Performance
As salva pointed out in the comments, Set::IntSpan does not perform well with a large number of ranges. An alternative is Set::IntSpan::Fast, which according to the documentation uses binary searches and tends toward O log N performance. If you install Set::IntSpan::Fast::XS as well, you will get even better performance (no need to change the use statement, the XS version will be used automatically if it's installed).
The following iterates through the ranges and pushes them onto an array:
use strict;
use warnings;
use Data::Dump;
use Set::IntSpan::Fast;
my #values = (1, 13, 3, 5, 6, 7, 11, 10, 8, 2, 12);
my $set = Set::IntSpan::Fast->new;
$set->add(#values);
my #ranges;
my $iter = $set->iterate_runs;
while (my ($from, $to) = $iter->()) {
push #ranges, [ $from, $to ];
}
dd #ranges;
Outputs:
([1, 3], [5, 8], [10, 13])
Note that to do anything useful with the ranges, you'll have to iterate through this array; it would be more efficient to do the work as you iterate through the set the first time instead of iterating through two different structures.
Using a hash, get some random element and look for consecutive elements both before and after:
my #ints = (...);
my %ints = map { $_ => 1 } #ints;
my #ranges;
while (keys %ints) {
my $bottom = my $top = each %ints;
delete $ints{$bottom};
1 while (delete $ints{--$bottom});
1 while (delete $ints{++$top});
push #ranges, [$bottom + 1, $top - 1];
}
say join ', ', map "$_->[0]-$_->[1]", #ranges;
If you want to get the job done well and quickly with minimal effort on your part, use Set::IntSpan as ThisSuitIsBlackNot suggests in his answer.
If you want a DIY job, then you can consider using this code as a basis:
#!/usr/bin/env perl
use strict;
use warnings;
$, = " ";
my #data = (1, 13, 3, 5, 6, 7, 11, 10, 8, 2, 12);
sub pr_region
{
my($lo, $hi) = #_;
print "($lo";
print ", $hi" if ($lo != $hi);
print ")\n";
}
sub print_regions
{
my(#data) = #_;
print "Raw: ", #data, "\n";
my #sorted = sort { $a <=> $b } #data;
#print "Sorted: ", #sorted, "\n";
my $lo = $sorted[0];
for my $i (1 .. scalar(#sorted)-1)
{
if ($sorted[$i-1] != $sorted[$i] - 1 &&
$sorted[$i-1] != $sorted[$i])
{
pr_region($lo, $sorted[$i-1]);
$lo = $sorted[$i];
}
}
pr_region($lo, $sorted[$#sorted]);
}
print_regions(#data);
print_regions(1);
print_regions(1, 10);
print_regions(1, 2, 10);
print_regions(1, 9, 10);
print_regions(#data, 11, 3, 19, -3);
The output from it is:
Raw: 1 13 3 5 6 7 11 10 8 2 12
(1, 3)
(5, 8)
(10, 13)
Raw: 1
(1)
Raw: 1 10
(1)
(10)
Raw: 1 2 10
(1, 2)
(10)
Raw: 1 9 10
(1)
(9, 10)
Raw: 1 13 3 5 6 7 11 10 8 2 12 11 3 19 -3
(-3)
(1, 3)
(5, 8)
(10, 13)
(19)
I've made no effort at minimizing the code. It prints its results rather than packaging them in a data structure for reuse. It doesn't handle an empty array.
This will be a lot easier to show in code than explain. I've just got a hash mapping to quickly fill a hash to run replacements on data the script reads in. E.g. if 5 output 6, if 3 output 2, if 23 output 6, etc etc. This works pretty well except for not mapping one of the keys.
(btw, if anyone can think of a more elegant way to map several keys in a hash to a single value, let me know :) )
Anyways onto the code...
$COLUMN = 6;
%PERIOD_1 = (map {( 1, 10, 14, 20, 22, 29, 35, 39 )[$_] => 1 } 0..100); #1st period
%PERIOD_2 = (map {( 3, 8, 11, 18, 24, 26, 32, 37 )[$_] => 2 } 0..100); #2nd period
%PERIOD_3 = (map {( 7, 13, 16, 21, 28, 34, 36 )[$_] => 3 } 0..100); #3rd period
%PERIOD_4 = (map {( 5, 2, 6, 15, 17, 23, 27, 31,38)[$_] => 4 } 0..100); #4th period
%PERIOD_5 = (map {( 4, 9, 12, 19, 25, 30, 33, 40 )[$_] => 5 } 0..100); #5th period
%PERIODS = (%PERIOD_1,%PERIOD_2,%PERIOD_3,%PERIOD_4,%PERIOD_5);
open (FILE,"<",$ARGV[0]);
while(<FILE>) {
my #columns = split(/\t/);
print $columns[$COLUMN] . "-" . $PERIODS{$columns[$COLUMN]};
}
close(FILE);
So this works pretty well. You get an output like:
37-2
29-1
15-4
6-4
34-3
24-2
5-
Which matches/replaces literally every value EXCEPT 5. I don't get it - all of the other keys in the mapping are in the hash, but for some reason 5 (and only 5) isn't. Could someone explain wtf the problem is? edit: fixed formatting
EDIT: YES, I USED WARNINGS IN THE CODE. I really don't care about the warning though (even if it is related to my problem) - I just want the problem solved. If I cared to understand the warning I would've asked about it.
This is some rather unusual code, and its probably not doing what you think. For example:
%PERIOD_2 = (map {( 3, 8, 11, 18, 24, 26, 32, 37 )[$_] => 2 } 0..100);
This will iterate over numbers from 0 to 100, but only 0 to 7 are of any interest to you, since the list contains 8 numbers. The subscript for 8, for example, will be empty, and the map iteration will return () => 2, or just a single 2. This will mean that you will get a long string of 2,2,2,2,2,2,2, which will lead to the key 2 always existing in that hash, no matter if it is in the list or not.
This is an overly complicated way of turning a list into a hash. You would normally just do:
my %hash = map { $_ => 2 } ( 3, 8, 11, 18, 24, 26, 32, 37 );
And if you had several lists to merge into one hash, you would do
my %hash;
for my $num (1, 10, 14, 20, 22, 29, 35, 39) {
$hash{$num} = 1;
}
for my $num (3, 8, 11, 18, 24, 26, 32, 37) {
$hash{$num} = 2;
}
....
If you had used
use strict;
use warnings;
You would most likely not have this problem, because you would get the warning Odd number of elements in hash assignment as toolic pointed out.
Also, in cases such as these, using the Data::Dumper module to debug is very convenient:
use Data::Dumper;
print Dumper \%hash; # send ref to hashes and arrays
For the code you had above, with these debugging tools you would get this output:
Odd number of elements in hash assignment at foo.pl line 9.
$VAR1 = {
'32' => 2,
'11' => 2,
'3' => 2,
'26' => 2,
'2' => undef,
'8' => 2,
'18' => 2,
'24' => 2,
'37' => 2
};
And as you can see, the problem is immediately identifiable: The key 2 has no value.
When you add the hashes together, you also overwrite previous valid values, such as the one for 5. For example:
%hash1 = (5 => 2);
%hash2 = (5 => undef);
%hash = (%hash1, %hash2);
Since hash keys are unique, 5 => undef will overwrite 5 => 2.
The "Odd number of elements" warning refers to the list that is assigned to the hash, not the number of keys. For example:
my %foo = (1, 2, 3); # odd number of elements
This hash will now have the keys 1 and 3, but key 3 will not have a value, so it will be undef. Usually in hash assignment, you want an even number of elements, so that each key has a value.
You should always
use strict;
use warnings;
at the top of your programs, and declare every variable with my as close as possible to its first point of use.
You should also preferably use only lower case and underscore for your variable names. Upper case is reserved for global identifiers like package names.
It is best to use a lexical file handle instead of a global one, and you should always check the success of your open calls, dying with the value of $! in the die string. Like this
open my $file, '<', $ARGV[0] or die "Unable to open input file: $!";
What this will do
my %period = (map {( 1, 10, 14, 20, 22, 29, 35, 39 )[$_] => 1 } 0..100);
is correctly generate your key/value pairs for those elements in the list, but when you get to 8 (the size of the list) it will just produce the value 1 as there is no corresponding element of the list. So you get
1 => 1,
10 => 1,
14 => 1,
20 => 1,
22 => 1,
29 => 1,
35 => 1,
39 => 1,
1,
1,
1,
1,
1,
and for those hashes where there is an even number of elements in the list you will end up with an unpaired trailing value.
I suggest you put the information into a data file, or at least into the __DATA__ section of your program, and read it into the hash like this
use strict;
use warnings;
my $column = 6;
my %periods;
while (<DATA>) {
my ($val, #keys) = /\d+/g;
next unless $val;
$periods{$_} = $val for #keys;
}
open my $fh, "<", $ARGV[0];
while(<$fh>) {
my #columns = split /\t/;
my $key = $columns[$column];
printf "%s - %s\n", $key, $periods{$key};
}
__DATA__
1: 1, 10, 14, 20, 22, 29, 35, 39;
2: 3, 8, 11, 18, 24, 26, 32, 37;
3: 7, 13, 16, 21, 28, 34, 36;
4: 5, 2, 6, 15, 17, 23, 27, 31, 38;
5: 4, 9, 12, 19, 25, 30, 33, 40;
(Don't worry about the format of the lines after __DATA__. It takes notice only of the decimal digits in the line. Everything else is just layout and is ignored.)
In Perl:
my %members = ( "fools" => 6,
"monsters" => 2,
"weirdos" => 1,
"coders" => 1,
"betrayers" => 1, );
When I write:
my #values_members = values %members;
The position of the elements in the array will not be 6, 2, 1, 1, 1 (the position "as they appear" in the code). It will be random or close to random.
I want a function such that:
my values_members = get_values_with_position_as_appears_in_code ( %members );
gives
( 6, 2, 1, 1, 1 );
Is this possible?
Perl hashes are unordered, so there is no particular guarantee about what order you will get things out of the hash.
You can use Tie::IxHash which will give you a hash that keeps track of its insertion order.
use strict;
use warnings;
use Tie::IxHash;
tie my %members, 'Tie::IxHash', ( "fools" => 6,
"monsters" => 2,
"weirdos" => 1,
"coders" => 1,
"betrayers" => 1, );
my #values = values %members;
print join "\n" #values;
Output:
6
2
1
1
1
What's the easiest way to flatten a multidimensional array ?
One level of flattening using map
$ref = [[1,2,3,4],[5,6,7,8]]; # AoA
#a = map {#$_} #$ref; # flattens it
print "#a"; # 1 2 3 4 5 6 7 8
Using List::Flatten seems like the easiest:
use List::Flatten;
my #foo = (1, 2, [3, 4, 5], 6, [7, 8], 9);
my #bar = flat #foo; # #bar contains 9 elements, same as (1 .. 9)
Actually, that module exports a single simple function flat, so you might as well copy the source code:
sub flat(#) {
return map { ref eq 'ARRAY' ? #$_ : $_ } #_;
}
You could also make it recursive to support more than one level of flattening:
sub flat { # no prototype for this one to avoid warnings
return map { ref eq 'ARRAY' ? flat(#$_) : $_ } #_;
}
The easiest and most natural way, is to iterate over the values and use the # operator to "dereference" / "unpack" any existing nested values to get the constituent parts. Then repeat the process for every reference value encountered.
This is similar to Viajayenders solution, but works for values not already in an array reference and for any level of nesting:
sub flatten {
map { ref $_ ? flatten(#{$_}) : $_ } #_;
}
Try testing it like so:
my #l1 = [ 1, [ 2, 3 ], [[[4]]], 5, [6], [[7]], [[8,9]] ];
my #l2 = [ [1,2,3,4,5], [6,7,8,9] ];
my #l3 = (1, 2, [3, 4, 5], 6, [7, 8], 9); # Example from List::Flatten
my #r1 = flatten(#l1);
my #r2 = flatten(#l1);
my #r3 = flatten(#l3);
if (#r1 ~~ #r2 && #r2 ~~ #r3) { say "All list values equal"; }
if data is always like an example, I recommend List::Flatten too.
but data has more than 2 nested array, flat cant't work.
like #foo = [1, [2, [3, 4, 5]]]
in that case, you should write recursive code for it.
how about bellow.
sub flatten {
my $arg = #_ > 1 ? [#_] : shift;
my #output = map {ref $_ eq 'ARRAY' ? flatten($_) : $_} #$arg;
return #output;
}
my #foo = (1, 2, [3, 4, 5, [6, 7, 8]], 9);
my $foo = [1, 2, [3, 4, 5, [6, 7, 8]], 9];
my #output = flatten #foo;
my #output2 = flatten $foo;
print "#output";
print "#output2";
The easiest way to flatten a multidimensional array when it includes:
1. arrays
2. array references
3. scalar values
4. scalar references
sub flatten {
map { ref $_ eq 'ARRAY' ? flatten(#{$_}) :
ref $_ eq 'SCALAR' ? flatten(${$_}) : $_
} #_;
}
The other flatten sub answer crashes on scalar references.
Something along the lines of:
my $i = 0;
while ($i < scalar(#array)) {
if (ref #array[$i] eq 'ARRAY') {
splice #array, $i, 1, #$array[$i];
} else {
$i++;
}
}
I wrote it blindly, no idea if it actually works but you should get the idea.
Same as Vijayender's solution but will work on mixed arrays containing arrayrefs and scalars.
$ref = [[1,2,3,4],[5,6,7,8],9,10];
#a = map { ref $_ eq "ARRAY" ? #$_ : $_ } #$ref;
print "#a"
Of course you can extend it to also dereference hashrefs:
#a = map { ref $_ eq "ARRAY" ? #$_ : ref $_ eq "HASH" ? %$_: $_ } $#ref;
or use grep to weed out garbage:
#a = map { #$_} grep { ref $_ eq 'ARRAY' } #$ref;
As of List::MoreUtils 0.426 we have an arrayify function that flattens arrays recursively:
#a = (1, [[2], 3], 4, [5], 6, [7], 8, 9);
#l = arrayify #a; # returns 1, 2, 3, 4, 5, 6, 7, 8, 9
It was introduced earlier but was broken.
let's say you have set of integer in the list.
List Declare:
#lists = (22, 10, 5, 2);
but if I do want all the elements to be divide in let's say 2, is there anyways to do other than manually computing in running loop?
Don't want to compute like this:
foreach $list (#lists)
{
print (list/2);
}
my #numbers = (22, 10, 5, 2);
# Create a new list, as in David Dorward's answer.
my #halves = map { $_ / 2 } #numbers;
# Or modify the original list directly.
$_ /= 2 for #numbers;
#lists = [22, 10, 5, 2];
should be
#lists = (22, 10, 5, 2);
then you can
#lists = map { $_ / 2 } #lists