Minimum bytes required to represent a signed hexadecimal value - perl

Can anyone tell me is there any function in Perl which converts signed decimal to Hex with minimum number of bytes.
ex: -555(dec) => FFFFFDD5(hex) [ converted using $Hex = sprintf( "%X", -555 )]
I want the result to be FDD5 instead of FFFFFDD5.
2 bytes are enough to represent -555.But i am getting 4 bytes conversion.
Please help!

There are two problems with your approach.
It's unparsable.
For example, even if you only know you have two numbers,
Bytes 12 34 56 7816 could refer to 1216 and 34567816.
Bytes 12 34 56 7816 could refer to 123416 and 567816.
Bytes 12 34 56 7816 could refer to 12345616 and 7816.
You could use some external means of identifying the length of the encoded number, but that would nullify some or all of the savings.
It's ambiguous.
For example,
Bytes FD D516 could refer to 6498110 (Bytes 00 00 FD D516 as an int32).
Bytes FD D516 could refer to -55510 (Bytes FF FF FD D516 as an int32).
One solution is to use a length prefix (like UTF-8).
-2^13..2^13-1 2 bytes 00xx xxxx xxxx xxxx
-2^21..2^21-1 3 bytes 01xx xxxx xxxx xxxx xxxx xxxx
-2^29..2^29-1 4 bytes 10xx xxxx xxxx xxxx xxxx xxxx xxxx xxxx
-2^31..2^31-1 5 bytes 1100 0000 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx
The optimal scheme will depend on the distribution of your numbers.
The packing/encoding function for the above scheme could be written as follows:
sub pack_vint32 {
my $n = shift;
my $nn = $n >= 0 ? $n : ~$n;
return substr(pack('L>', ($n & 0x3FFF ) | 0x0000 ), -2) if !($nn & ~0x1FFF);
return substr(pack('L>', ($n & 0x3FFFFF ) | 0x400000 ), -3) if !($nn & ~0x1FFFFF);
return substr(pack('L>', ($n & 0x3FFFFFFF) | 0x80000000), -4) if !($nn & ~0x1FFFFFFF);
return "\xC0".pack('L>', $n);
}
And the unpacking/decoding function for the above scheme could be written as follows:
sub unpack_vint32 {
for (shift) {
if (/^[\x00-\x3F]/) {
return if length() < 2;
my $n = unpack('L>', "\x00\x00".substr($_, 0, 2, '')) & 0x3FFF;
$n -= 0x4000 if $n & 0x2000;
return $n;
}
elsif (/^[\x40-\x7F]/) {
return if length() < 3;
my $n = unpack('L>', "\x00".substr($_, 0, 3, '')) & 0x3FFFFF;
$n -= 0x400000 if $n & 0x200000;
return $n;
}
elsif (/^[\x80-\xBF]/) {
return if length() < 4;
my $n = unpack('L>', substr($_, 0, 4, '')) & 0x3FFFFFFF;
$n -= 0x40000000 if $n & 0x20000000;
return $n;
}
elsif (/^\xC0/) {
return if length() < 5;
return unpack('xl>', substr($_, 0, 5, ''));
}
elsif (length() == 0) {
return;
}
}
croak("Bad data");
}
Test:
my $s =
join '',
map { pack_vint32($_) }
map { $_, -$_ }
130, 555, 0x12, 0x345678, 0x12345678;
say length($s);
say sprintf("%v02X", $s);
while ( my ($n) = unpack_vint32($s) ) {
say $n;
}
croak("Bad data") if length($s);
Output:
28
00.82.3F.7E.02.2B.3D.D5.00.12.3F.EE.80.34.56.78.BF.CB.A9.88.92.34.56.78.AD.CB.A9.88
----- ----- ----- ----- ----- ----- ----------- ----------- ----------- -----------
130 | | | | | | | | |
-130 ---+ | | | | | | | |
555 ----------+ | | | | | | |
-555 ---------------+ | | | | | |
18 -----------------------+ | | | | |
-18 ----------------------------+ | | | |
3430008 ---------------------------------+ | | |
-3430008 --------------------------------------------+ | |
305419896 -------------------------------------------------------+ |
-305419896 ------------------------------------------------------------------+

I think the question wants the string (ASCII) representation in hexadecimal that takes the minimum number of bytes in two's complement. I agree with comments and another answer regarding this being ambiguous, but it may be fine for "human" consumption. In this case:
sub min_bytes {
my $n = shift;
my $s = 256; # Fits in one byte
my $i = 1; # Bytes counter
while( 1 ) {
if( $n < 0 && -$n <= $s / 2 ) {
return $i;
}
elsif( $n >= 0 && $n < ($s / 2) - 1 ) {
return $i;
}
$s *= 256;
$i++;
}
}
sub to_hex {
my $n = shift;
my $l = min_bytes($n);
my $h = $n > 0 ? $n : (256 ** $l) + $n;
my $s = '';
for( my $i = 0; $i < $l; $i++ ) {
$s = unpack('H2', pack('C', $n % 256)) . $s;
$n = $n >> 8;
}
return $s;
}
# Let's try a few numbers
my #numbers = (-10, -555, -100000, -100000000, -10000000000, -10000000000000);
for my $n (#numbers) {
my $l = min_bytes($n);
my $h = to_hex($n);
print $n, " takes ", $l, " byte(s) and looks like ", uc $h, "\n";
}
I have tried not to use anything complicated. The bit unpack('H2', pack('C', $n % 256)) converts a single byte decimal number into hex.
This prints:
-10 takes 1 byte(s) and looks like F6
-555 takes 2 byte(s) and looks like FDD5
-100000 takes 3 byte(s) and looks like FE7960
-100000000 takes 4 byte(s) and looks like FA0A1F00
-10000000000 takes 5 byte(s) and looks like FDABF41C00
-10000000000000 takes 6 byte(s) and looks like F6E7B18D6000
The code uses Perl arithmetic, which may give wrong results for bigger numbers than the internal representation's precision.

Related

Perl: How to align different columns according to their size

I have file something like this,
SR Name Rollno Class
1 Sanjay 01 B
2 Rahul Kumar 09 A
Now I need to add "|" between each. So it should look like
SR | Name |Rollno | Class|
1 | Sanjay |01 | B |
2 | Rahul Kumar Khanna|09 | A |
I tried this,
sub alignment {
my ( $string ) = #_;
my $blk_len = 15; #Assuming some block size.
my $len = length $string;
my $right = $blk_len - $len;
my $string = $string . ( " " x $right );
return $string;
}
But the problem is if the name of the person is big I need to increase the block size so that the "|" could come properly.
The block size will apply to all and I am getting like this
SR | Name | Rollno | Class |
1 | Sanjay | 01 | B |
2 | Rahul Kumar Khanna | 09 | A |
UPDATE:
I am using Perl6::form
my $text;
foreach my $line (#arr) {
my ($SR, $Name, $Rollno, $Class) = split (" ", $line);
my $len = length $Name;
$text = form
'| {||||||||} | {||||||||} | {||||||||} | {||||||||}|',
$SR, $Name, $Rollno, $Class
print $text;
}
};
Here till now I have done but the name is not comming properly . I have add extra "|" in name for that. Is there any way we can add "|" by calculating length like(below) but geeting error.
{||||||}x$len
If that function is being applied to each field you will determine the maximum length for each column and keep an external record of that. That value, along with the field text would need to be feed to the alignment function (changes needed) and the expected right padding could then be produced.
Trying to change your code as little as possible:
#!/usr/bin/perl
my #lines;
my #max;
# Read file line by line.
while (my $line = <STDIN>) {
next if ($line =~ /^\s+?$/);
chomp($line);
my #fields;
# store lines, field by field
push #fields, split(/\s{2,}/, $line);
push #lines, \#fields;
# check for maximum fields length
for (my $i = 0; $i <= $#fields; ++$i) {
$max[$i] = length($fields[$i]) if ($#max < $i || $max[$i] < length($fields[$i]));
}
}
# Format each line
foreach my $line (#lines) {
for (my $i = 0; $i <= $#{$line}; ++$i) {
print alignment($$line[$i], $max[$i]) . "|";
}
print "\n";
}
# Your function with minimum changes
sub alignment {
# Added a new parameter
my ($string, $m_field_size) = #_;
# Determine the blk_len to use. The default value 15 would be better of as a Constant.
my $blk_len = ($m_field_size > 15 ? $m_field_size : 15); #Assuming some block size
my $len = length $string;
my $right = $blk_len - $len;
my $string = $string . ( " " x $right );
return $string;
}
This is all it is needed.
Please study following approach if it fits your task
extend string to max length
split into array based on field lengths
join array element with separator
use strict;
use warnings;
use feature 'say';
my $length = 32;
while( my $str = <DATA> ) {
chomp $str;
if ( $str eq '' ) {
say $str;
} else {
$str .= ' ' for length($str)..$length;
say join '| ', unpack 'a5a13a9a6a', $str;
}
}
# 1 2 3 4
#1234567890123456789012345678901234567890
__DATA__
SR Name Rollno Class
1 Sanjay 01 B
2 Rahul Kumar 09 A
Output
SR | Name | Rollno | Class |
1 | Sanjay | 01 | B |
2 | Rahul Kumar | 09 | A |

Using Tree::DAG_Node to print list heap into tree-format

To make it simple, I am trying to get this heap to print in a tree like format. It's close but I know I am missing stuff, but I just can't wrap my head around this module. I know there is tree::simple and I think just Tree? But I can't really find any tutorials on how to actually use with a list or a array. The heap sort is right, cause it's sorting the list after the tree has been posted but I can't figure how to draw the tree correctly, then again outputting has never been my strong suit on any language. I think it's not grabbing the data from the file? least that is my idea but i'm not confident enough to be sure. here is my code so far.
#!/usr/bin/perl
use 5.006;
use strict;
use warnings;
use Tree::DAG_Node;
process_data(read_file('data.txt'));
process_data((3,1,4,1,5,9,2,6,5,3,6));
sub read_file{
my($filename)=#_;
my #data=();
my #words;
open(my $fh, "<", $filename)
or die "Could not open file: $!\n";
while(<$fh>){
chomp;
#words = split(' ');
foreach my $word(#words) {
push #data, $word;
}
}
close $fh;
return #data;
}
sub heap_sort {
my ($a) = #_;
my $n = #$a;
for (my $i = ($n - 2) / 2; $i >= 0; $i--) {
down_heap($a, $n, $i);
}
for (my $i = 0; $i < $n; $i++) {
my $t = $a->[$n - $i - 1];
$a->[$n - $i - 1] = $a->[0];
$a->[0] = $t;
down_heap($a, $n - $i - 1, 0);
}
}
sub down_heap {
my ($a, $n, $i) = #_;
while (1) {
my $j = max($a, $n, $i, 2 * $i + 1, 2 * $i + 2);
last if $j == $i;
my $t = $a->[$i];
$a->[$i] = $a->[$j];
$a->[$j] = $t;
$i = $j;
}
sub max {
my ($a, $n, $i, $j, $k) = #_;
my $m = $i;
$m = $j if $j < $n && $a->[$j] > $a->[$m];
$m = $k if $k < $n && $a->[$k] > $a->[$m];
return $m;
}
}
sub draw_tree{
my(#data)=#_;
my $root = Tree::DAG_Node->new;
$root->name($_[0]);
$root->new_daughter->name($_) for ('1'..'10');
my #names = #data;
my $count =0;
for my $n ($root->daughters) {
for (split //, $names[$count++]) {
$n->new_daughter->name($_)
}
}
print map "$_\n", #{$root->draw_ascii_tree};
}
sub process_data{
my(#data)=#_;
my #a = #data;
print "#a\n";
print "\n";
heap_sort(\#a);
print "\n";
print "#a\n";
print "\n";
draw_tree(#a);
}
and here is the output I am getting so far.
10,4,5,2,1,7
Use of uninitialized value in split at HEAPSORTtree.pl line 77.
Use of uninitialized value in split at HEAPSORTtree.pl line 77.
Use of uninitialized value in split at HEAPSORTtree.pl line 77.
Use of uninitialized value in split at HEAPSORTtree.pl line 77.
Use of uninitialized value in split at HEAPSORTtree.pl line 77.
Use of uninitialized value in split at HEAPSORTtree.pl line 77.
Use of uninitialized value in split at HEAPSORTtree.pl line 77.
Use of uninitialized value in split at HEAPSORTtree.pl line 77.
Use of uninitialized value in split at HEAPSORTtree.pl line 77.
|
<10,4,5,2,1,7>
/---------------------------------------+--
-+---+---+---+---+---+---+---\
| |
| | | | | | | |
<1> <2>
<3> <4> <5> <6> <7> <8> <9> <10>
/-----------------+-----------------+---+---+---+---+---+---+---+---+---\
| | | | | | | | | | | |
<1> <Tree::DAG_Node=HASH(0x4b32dc)> <,> <4> <,> <5> <,> <2> <,> <1> <,> <7>
10,4,5,2,1,7
3 1 4 1 5 9 2 6 5 3 6
|
<1>
/---+---+---+---+---+---+---+---+---\
| | | | | | | | | |
<1> <2> <3> <4> <5> <6> <7> <8> <9> <10>
| | | | | | | | | |
<1> <1> <2> <3> <3> <4> <5> <5> <6> <9>
1 1 2 3 3 4 5 5 6 9 6
Press any key to continue . . .
the output I want is similar to this
|
<root>
/-------+-------+-------\
| | | |
<1> <d> <e> <f>
/---+---\ |
| | | <3>
<a> <b> <c> /---+---\
| | |
<g> <h> <i>

modify this code to print elements that differ a x number from previous position

I wrote this code to find substrings every x elements:
print "enter file path\n";
$letters = <>;
chomp ($letters);
$sequence = "";
open (LETTERS, $letters) or die "error opening\n";
print "how many letters at a shot\n";
$number = <>;
chomp ($number);
$size = length $sequence;
chomp ($size);
for ($i = 0; $i < $size; $i++) {
$test = substr ($sequence, $i, $number);
print "> Test $i\n";
print "$test\n";
if ($i >= $size - $number) {
last;
}
}
so if I open a file with this string and choose x = 3:
abcdefg
I get this result:
> Test 0 abc
> Test 1 bcd
> Test 2 cde
> Test 3 def
> Test 4 efg
Each substring differs from one position from the previous substring, I'd like to be able to control this number and dislocate the substring by 2 for example. So the result would be:
> Test 0 abc
> Test 2 cde
> Test 4 efg
Any suggestions? Thanks
Add:
...
$step = <>;
...
And change:
for ($i = 0; $i < $size; $i++) {
to
for ($i = 0; $i < $size; $i+=$step) {
for ($i = 0; $i < $size; $i += 2) {

perl: bignum not working with STDIN

#!/usr/bin/perl
use bignum;
$line = <STDIN>;
( $arr[0], $arr[1], $n ) = split( / /, $line );
$i = 2;
sub func {
while ( $i < $n ) {
$t = $arr[ $i - 1 ];
$arr[$i] = $arr[ $i - 1 ] * $arr[ $i - 1 ] + $arr[ $i - 2 ];
$i = $i + 1;
}
return $arr[ $i - 1 ];
}
print func;
when i am setting manual value for $arr[1] then bignum is working fine but when the value is taken from STDIN then it is being printed as integer no BIGINt. can anyone explain why this is happening.
When you use strings in a numeric context, perl converts them using your C library's atof(). This is not changed by bignum. If you'd like your strings converted in a different manner, I'd recommend Math::BigFloat->new or Math::BigInt->new.

How to group the values in foreach by if condition?

My script like this
use warnings;
use strict;
my #ar = <DATA>;
for(my $i = 0; $i<=$#ar; $i++){
$ar[$i] =~m/(\d+)$/g;
print "$ar[$i]\n" if ($& <= 15);
print "$ar[$i]\n" if ($& >100);
print "$ar[$i]\n" if ($& <40 && $& > 15);
}
__DATA__
hinsa 121
mkzin 12
mkva 34
mvakine 2
mzkev 9
mkvvz 5
mkhvzz 35
It gives the outputs but it is not group the value by if condition. and I also try this
#ar = <DATA>;
for(my $i = 0; $i<=$#ar; $i++){
$ar[$i] =~m/(\d+)$/g;
print "$ar[$i]\n" if ($& <= 15);
}
for(my $v = 0; $v<=$#ar; $v++){
$ar[$v] =~m/(\d+)$/g;
print "$ar[$v]\n" if ($& >100);
}
for(my $z = 0; $z<=$#ar; $z++){
$ar[$z] =~m/(\d+)$/g;
print "$ar[$z]\n" if ($& <40 && $& > 15);
}
In this code the second for condition is not working.
It gives the output:
mkzin 12
mvakine 2
mzkev 9
mkvvz 5
mkva 34
mkhvzz 35
I expect output is
mkzin 12
mvakine 2
mzkev 9
mkvvz 5
hisa 121
mkva 34
mkhvzz 35
How can i do it?
And also please explain, In my script 2 why the second foreach condition is not working?
#Hussain: When you write a perl code make sure that you use use strict; and use warnings;. I have modified your perl code and the problem with your code is you are trying to compare uninitialized $& value with a number. So it will throw a warning saying use of uninitialized $& in numeric gt (>) at so and so. For that i have modified with a scalar variable as shown below:
Input File(test.txt):
hinsa 121
mkzin 12
mkva 34
mvakine 2
mzkev 9
mkvvz 5
mkhvzz 35
Code:
use strict;
use warnings;
#Pass test.txt as an argument to the program
my $file = $ARGV[0];
open (my $fh, "<", $file) || die "cant open file";
my #ar = <$fh>;
for(my $i = 0; $i<=$#ar; $i++){
my $temp = 0;
($temp) = $ar[$i] =~ m/(\d+)/g;
print "$ar[$i]\n" if ($temp <= 15);
}
for(my $v = 0; $v<=$#ar; $v++){
my $temp = 0;
($temp) = $ar[$v] =~ m/(\d+)/g;
print "$ar[$v]\n" if ($temp > 100);
}
for(my $z = 0; $z<=$#ar; $z++){
my $temp = 0;
($temp) = $ar[$z] =~ m/(\d+)/g;
print "$ar[$z]\n" if ($temp <40 && $temp > 15);
}
close($fh);
Output:
mkzin 12
mvakine 2
mzkev 9
mkvvz 5
hisa 121
mkva 34
mkhvzz 35
There is no need for such convoluted code.
This program works by saving each line of the file into the appropriate element of array #groups, and printing the contents once the file has been read.
I hope you realise that lines with a value between 40 and 100 won't be printed at all?
use strict;
use warnings;
my #groups;
while (<DATA>) {
next unless /(\d+)/;
my $i;
$i = 0 if $1 <= 15;
$i = 1 if $1 > 100;
$i = 2 if $1 < 40 and $1 > 15;
push #{ $groups[$i] }, $_ if defined $i;
}
for (#groups) {
print for #$_;
print "\n";
}
__DATA__
hinsa 121
mkzin 12
mkva 34
mvakine 2
mzkev 9
mkvvz 5
mkhvzz 35
output
mkzin 12
mvakine 2
mzkev 9
mkvvz 5
hinsa 121
mkva 34
mkhvzz 35