In Perl, how can I unpack to several variables? - perl

I have a struct wich contains:
struct mystruct{
int id[10];
char text[40];
unsigned short int len;
};
And I'm trying to unpack it in a single line, something like this:
my(#ids,$text,$length) = unpack("N10C40n",$buff) ;
But everything is going to the first array(#ids), i've tried templates as "N10 C40 n" and "(N10)(C40)(n)"
So, either this can't be done or I'm not using the proper template string.
Note: I'm using big endian data.
Any hints?

In list assignment the first array or hash will eat everything (how would it know where to stop?). Try this instead:
my #unpacked = unpack "N10Z40n", $buff;
my #ids = #unpacked[0 .. 9];
my ($text, $length) = #unpacked[10, 11];
you could also say
my #ids;
(#ids[0 .. 9], my ($text, $length)) = unpack "N10Z40n", $buff;

If the order of the #ids does not matter:
my ($length, $text, #ids) = reverse unpack("N10C40n",$buff) ;

Related

What is wrong with this Perl subroutine?

I'm trying to implement a subroutine that calculates the d-neighbors of an input string. This is apart of an implementation of planted motif search, but my question is much more general. Here is the code:
#subroutine for generating d-neighbors
sub generate_d_neighbors{
# $sequence is the sequence to generate d-neighbors from
# $HD is the Hamming Distance
my ($sequence, $HD) = #_;
for(my $i = 0; $i=$HD; $i++){
my #l = ['A', 'C', 'T', 'G'];
my #t = splice(#l,$sequence[$i]);
#TODO
}
}
The error is occurring at the last line, saying that:
Global symbol "#sequence" requires explicit package name (did you forget to declare "my #sequence"?
It was my understanding that Perl does not take parameters in the form subroutine(param1, param2) like in Java for example, but why is $sequence not being recognized as already having been initialized?
There are some problems with your code:
sub generate_d_neighbors{
my ($sequence, $HD) = #_;
for(my $i = 0; $i=$HD; $i++){
my #l = ['A', 'C', 'T', 'G'];
my #t = splice(#l,$sequence[$i]);
}
}
First, let's look at
for(my $i = 0; $i=$HD; $i++){
Assuming $HD is nonzero, this loop will never terminate because the condition will never be false. If you wanted $i to range from 0 to $HD, writing the statement as for my $i (0 .. $HD) would have been better.
Second, you have
my #t = splice(#l,$sequence[$i]);
where you seem to assume there is an array #sequence and you are trying to access its first element. However, $sequence is a reference to an array. Therefore, you should use
$sequence->[$i]
Third (thanks #Ikegami), you have
my #l = ['A', 'C', 'T', 'G'];
in the body of the for-loop. Then #l will contain a single element, a reference to an anonymous array containing the elements 'A', 'C', 'T', and 'G'. Instead, use:
my #l = qw(A C T G);
I am not sure exactly what you want to achieve with splice(#l, $sequence->[$i]), but that can be better written as:
my #t = #l[0 .. ($sequence->[$i] - 1)];
In fact, you could reduce the two assignments to:
my #t = qw(A C T G)[0 .. ($sequence->[$i] - 1)];
It looks to me like you want
substring($sequence, 0, 1)
instead of
$sequence[0].
In Perl, strings are first class variables, not a type of array.
Or maybe you want splice(#l, $sequence->[0])?
This list-assignment syntax:
my (#sequence, $HD) = #_;
doesn't do what you might want it to do (put the last argument in $HD and the rest in #sequence). The array always takes all the arguments it can, leaving none for whatever comes after it.
Reversing the order can work, for cases where there is only one array:
my ($HD, #sequence) = #_;
and you make the corresponding change in the caller.
To solve the problem more generally, use a reference:
my ($sequence, $HD) = #_;
and call the sub like this:
generate_d_neighbors(\#foo, $bar);
or this:
# Note the brackets, which make an array reference, unlike parentheses
# which would result in a flat list.
generate_d_neighbors([...], 42);
If you use a prototype:
sub generate_d_neighbors (\#$)
then the caller can say
generate_d_neighbors(#foo, $bar);
and the #foo automatically becomes a reference as if it had been\#foo.
If you use any of the reference-based solutions, you must alter the body of the function to use $sequence instead of #sequence, following these rules:
Change #sequence to #$sequence
Change $#sequence to $#$sequence
Change $sequence[...] to $sequence->[...]
Change #sequence[...] to #$sequence[...] (but be sure you really meant to use an array slice... if you're new to perl you probably didn't mean it, and should have used $sequence[...] instead)

perl-how to treat a string as a binary number?

Read a file that contains an address and a data, like below:
#0, 12345678
#1, 5a5a5a5a
...
My aim is to read the address and the data. Consider the data I read is in hex format, and then I need to unpack them to binary number.
So 12345678 would become 00010010001101000101011001111000
Then, I need to further unpack the transferred binary number to another level.
So it becomes, 00000000000000010000000000010000000000000001000100000001000000000000000100000001000000010001000000000001000100010001000000000000
They way I did is like below
while(<STDIN>) {
if (/\#(\S+)\s+(\S+)/) {
$addr = $1;
$data = $2;
$mem{$addr} = ${data};
}
}
foreach $key (sort {$a <=> $b} (keys %mem)) {
my $str = unpack ('B*', pack ('H*',$mem{$key}));
my $str2 = unpack ('B*', pack ('H*', $str));
printf ("#%x ", $key);
printf ("%s",$str2);
printf ("\n");
}
It works, however, my next step is to do some numeric operation on the transferred bits.
Such as bitwise or and shifting. I tried << and | operator, both are for numbers, not strings. So I don't know how to solve this.
Please leave your comments if you have better ideas. Thanks.
You can employ Bit::Vector module from metaCPAN
use strict;
use warnings;
use Bit::Vector;
my $str = "1111000011011001010101000111001100010000001111001010101000111010001011";
printf "orig str: %72s\n", $str;
#only 72 bits for better view
my $vec = Bit::Vector->new_Bin(72,$str);
printf "vec : %72s\n", $vec->to_Bin();
$vec->Move_Left(2);
printf "left 2 : %72s\n", $vec->to_Bin();
$vec->Move_Right(4);
printf "right 4 : %72s\n", $vec->to_Bin();
prints:
orig str: 1111000011011001010101000111001100010000001111001010101000111010001011
vec : 001111000011011001010101000111001100010000001111001010101000111010001011
left 2 : 111100001101100101010100011100110001000000111100101010100011101000101100
right 4 : 000011110000110110010101010001110011000100000011110010101010001110100010
If you need do some math with arbitrary precision, you can also use Math::BigInt or use bigint (http://perldoc.perl.org/bigint.html)
Hex and binary are text representation of numbers. Shifting and bit manipulations are numerical operations. You want a number, not text.
my $hex = '5a5a5a5a';
$num = hex($hex); # Convert to number.
$num >>= 1; # Manipulate the number.
$hex = sprintf('%08X', $num); # Convert back to hex.
In a comment, you mention you want to deal with 256 bit numbers. The native numbers don't support that, but you can use Math::BigInt.
My final solution of this is forget about treat them as numbers, just treat them as string . I use substring and string concentration instead of shift. Then for the or operation , I just add each bit of the string, if it's 0 the result is 0, else is 1.
It may not be the best way to solve this problem. But that's the way I finally used.

Randomizing a list using Rand in perl

Hi i currently am using the List::Util shuffle to randomize a array with CGI however I want to modify the code to use rand instead
here is my code
print "Content-type: text/html\n\n";
use List::Util qw(shuffle);
#haikuone = ('behind', 'the', 'red', 'barn');
#haikutwo = ('prairie', 'grasses', 'reclaiming');
#haikuthree = ('the', 'basketball', 'court');
#randomize1 = shuffle(#haikuone);
#randomize2 = shuffle(#haikutwo);
#randomize3 = shuffle(#haikuthree);
print "<html>\n";
print "<head><title>Haiku_Random</title></head>\n";
print "<body>\n";
print "<pre>\n";
print "RANDOM HAIKU (DISCLAIMER: NONSENSE MAY OCCUR)\n";
print "#randomize1\n";
print "#randomize2\n";
print "#randomize3\n";
How would i modify this code to use rand instead of List::Util
I dont think its much but a novice here
I'm trying to get this working
$haikuone = ('behind', 'the', 'red', 'barn');
$haikutwo = ('prairie', 'grasses', 'reclaiming');
$haikuthree = ('the', 'basketball', 'court');
#random1 = $line1[rand #haikuone];
#random2 = $line2[rand #haikutwo];
#random3 = $line3[rand #haikuthree];
print "RANDOM HAIKU (DISCLAIMER: NONSENSE MAY OCCUR)\n";
print "$line1\n";
Now when i do this
#!/usr/local/bin/perl
#haikuone = ('behind', 'the', 'red', 'barn');
#haikutwo = ('prairie', 'grasses', 'reclaiming');
#haikuthree = ('the', 'basketball', 'court');
#random1 = $line1[rand #haikuone];
#random2 = $line2[rand #haikutwo];
#random3 = $line3[rand #haikuthree];
print "RANDOM HAIKU (DISCLAIMER: NONSENSE MAY OCCUR)\n";
print "#haikuone\n";
It will print haikuone but it wont randomize it
sub fisher_yates_shuffle {
my $deck = shift; # $deck is a reference to an array
return unless #$deck; # must not be empty!
my $i = #$deck;
while (--$i) {
my $j = int rand ($i+1);
#$deck[$i,$j] = #$deck[$j,$i];
}
}
my #randomize1 = #haikuone;
fisher_yates_shuffle(\#randomize1);
print "#randomize1\n";
Always use use strict; use warnings;! You have the following code, but don't have any arrays named #haikuone, #haikutwo, #haikuthree, #line1, #line2 or #line3.
#random1 = $line1[rand #haikuone];
#random2 = $line2[rand #haikutwo];
#random3 = $line3[rand #haikuthree];
It's also really weird that use three arrays with one element each.
Hi i currently am using the List::Util shuffle to randomize a array
with CGI
This makes sense. List::Util::shuffle() is the best way to shuffle a list in Perl - whether or not you're writing a CGI program.
however I want to modify the code to use rand instead
This doesn't make sense. rand() doesn't shuffle a list. It just generates a random number.
It's a good idea to use rand() to extract a single random element from an array.
my $random_element = #array[rand #array];
But that's not what you're trying to do.
If you really want to use rand() then you need to incorporate its use in a function. There's a good function given in the Perl FAQ (in the answer to the question - "How do I shuffle an array randomly?" - so perhaps you should have taken a look at the FAQ before asking here) which looks like this:
sub fisher_yates_shuffle {
my $deck = shift; # $deck is a reference to an array
return unless #$deck; # must not be empty!
my $i = #$deck;
while (--$i) {
my $j = int rand ($i+1);
#$deck[$i,$j] = #$deck[$j,$i];
}
}
But note that this is implemented in Perl. The shuffle() function in List::Util is written in C, so it's going to be faster.
So, all in all, there's really no good reason for not using List::Util::shuffle().

Set::IntervalTree acting weirdly when I add nodes through a loop in perl

I am trying to use the Set::IntervalTree module and I think it is giving me the same node pointers if I insert the elements in a loop.
If I insert them outside a loop sequentially one after the another the nodes are inserted fine and the find / find_window calls works perfectly. But on the nodes which were added in the loop, the find functions give strange results.
#!/usr/bin/perl
use Set::IntervalTree;
my $tree = Set::IntervalTree->new;
$tree->insert("60:70", 60, 70);
$tree->insert("70:80", 70, 80);
$tree->insert("80:90", 80, 90);
for(my $i = 0; $i < 60; $i=$i+10)
{
$j = $i+10;
print "$i".":"."$j\n";
$tree->insert("$i".":"."$j", $i, $i+10);
}
print $tree->str;
my $results1 = $tree->fetch(25, 28);
my $window = $tree->fetch_window(25,250);
my #arr1 = #$results1;
print " #arr1 found.\n";
my $results2 = $tree->fetch(65, 68);
my #arr2 = #$results2;
print " #arr2 found.\n";
This is the output. Check the node pointers.. the ones added from the loop have same pointers and they return wrong interval (which i guess is due to the same pointer values.)
Node:0x9905b20, k=0, h=9, mH=9 l->key=NULL r->key=NULL p->key=10 color=BLACK
Node:0x9905b20, k=10, h=19, mH=29 l->key=0 r->key=20 p->key=30 color=RED
Node:0x9905b20, k=20, h=29, mH=29 l->key=NULL r->key=NULL p->key=10 color=BLACK
Node:0x9905b20, k=30, h=39, mH=89 l->key=10 r->key=70 p->key=NULL color=BLACK
Node:0x9905b20, k=40, h=49, mH=49 l->key=NULL r->key=NULL p->key=50 color=RED
Node:0x9905b20, k=50, h=59, mH=69 l->key=40 r->key=60 p->key=70 color=BLACK
Node:0x98c6270, k=60, h=69, mH=69 l->key=NULL r->key=NULL p->key=50 color=RED
Node:0x98fd138, k=70, h=79, mH=89 l->key=50 r->key=80 p->key=30 color=RED
Node:0x98fd078, k=80, h=89, mH=89 l->key=NULL r->key=NULL p->key=70 color=BLACK
50:60 found.
60:70 found.
It looks like you have to use a Perl scalar variable if you are calling insert from an inner block. If you use an interpolated string it will be discarded and overwritten by the subsequent execution of the block.
This code seems to work fine. Please always use strict and use warnings on all your programs - particularly those you are asking for help with.
#!/usr/bin/perl
use strict;
use warnings;
use Set::IntervalTree;
my $tree = Set::IntervalTree->new;
for (my $i = 0; $i <= 80; $i += 10) {
my $name = sprintf '%02d:%02d', $i, $i+10;
$tree->insert($name, $i, $i+10);
}
my $results1 = $tree->fetch(25, 28);
print "#$results1 found\n";
my $results2 = $tree->fetch(65, 68);
print "#$results2 found\n";
output
20:30 found
60:70 found
i have used this package. the C++ code for insert has arguments (&t, int, int). this means that the C++ code store away a pointer to the perl data
for the first argument. using a scalar is not really a solution. eventually
the value will be overwritten. the solution i used was to put the id into a
hash and pass the hash value as the id. this will keep around the perl data
for the id. the C++ code should copy the id to maintain its value.
$id = "$start:$end";
$rephash1{$id} = $id;
$repeats1{$scaffold} = Set::IntervalTree->new if (!defined($repeats1{$scaffold}));
$repeats1{$scaffold}->insert($rephash1{$id}, $start, $end);

How to convert hex to string of hex

I have a problem understanding and using the 'vec' keyword.
I am reading a logpacket in which values are stored in little endian hexadecimal. In my code, I have to unpack the different bytes into scalars using the unpack keyword.
Here's an example of my problem:
my #hexData1 = qw(50 65);
my $data = pack ('C*', #hexData1);
my $x = unpack("H4",$data); # At which point the hexadecimal number became a number
print $x."\n";
#my $foo = sprintf("%x", $foo);
print "$_-> " . vec("\x65\x50", $_, 1) . ", " for (0..15); # This works.
print "\n";
But I want to use the above statement in the way below. I don't want to send a string of hexadecimal in quotes. I want to use the scalar array of hex $x. But it won't work. How do I convert my $x to a hexadecimal string. This is my requirement.
print "$_-> " . vec($x, $_, 1).", " for (0..15); # This doesn't work.
print "\n";
My final objective is to read the third bit from the right of the two byte hexadecimal number.
How do I use the 'vec' command for that?
You are making the mistake of unpacking $data into $x before using it in a call to vec. vec expects a string, so if you supply a number it will be converted to a string before being used. Here's your code
my #hexData1 = qw(50 65);
my $data= pack ('C*', #hexData1);
The C pack format uses each value in the source list as a character code. It is the same as calling chr on each value and concatenating them. Unfortunately your values look like decimal, so you are getting chr(50).chr(65) or "2A". Since your values are little-endian, what you want is chr(0x65).chr(0x50) or "\x65\x50", so you must write
my $data= pack ('(H2)*', reverse #hexData1);
which reverses the list of data (to account for it being little-endian) and packs it as if it was a list of two-digit hex strings (which, fortunately, it is).
Now you have done enough. As I say, vec expects a string so you can write
print join ' ', map vec($data, $_, 1), 0 .. 15;
print "\n";
and it will show you the bits you expect. To extract the the 3rd bit from the right (assuming you mean bit 13, where the last bit is bit 15) you want
print vec $data, 13, 1;
First, get the number the bytes represent.
If you start with "\x50\x65",
my $num = unpack('v', "\x50\x65");
If you start with "5065",
my $num = unpack('v', pack('H*', "5065"));
If you start with "50","65",
my $num = unpack('v', pack('H*', join('', "50","65"));
Then, extract the bit you want.
If you want bit 10,
my $bit = ($num >> 10) & 1;
If you want bit 2,
my $bit = ($num >> 2) & 1;
(I'm listing a few possibilities because it's not clear to me what you want.)