I'm trying to dynamically create a list of values from an AJAX request (using Catalyst), like this:
my #listofvalues = #{$params->{parameter_with_many_values}};
Then I loop through the list to make database insertions (one for each value). Since I need to loop through various lists like the one above, I need to access the index of the list. I am currently doing it like this:
foreach my $key (0 .. $#listofvalues){
$table_model->new({
field1 => $listofvalues[$key],
field2 => $x,
field3 => $another_listofvalues[$key]
field4 => $yet_another_listofvalues[$key]
});
}
This seems to work fine when two or more elements are received in the request. Whenever a single element is received, I get an error like
[error] Caught exception in pbitdb::Controller::Subjects->add "Can't use string ("1") as an ARRAY ref while "strict refs" in use at /home/lioneluranl/svn/pbitdb/pbitdb/script/../lib/pbitdb/Controller/Subjects.pm line 119."
Where, in this case, 1 is the value received and line 119 is the line where #listofvalues is being declared.
Now I've tried several to workaround this issue, but haven't found anything that works both ways (for a single or for various values). Any suggestions?
Yes, single element lists are OK in Perl, as are arrays and references to such arrays.
#!/usr/bin/perl
use warnings;
use strict;
use Data::Dumper;
sub new {
print Dumper \#_;
}
my $table_model = 'main';
for my $values ( [ 'a' .. 'c' ],
[ 'd' ]
) {
my $params = { parameter_with_many_values => $values };
my #listofvalues = #{ $params->{parameter_with_many_values} };
my #another_listofvalues = map uc, #listofvalues;
for my $key (0 .. $#listofvalues) {
my $x = rand;
$table_model->new({
field1 => $listofvalues[$key],
field2 => $x,
field3 => $another_listofvalues[$key]
});
}
}
How do you populate $params->{parameter_with_many_values}?
Update
It seems Catalyst::Request should mention that their "safe" parameters should be handled as follows:
#!/usr/bin/perl
use warnings;
use strict;
use Data::Dumper;
for my $params ( { param_with_many_values => 'a' },
{ param_with_many_values => [ 'a' .. 'e' ] },
{ something => 'else' }
) {
my $value_or_values = $params->{param_with_many_values};
my #list_of_values = ref $value_or_values ? #$value_or_values
: defined $value_or_values ? $value_or_values
: ();
print Dumper \#list_of_values;
}
First of all, you are asking about arrays (a type of variable), not lists (a vague term which can take on numerous definitions, none of which are pertinent here).
Yes, you can have an array with only one element.
$ perl -e'my #a = "ele"; CORE::say 0+#a; CORE::say for #a;'
1
ele
That's not the problem. The problem is that
#{$params->{parameter_with_many_values}}
expects
$params->{parameter_with_many_values}
to contain a reference to an array, but it contains 1 instead. It was probably set using
$params->{parameter_with_many_values} = #a; # Assigns number of elements
instead of
$params->{parameter_with_many_values} = \#a;
Related
Coming from this SO question, I'm trying to have a List (or non-scalar thing, in general) as the value assigned to a Hash key, this way:
my %syns-by-name does Associative[Str,List] = Bq => ("Bq", "becquerel", "becquerels");
my Str #syns = %syns-by-name<Bq>;
That does not work, however. Lists are itemized before being assigned, so the value is always a Scalar. You need to do a workaround to actually make this work:
my %syns-by-name does Associative[Str,List] = Bq => ("Bq", "becquerel", "becquerels");
my #list := <C coulomb coulombs>;
%syns-by-name<C> := #list;
my Str #syns = %syns-by-name<C>;
say #syns;
This returns what we were looking for, a list. However, how could we do that directly on the assignment and convince a list is a list and not an itemized list?
Assuming you don't need mutation afterwards, use a Map instead of a Hash.
my %syns-by-name is Map = Bq => ("Bq", "becquerel", "becquerels");
my Str #syns = %syns-by-name<Bq>;
say #syns; # [Bq becquerel becquerels]
Since there's no expectation that entries in a Map are assignable, it doesn't create Scalar containers for the values.
How about:
role deconting {
method AT-KEY(\key) {
callsame<>
}
}
my %h does deconting = a => <a b c>;
dd $_ for %h<a>; # "a""b""c"
This makes sure that the hash that does the "deconting" role will always return whatever is in the hash decontainerized.
Making it decontainerized on assignment can also be done, but is a lot more tricky as that would need tweaking of at least two methods: STORE and ASSIGN-KEY.
Despite the excellent answers from #Jonathan_Worthington and #Elizabeth_Mattijsen, I wanted to post the code below, which utilizes simple decontainerization:
~$ raku
Welcome to 𝐑𝐚𝐤𝐮𝐝𝐨™ v2020.10.
Implementing the 𝐑𝐚𝐤𝐮™ programming language v6.d.
Built on MoarVM version 2020.10.
To exit type 'exit' or '^D'
> my %syns-by-name = Bq => ("Bq", "becquerel", "becquerels");
{Bq => (Bq becquerel becquerels)}
> my Str #syns = %syns-by-name<Bq>;
Type check failed in assignment to #syns; expected Str but got List (("Bq", "becquerel", ...)
in block <unit> at <unknown file> line 1
> my Str #syns = %syns-by-name<Bq>[];
[Bq becquerel becquerels]
>
I gather there is an academic question here as to how the variable is defined versus how the values of the variable are accessed. However I don't want a casual reader to conclude that Raku is lacking functionality vis-à-vis hashes and lists.
> my %syns-by-name = Bq => ("Bq", "becquerel", "becquerels");
{Bq => (Bq becquerel becquerels)}
> dd $_ for %syns-by-name<Bq>[]
"Bq"
"becquerel"
"becquerels"
Nil
> my $list = <C coulomb coulombs>;
(C coulomb coulombs)
> say $list.WHAT
(List)
> %syns-by-name<C> = $list
(C coulomb coulombs)
> dd $_ for %syns-by-name<C>[]
"C"
"coulomb"
"coulombs"
Nil
>
I hope this answer isn't superfluous and casual readers will benefit. Thank you.
my %hash =
two => 2,
three => 3,
one => 1,
;
for %hash.sort(*.key)>>.kv -> ($key, $value) {
say "'$key' => '$value'";
}
Is %hash.sort({.key})>>.kv equivalent to above sort?
Why this sort doesn't work without hyper >> hint?
The sort method is returning a List of Pairs.
Since calling .kv on the list returns a list of index, Pair lists, which you don't want; you can't just call .kv. So you have to pull out the key and value from the Pair objects in the list individually by calling the .kv method on each of them, which >>.kv does.
You could also have used .map(*.kv) instead.
The >>.kv syntax allows an implementation to spread the work over multiple threads if it makes sense to do so.
( Currently Rakudo just does the work in a semi-random order to prevent people from using the feature wrong )
There is an alternate way of writing the loop by extracting the attributes using adverbs in the sub-signature:
for %hash.sort -> (:$key, :$value) {
say "'$key' => '$value'";
}
for %hash.sort -> $pair (:$key, :$value) {
say $pair;
say $key === $pair.key and $value === $pair.value; # True
}
# :$key is short for :key($key)
for %hash.sort -> (:key($k), :value($v)) {
say "'$k' => '$v'";
}
This can be useful on other objects which don't have a method for creating a list of their public attributes
class C { has $.a; has $.b; has $.c; has $!private-value }
my $c = 5;
my $obj = C.new(:a<A>,:b(1),:$c);
given $obj -> ( :$a, :b($b), :$c) ) {
say "$a $b $c";
}
# ignore $.a by using an unnamed scalar
given $obj -> ( :a($), :$b, :$c ) { ... }
# places any unspecified public attributes in %others
given $obj -> ( :$a, :$b, *%others ) {
.say for keys %others; # c
}
# ignores any unspecified attributes
# useful to allow subclasses to add more attributes
# or to just drop any values you don't care about
given $obj -> ( :$a, :$b, *% ) { ... }
# fails because it doesn't handle the public c attribute
# in the sub-signature
given $obj -> ( :$a, :$b ) { ... }
That is only the beginning of what's possible with signatures.
All of the following is also allowed in subroutine and method signatures, optional, and completely overkill for this example.
It is really useful in multi subs and multi methods for restricting the possible candidates.
for 'one' => 1, 1/3
->
# Type is an alias to the object type
::Type Any $_ # Any is the default type requirement
# the public attributes of the object
(
::A-Type Any :key( :numerator( $a ) ),
::B-Type Any :value( :denominator( $b ) ) where $b >= 1,
)
{
my Type $obj = $_; # new variable declared as having the same type
my A-Type $new-a = $a;
my B-Type $new-b = $b;
# could have used $_.^name or .^name instead of Type.^name
# so you don't actually have to add the alias to the signature
# to get the name of the arguments type
say Type.^name, ' ', $_;
say ' ', A-Type.^name, ' ', $a;
say ' ', B-Type.^name, ' ', $b;
}
Pair one => 1
Str one
Int 1
Rat 0.333333
Int 1
Int 3
As to using .sort({.key}), yes that is basically the same thing, as sort accepts anything Callable there.
I would like to point out that you didn't even need to provide an argument to sort because it's default is even smarter than what you gave it.
Perl 6 has many ways of creating and accessing Callable things. So any of the following would have worked:
*.key
{ .key } # { $_.key }
-> $_ { .key } # basically what the previous line turns into
{ $^placeholder-var.key }
sub ($_) { .key }
&a-subroutine-reference # you would have to create the subroutine though
Also since all of the normal operators are actually subroutines, you could use them in other places where you need a Callable. ( I can't think of one that works in that spot though )
&infix:<+> # the subroutines responsible for the numeric addition operator
&[+] # ditto
&prefix:<++>
&postfix:<++>
# etc
As far as I can see, the ony difference between the two versions is use of a block with implicit $_ parameter instead of using a Whatever-Star, so they are indeed equivalent.
This is Perl, so There Is More Than One Way to Do It:
*.key
{ .key }
{ $^arg.key }
-> $arg { $arg.key }
Why this sort doesn't work without hyper >> hint?
sort coerces the hash to a list of pairs, and that's what you'll get:
say %hash.sort(*.key).perl;
# ("one" => "1", "three" => "3", "two" => "2")
To get rid of the pairs, you need to iterate over the list and call .kv on each one:
say %hash.sort(*.key)>>.kv.perl;
# (("one", "1"), ("three", "3"), ("two", "2"))
say %hash.sort(*.key).map(*.kv).perl;
# (("one", "1"), ("three", "3"), ("two", "2"))
You could coerce to Hash before calling .kv:
say %hash.sort(*.key).hash.kv.perl;
# ("one", "1", "three", "3", "two", "2")
but this would of course defeat the purpose of the excercise as hash ordering cannot be relied on.
You may have noticed that you'll get different results depending on how exactly you write the code. If there no trailing .list, what you get is actually a Parcel and not a List, but semantics have not been finalized.
Note that even though the returned objects all perlify with simple parentheses, some are parcels and some are lists, which you can check by calling .WHAT. This is still a work in progress.
Also note the inner parentheses in some of these variants, which you can get rid of with a call to .flat. If you do so, you can use -> $key, $value as signature of your for loop instead of -> ($key, $value) (or, more explicitly, -> $anon ($key, $value)) which uses signature binding to unpack the parcels.
Instead of using .kv, you could use the same approach to unpack the pair objects instead:
for %hash.sort(*.key) -> (:$key, :$value) {
say "'$key' => '$value'";
}
Is there also a possibility to use the 'where' keyword in another place then a switch? Can I use it in a for in loop for example?
I have an array with bools, all with a value, can I do something like this:
var boolArray: [Bool] = []
//(...) set values and do stuff
for value where value == true in boolArray {
doSomething()
}
This would be a lot nicer than use an if, so I am wondering if there is a possibility to use where in combination with a for-loop. Ty for your time.
In Swift 2, new where syntax was added:
for value in boolArray where value == true {
...
}
In Pre 2.0 one solution would be to call .filter on the array before you iterate it:
for value in boolArray.filter({ $0 == true }) {
doSomething()
}
A normal for-loop will iterate all elements present in the list. But sometimes we want to iterate only when data satisfy some condition, there we can use where clause with for -loop. It's just a replacement of if condition inside the loop.
For example:
let numbers = [1,2,3,4,5,6,7]
for data in numbers {
if (data % 2 == 0) {
print(data)
}
}
can be rewritten in the simpler way as:
for data in numbers where data % 2 == 0 {
print(data)
}
Yes, you can use "where" clause with for loop.
let arr = [1,2,3,4,5]
for value in arr where value != 0 {
print(value)
}
Considering your example,
var boolArray: [Bool] = []
//(...) set values and do stuff
for value in boolArray where value == true {
doSomething()
}
I use code like this to find data values for my calculations:
sub get_data {
$x =0 if($_[1] eq "A"); #get column number by name
$data{'A'}= [2.00000, 0.15000, -0.00143, 33.51030, 0.77, 1, 0, 12];
return $data{$_[0]}[$x];
}
Data is stored like this in Perl file. I plan no more than 100 columns. Then to get value I call:
get_data(column, row);
Now I realized that that is terribly slow way to look up data in table. How can I do it faster? SQL?
Looking at your github code, the main problem you have is that your
big hash of arrays is initialized every time the function is called.
Your current code:
my #atom;
# {'name'}= radius, depth, solvation_parameter, volume, covalent_radius, hydrophobic, H_acceptor, MW
$atom{'C'}= [2.00000, 0.15000, -0.00143, 33.51030, 0.77, 1, 0, 12];
$atom{'A'}= [2.00000, 0.15000, -0.00052, 33.51030, 0.77, 0, 0, ''];
$atom{'N'}= [1.75000, 0.16000, -0.00162, 22.44930, 0.75, 0, 1, 14];
$atom{'O'}= [1.60000, 0.20000, -0.00251, 17.15730, 0.73, 0, 1, 16];
...
Time taken for your test case on the slow netbook I'm typing this on: 6m24.400s.
The most important thing to do is to move this out of the function, so it's
initialized only once, when the module is loaded.
Time taken after this simple change: 1m20.714s.
But since I'm making suggestions, you could write it more legibly:
my %atom = (
C => [ 2.00000, 0.15000, -0.00143, 33.51030, 0.77, 1, 0, 12 ],
A => [ 2.00000, 0.15000, -0.00052, 33.51030, 0.77, 0, 0, '' ],
...
);
Note that %atom is a hash in both cases, so your code doesn't do what you
were imagining: it declares a lexically-scoped array #atom, which is unused, then proceeds to fill up an unrelated global variable %atom. (Also do you really want an empty string for MW of A? And what kind of atom is A anyway?)
Secondly, your name-to-array-index mapping is also slow. Current code:
#take correct value from data table
$x = 0 if($_[1] eq "radius");
$x = 1 if($_[1] eq "depth");
$x = 2 if($_[1] eq "solvation_parameter");
$x = 3 if($_[1] eq "volume");
$x = 4 if($_[1] eq "covalent_radius");
$x = 5 if($_[1] eq "hydrophobic");
$x = 6 if($_[1] eq "H_acceptor");
$x = 7 if($_[1] eq "MW");
This is much better done as a hash (again, initialized outside the function):
my %index = (
radius => 0,
depth => 1,
solvation_parameter => 2,
volume => 3,
covalent_radius => 4,
hydrophobic => 5,
H_acceptor => 6,
MW => 7
);
Or you could be snazzy if you wanted:
my %index = map { [qw[radius depth solvation_parameter volume
covalent_radius hydrophobic H_acceptor MW
]]->[$_] => $_ } 0..7;
Either way, the code inside the function is then simply:
$x = $index{$_[1]};
Time now: 1m13.449s.
Another approach is just to define your field numbers as constants.
Constants are capitalized by convention:
use constant RADIUS=>0, DEPTH=>1, ...;
Then the code in the function is
$x = $_[1];
and you then need to call the function using the constants instead of strings:
get_atom_parameter('C', RADIUS);
I haven't tried this.
But stepping back a bit and looking at how you are using this function:
while($ligand_atom[$x]{'atom_type'}[0]) {
print STDERR $ligand_atom[$x]{'atom_type'}[0];
$y=0;
while($protein_atom[$y]) {
$d[$x][$y] = sqrt(distance_sqared($ligand_atom[$x],$protein_atom[$y]))
- get_atom_parameter::get_atom_parameter($ligand_atom[$x]{'atom_type'}[0], 'radius');
- get_atom_parameter::get_atom_parameter($protein_atom[$y]{'atom_type'}[0], 'radius');
$y++;
}
$x++;
print STDERR ".";
}
Each time through the loop you are calling get_atom_parameter twice to
retrieve the radius.
But for the inner loop, one atom is constant throughout. So hoist the call
to get_atom_parameter out of the inner loop, and you've almost halved the
number of calls:
while($ligand_atom[$x]{'atom_type'}[0]) {
print STDERR $ligand_atom[$x]{'atom_type'}[0];
$y=0;
my $lig_radius = get_atom_parameter::get_atom_parameter($ligand_atom[$x]{'atom_type'}[0], 'radius');
while($protein_atom[$y]) {
$d[$x][$y] = sqrt(distance_sqared($ligand_atom[$x],$protein_atom[$y]))
- $lig_radius
- get_atom_parameter::get_atom_parameter($protein_atom[$y]{'atom_type'}[0], 'radius');
$y++;
}
$x++;
print STDERR ".";
}
But there's more. In your test case the ligand has 35 atoms and the
protein 4128 atoms. This means that your initial code made
4128*35*2 = 288960 calls to get_atom_parameter, and while now it's
only 4128*35 + 35 = 144515 calls, it's easy to just make some arrays with
the radii so that it's only 4128 + 35 = 4163 calls:
my $protein_size = $#protein_atom;
my $ligand_size;
{
my $x=0;
$x++ while($ligand_atom[$x]{'atom_type'}[0]);
$ligand_size = $x-1;
}
#print STDERR "protein_size = $protein_size, ligand_size = $ligand_size\n";
my #protein_radius;
for my $y (0..$protein_size) {
$protein_radius[$y] = get_atom_parameter::get_atom_parameter($protein_atom[$y]{'atom_type'}[0], 'radius');
}
my #lig_radius;
for my $x (0..$ligand_size) {
$lig_radius[$x] = get_atom_parameter::get_atom_parameter($ligand_atom[$x]{'atom_type'}[0], 'radius');
}
for my $x (0..$ligand_size) {
print STDERR $ligand_atom[$x]{'atom_type'}[0];
my $lig_radius = $lig_radius[$x];
for my $y (0..$protein_size) {
$d[$x][$y] = sqrt(distance_sqared($ligand_atom[$x],$protein_atom[$y]))
- $lig_radius
- $protein_radius[$y]
}
print STDERR ".";
}
And finally, the call to distance_sqared [sic]:
#distance between atoms
sub distance_sqared {
my $dxs = ($_[0]{'x'}-$_[1]{'x'})**2;
my $dys = ($_[0]{'y'}-$_[1]{'y'})**2;
my $dzs = ($_[0]{'z'}-$_[1]{'z'})**2;
return $dxs+$dys+$dzs;
}
This function can usefully be replaced with the following, which uses
multiplication instead of **.
sub distance_sqared {
my $dxs = ($_[0]{'x'}-$_[1]{'x'});
my $dys = ($_[0]{'y'}-$_[1]{'y'});
my $dzs = ($_[0]{'z'}-$_[1]{'z'});
return $dxs*$dxs+$dys*$dys+$dzs*$dzs;
}
Time after all these modifications: 0m53.639s.
More about **: elsewhere you declare
use constant e_math => 2.71828;
and use it thus:
$Gauss1 += e_math ** (-(($d[$x][$y]*2)**2));
The built-in function exp() calculates this for you (in fact, ** is commonly
implemented as x**y = exp(log(x)*y), so each time you are doing this you are
performing an unnecessary logarithm the result of which is just slightly less
than 1 as your constant is only accurate to 6 d.p.). This change would alter
the output very slightly. And again, **2 should be replaced by multiplication.
Anyway, this answer is probably long enough for now, and calculation of d[]
is no longer the bottleneck it was.
Summary: hoist constant values out of loops and functions! Calculating the
same thing repeatedly is no fun at all.
Using any kind of database for this would not help your performance in the
slightest. One thing that might help you though is Inline::C. Perl is
not really built for this kind of intensive computation, and Inline::C
would allow you to easily move performance-critical bits into C while
keeping your existing I/O in Perl.
I would be willing to take a shot at a partial C port. How stable
is this code, and how fast do you want it to be? :)
Putting this in a DB will make it MUCH easier to maintain, scale, expand, etc.... Using a DB can also save you a lot of RAM -- it gets and stores in RAM only the desired result instead of storing ALL values.
With regards to speed it depends. With a text file you take a long time to read all the values into RAM, but once it is loaded, retrieving the values is super fast, faster than querying a DB.
So it depends on how your program is written and what it is for. Do you read all the values ONCE and then run 1000 queries? The TXT file way is probably faster. Do you read all the values every time you make a query (to make sure you have the latest value set) -- then the DB would be faster. Do you 1 query/day? use a DB. etc......
I need to go to the registry and check a programs installed version. I am using perl to a whole lot of others things but the registry checking part isn't working. The program version has to be 9.7 and up so it could be 9.8 or 9.7.5.
When I install the program it shows 9.7.4 but I just need the 9.7 to be checked.
Bellow is me going to DisplayVersion which is a REG_SZ which shows 9.7.4.
OR
I could use VersionMajor and VersionMinor together which is a REG_DWORD. Which for Major is 9 and Minor is 7.
$ProgVersion= `$rootpath\\system32\\reg\.exe query \\\\$ASSET\\HKLM\\SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{9ACB414D-9347-40B6-A453-5EFB2DB59DFA} \/v DisplayVersion`;
if ($ProgVersion == /9.7/)
This doesn't work I could make it 9.200 and it still works. I tried to use this instead and it still wouldn't work. This next part is assuming that a new client needs to be install if it goes from 9.7. I was trying to use Great than or equal to, but it didn't work.
$ProgVersionMajor= `$rootpath\\system32\\reg\.exe query \\\\$ASSET\\HKLM\\SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{9ACB414D-9347-40B6-A453-5EFB2DB59DFA} \/v VersionMajor`;
$ProgVersionMinor= `$rootpath\\system32\\reg\.exe query \\\\$ASSET\\HKLM\\SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{9ACB414D-9347-40B6-A453-5EFB2DB59DFA} \/v VersionMinor`;
if (($ProgVersionMajor=~ /9/) && ($ProgVersionMinor=~ /7/))
Any help on doing this correctly or fixing what I am doing.
Several things:
You don't mention it, but are you using the Perl module Win32::TieRegistry? If not, you should. It'll make handling the Windows registry much easier.
In the Perl documentation, you can look at Version String under Scalar Value Constructors. This will make manipulating version strings much, much easier. Version strings have either more than one decimal place in them, or start with the letter v. I always prefix them with v to make it obvious what it is.
Here's a sample program below showing you how they can be used in comparisons:
#! /usr/bin/env perl
#
use strict;
use warnings;
my $version = v4.10.3;
for my $testVersion (v3.5.2, v4.4.1, v5.0.1) {
if ($version gt $testVersion) {
printf qq(Version %vd is greater than test %vd\n), $version, $testVersion;
}
else {
printf qq(Version %vd is less than test %vd\n), $version, $testVersion;
}
}
Note that I can't just print version strings. I have to use printf and sprintf and use the %vd vector decimal format to print them out. Printing version strings via a regular print statement can cause all sorts of havoc since they're really unicode representations. You put them in a print statement and you don't know what you're getting.
Also notice that you do not put quotes around them! Otherwise, you'll just make them regular strings.
NEW ANSWER
I was trying to find a way to convert a string into a v-string without downloading an optional package like Perl::Version or (Version), and I suddenly read that v-strings are deprecated, and I don't want to use a deprecated feature.
So, let's try something else...
We could simply divide up version numbers into their constituent components as arrays:
v1.2.3 => $version[0] = 1, $version[1] = 2, $version[2] = 3
By using the following bit of code:
my #version = split /\./, "9.7.5";
my #minVersion = split /\./, "9.7"
Now, we can each part of the version string against the other. In the above example, I compare the 9 of #version with the 9 of #version, etc. If #version was 9.6 I would have compared the 6 in #version against the 7 in #minVersion and quickly discovered that #minVersion is a higher version number. However, in both the second parts are 7. I then look at the third section. Whoops! #minVersion consists of only two sections. Thus, #version is bigger.
Here's a subroutine that does the comparison. Note that I also verify that each section is an integer via the /^\d+$/ regular expression. My subroutine can return four values:
0: Both are the same size
1: First Number is bigger
2: Second Number is bigger
undef: There is something wrong.
Here's the program:
my $minVersion = "10.3.1.3";
my $userVersion = "10.3.2";
# Create the version arrays
my $result = compare($minVersion, $userVersion);
if (not defined $results) {
print "Non-version string detected!\n";
}
elsif ($result == 0) {
print "$minVersion and $userVersion are the same\n";
}
elsif ($result == 1) {
print "$minVersion is bigger than $userVersion\n";
}
elsif ($result == 2) {
print "$userVersion is bigger than $minVersion\n";
}
else {
print "Something is wrong\n";
}
sub compare {
my $version1 = shift;
my $version2 = shift;
my #versionList1 = split /\./, $version1;
my #versionList2 = split /\./, $version2;
my $result;
while (1) {
# Shift off the first value for comparison
# Returns undef if there are no more values to parse
my $versionCompare1 = shift #versionList1;
my $versionCompare2 = shift #versionList2;
# If both are empty, Versions Matched
if (not defined $versionCompare1 and not defined $versionCompare2) {
return 0;
}
# If $versionCompare1 is empty $version2 is bigger
if (not defined $versionCompare1) {
return 2;
}
# If $versionCompare2 is empty $version1 is bigger
if (not defined $versionCompare2) {
return 1;
}
# Make sure both are numeric or else there's an error
if ($versionCompare1 !~ /\^d+$/ or $versionCompare2 !~ /\^\d+$/) {
return;
}
if ($versionCompare1 > $versionCompare2) {
return 1;
}
if ($versionCompare2 > $versionCompare1) {
return 2;
}
}
}
Using Win32::TieRegistry
You said in your answer you didn't use Win32::TieRegistry. I just want to show you what it can do for the readability of your program:
Your Way
$ProgVersion= `$rootpath\\system32\\reg\.exe query \\\\$ASSET\\HKLM\\SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{9ACB414D-9347-40B6-A453-5EFB2DB59DFA} \/v DisplayVersion`;
With Win32::TieRegistry
use Win32::TieRegistry ( TiedHash => '%RegHash', DWordsToHex => 0 );
my $key = $TiedHash->{LMachine}->{Software}->{Wow6432Node}->{Microsoft}->{Windows}->{CurrentVersion}->{Uninstall}->{9ACB414D-9347-40B6-A453-5EFB2DB59DFA}->{Version};
my $programValue = $key->GetValue;
my $stringValue = unpack("L", $programValue);
Or, you can split it up:
my $MSSoftware = $TiedHash->{LMachine}->{Software}->{Wow6432Node}->{Microsoft};
my $uninstall = $MSSoftware->{Windows}->{CurrentVersion}->{Uninstall};
my $programVersion = $uninstall->{9ACB414D-9347-40B6-A453-5EFB2DB59DFA}->{Version};
See how much easier that's to read. You can also use this to test keys too.
(Word 'o Warning: I don't have a Windows machine in front of me, so I didn't exactly check the validity of the code. Try playing around with it and see what you get.)