print truth table in to odt file - perl

i want to print truth table in to a table in an adt file, t got a program but i don know how to get vale or value to print to odt file, this program just print result on screen !
sub truth_table {
my $s = shift;
#print "$s\n";
my #vars;
for ($s =~ /([a-zA-Z_]\w*)/g) {
push #vars, $_ ;
}
#print "$s\n";
#print "$_\n";
#print Dumper \#vars;
#print "\n", join("\t", #vars, $s), "\n", '-' x 40, "\n";
#print Dumper \#vars;
#vars = map("\$$_", #vars);
$s =~ s/([a-zA-Z_]\w*)/\$$1/g;
$s = "print(".join(',"\t",', map("($_?'1':'0')", #vars, $s)).",\"\\n\")";
$s = "for my $_ (0, 1) { $s }" for (reverse #vars);
eval $s;
}
truth_table 'A ^ A_1';

Get the result of eval with Capture::Tiny, then split the string into a two-dimensional array based on https://stackoverflow.com/a/4226073/5100564.
use Capture::Tiny 'capture_stdout';
sub truth_table {
#...the rest of your code here...
my $stdout = capture_stdout {
eval $s;
};
return $stdout;
}
$truth_string = truth_table 'A ^ A_1';
my #truth_array;
foreach my $line (split "\n", $truth_string) {
push #truth_array, [split ' ', $line];
}
foreach my $line (#truth_array) {
foreach my $val (#$line) {
print $val;
}
print "\n";
}
For this to work, I executed the following commands based on What's the easiest way to install a missing Perl module?
cpan
install Capture::Tiny
However, I would solve this problem in LibreOffice with a Python macro instead. APSO makes it convenient to enter and run this code.
import uno
from itertools import product
def truth_table():
NUM_VARS = 2 # A and B
columns = NUM_VARS + 1
rows = pow(2, NUM_VARS) + 1
oDoc = XSCRIPTCONTEXT.getDocument()
oText = oDoc.getText()
oCursor = oText.createTextCursorByRange(oText.getStart())
oTable = oDoc.createInstance("com.sun.star.text.TextTable")
oTable.initialize(rows, columns)
oText.insertTextContent(oCursor, oTable, False)
for column, heading in enumerate(("A", "B", "A ^ B")):
oTable.getCellByPosition(column, 0).setString(heading)
row = 1 # the second row
for p in product((0, 1), repeat=NUM_VARS):
result = truth_function(*p)
for column in range(NUM_VARS):
oTable.getCellByPosition(column, row).setString(p[column])
oTable.getCellByPosition(column + 1, row).setString(result)
row += 1
def truth_function(x, y):
return pow(x, y);
g_exportedScripts = truth_table,
Using product in this way is based on Creating a truth table for any expression in Python.
More documentation for Python-UNO can be found at https://wiki.openoffice.org/wiki/Python.

Related

Perl :Not a HASH reference while creating nested hash from multi dimension arrays

I want to create a nested hash by reading values from multi dimention arrays which are separated by -> e.g.
Array 1: key1->key2->key3->value
Array 2: key1->key2->value
Array 3: key1->value
When key has value and sub keys as well e.g. key2 has value and another key key3 as well then get error "Not a HASH reference ".Seem it is overwriting previous hash and considering it array.
Help is appreciated. I have tried to debug and print the values of variables and output by using dumper module and see that it is ARRAY reference and not hash.
in order to repro, please create .txt files e.g. from 1 to 3.txt in any folder and have below content in these files 1.txt : /TEST-TAG = ABC->DEF->fma->GHI/ 2.txt:/*TEST-TAG = ABC->DEF->fma 3.txt:/*TEST-TAG = ABC->DEF and then have in perl script
#!/usr/bin/perl
use strict;
use warnings;
my #lines=`grep -R 'TEST-TAG =' <FOLDER where .txt files present>`;
my $hash;
#parse the lines which has pattern /\*TEST-TAG = ABC->DEF->fma->GHI\*/
foreach (#lines)
{
print "line is $_\n";
my($all_cat) = $_ =~ /\=(.*)\*\//;
print "all cat is $all_cat\n";
my($testname) = $_ =~ /\/.*\/(.*)\./;
print "testname is $testname\n";
if (!$all_cat eq "") {
$all_cat =~ s/ //g;
my #ts = split(',', $all_cat);
print "ts is #ts\n";
my $i;
foreach (#ts) {
my #allfeat = split('->',$_);
my $count = scalar #allfeat;
for ($i = 0; $i<$count; $i++) {
my #temparr = #allfeat[$i..$count-1];
print "temparr is #temparr\n";
push #temparr, $testname;
ToNestedHash($hash, #temparr);
}
}
}
}
sub ToNestedHash {
my $ref = \shift;
print "sandeep in ref $ref\n";
print "sandeep in ref", ref($ref), "\n";
my $h = $$ref;
print "sandeep h $h\n";
my $value = pop;
print "sandeep value is $value\n";
print "sandeep array is #_\n";
print "refrence", ref($h), "\n";
foreach my $i (#_) {
print " before INDEX $i\n";
print Dumper($ref);
$ref =\$$ref->{ $i };
print "after INDEX $i\n";
print Dumper($ref);
}
if (!isinlist(\#{$$ref},$value)) {
push #{$$ref}, $value;
}
return $h;
}
# If element exists in the list
sub isinlist {
my ($aref, $key) = ($_[0], $_[1]);
foreach my $elem (#$aref){
if ($elem eq $key) {
return 1;
}
}
return 0;
}
I get this output with debug prints
line is File.txt:/*TEST-TAG = ABC->DEF->fma->GHI*/
all cat is ABC->DEF->fma->GHI
testname is hmma_884_row_row_f16_f16
ts is ABC->DEF->fma->GHI
temparr is ABC DEF fma GHI
sandeep in ref REF(0x12a1048)
sandeep in refREF
sandeep h HASH(0x12a09a0)
sandeep value is hmma_884_row_row_f16_f16
sandeep array is ABC DEF fma GHI
refrenceHASH
REF
temparr is DEF fma GHI
sandeep in ref REF(0x12a1048)
sandeep in refREF
sandeep h HASH(0x12a09a0)
sandeep value is hmma_884_row_row_f16_f16
sandeep array is DEF fma GHI
refrenceHASH
REF
temparr is fma GHI
sandeep in ref REF(0x12a1048)
sandeep in refREF
sandeep h HASH(0x12a09a0)
sandeep value is hmma_884_row_row_f16_f16
sandeep array is fma GHI
refrenceHASH
Not a HASH reference at createjson.pl line 80.
problematic line is $ref =\$$ref->{$_} foreach (#_);
After sleeping on it, I realized where you were trying to go with this more. Your issue of concern is the fact that you're trying to use some hash values as both arrays and as hashes. There are two approaches to dealing with this. Detect and handle it, or avoid it. The avoid code is much cleaner, so I'll show that.
As I mentioned in my original answer, I'm not sure what you had in mind for the 'Dumper' lines, but Data::Dump is probably a useful replacement for what you were using, with less complication than the Data::Dumper module that I was thinking you were somehow managing to use. I also chose to still provide a replacement for your file name regex, as I still don't want to bother with a full path name.
#!/usr/bin/perl
use strict;
use warnings;
use Data::Dump;
my #lines = `grep -R 'TEST-TAG =' foo`;
my $hash;
$| = 1; # keep STDOUT and STDERR together
#parse the lines which has pattern /\*TEST-TAG = ABC->DEF->fma->GHI\*/
foreach (#lines) {
print "line is $_\n";
my($all_cat) = $_ =~ /\=(.*)\*\//;
print "all cat is $all_cat\n";
my($testname) = $_ =~ /(?:.*\/)?(.*?)\./;
print "testname is $testname\n";
if ($all_cat ne "") {
$all_cat =~ s/ //g;
my #ts = split(',', $all_cat);
print "ts is #ts\n";
my $i;
foreach (#ts) {
my #allfeat = split('->',$_);
my $count = scalar #allfeat;
for ($i = 0; $i<$count; $i++) {
my #temparr = #allfeat[$i..$count-1];
print "temparr is #temparr\n";
push #temparr, $testname;
ToNestedHash($hash, #temparr);
}
}
}
}
sub ToNestedHash {
my $ref = \shift;
print "sandeep in ref ";
dd $ref;
my $h = $$ref;
print "sandeep h ";
dd $h;
my $value = pop;
print "sandeep value is $value\n";
print "sandeep array is #_\n";
print "refrence", ref($h), "\n";
foreach my $i (#_) {
print " before INDEX $i\n";
dd $ref;
$ref =\$$ref->{ $i };
print "after INDEX $i\n";
dd $ref;
}
$ref =\$$ref->{ _ARRAY };
if (!isinlist(\#{$$ref},$value)) {
push #{$$ref}, $value;
}
return $h;
}
# If element exists in the list
sub isinlist {
my ($aref, $key) = ($_[0], $_[1]);
foreach my $elem (#$aref){
if ($elem eq $key) {
return 1;
}
}
return 0;
}

Handling Nested Delimiters in perl

use strict;
use warnings;
my %result_hash = ();
my %final_hash = ();
Compare_results();
foreach my $key (sort keys %result_hash ){
print "$key \n";
print "$result_hash{$key} \n";
}
sub Compare_results
{
while ( <DATA> )
{
my($instance,$values) = split /\:/, $_;
$result_hash{$instance} = $values;
}
}
__DATA__
1:7802315095\d\d,7802315098\d\d;7802025001\d\d,7802025002\d\d,7802025003\d\ d,7802025004\d\d,7802025005\d\d,7802025006\d\d,7802025007\d\d
2:7802315095\d\d,7802025002\d\d,7802025003\d\d,7802025004\d\d,7802025005\d\d,7802025006\d\d,7802025007\d\d
Output
1
7802315095\d\d,7802315098\d\d;7802025001\d\d,7802025002\d\d,7802025003\d\d,7802025004\d\d,7802025005\d\d,7802025006\d\d,7802025007\d\d
2
7802315095\d\d,7802025002\d\d,7802025003\d\d,7802025004\d\d,7802025005\d\d,7802025006\d\d,7802025007\d\d
Iam trying to fetch value of each key and again trying to split the comma seperated value from result hash , if i find a semicolon in any value i would want to store the left and right values in separate hash keys.
Something like below
1.#split the value of result_hash{$key} again by , and see whether any chunk is seperated by ;
2. #every chunk without ; and value on left with ; should be stored in
#{$final_hash{"eto"}} = ['7802315095\d\d','7802315098\d\d','7802025002\d\d','7802025003\d\d','7802025004\d\d','7802025005\d\d','7802025006\d\d','7802025007\d\d'] ;
3.#Anything found on the right side of ; has to be stored in
#{$final_hash{"pro"}} = ['7802025001\d\d'] ;
Is there a way that i can handle everything in the subroutine? Can i make the code more simpler
Update :
I tried splitting the string in a single shot, but its just picking the values with semicolon and ignoring everything
foreach my $key (sort keys %result_hash ){
# print "$key \n";
# print "$result_hash{$key} \n";
my ($o,$t) = split(/,|;/, $result_hash{$key});
print "Left : $o \n";
print "Left : $t \n";
#push #{$final_hash{"eto"}}, $o;
#push #{$final_hash{"pro"}} ,$t;
}
}
My updated code after help
sub Compare_results
{
open my $fh, '<', 'Data_File.txt' or die $!;
# split by colon and further split by , and ; if any (done in insert_array)
my %result_hash = map { chomp; split ':', $_ } <$fh> ;
foreach ( sort { $a <=> $b } (keys %result_hash) )
{
($_ < 21)
? insert_array($result_hash{$_}, "west")
: insert_array($result_hash{$_}, "east");
}
}
sub insert_array()
{
my ($val,$key) = #_;
foreach my $field (split ',', $val)
{
$field =~ s/^\s+|\s+$//g; # / turn off editor coloring
if ($field !~ /;/) {
push #{ $file_data{"pto"}{$key} }, $field ;
}
else {
my ($left, $right) = split ';', $field;
push #{$file_data{"pto"}{$key}}, $left if($left ne '') ;
push #{$file_data{"ero"}{$key}}, $right if($right ne '') ;
}
}
}
Thanks
Update Added a two-pass regex, at the end
Just proceed systematically, analyze the string step by step. The fact that you need consecutive splits and a particular separation rule makes it unwieldy to do in one shot. Better have a clear method than a monster statement.
use warnings 'all';
use strict;
use feature 'say';
my (%result_hash, %final_hash);
Compare_results();
say "$_ => $result_hash{$_}" for sort keys %result_hash;
say '---';
say "$_ => [ #{$final_hash{$_}} ]" for sort keys %final_hash;
sub Compare_results
{
%result_hash = map { chomp; split ':', $_ } <DATA>;
my (#eto, #pro);
foreach my $val (values %result_hash)
{
foreach my $field (split ',', $val)
{
if ($field !~ /;/) { push #eto, $field }
else {
my ($left, $right) = split ';', $field;
push #eto, $left;
push #pro, $right;
}
}
}
$final_hash{eto} = \#eto;
$final_hash{pro} = \#pro;
return 1; # but add checks above
}
There are some inefficiencies here, and no error checking, but the method is straightforward. If your input is anything but smallish please change the above to process line by line, what you clearly know how to do. It prints
1 => ... (what you have in the question)
---
eto => [ 7802315095\d\d 7802315098\d\d 7802025002\d\d 7802025003\d\ d ...
pro => [ 7802025001\d\d ]
Note that your data does have one loose \d\ d.
We don't need to build the whole hash %result_hash for this but only need to pick the part of the line after :. I left the hash in since it is declared global so you may want to have it around. If it in fact isn't needed on its own this simplifies
sub Compare_results {
my (#eto, #pro);
while (<DATA>) {
my ($val) = /:(.*)/;
foreach my $field (split ',', $val)
# ... same
}
# assign to %final_hash, return from sub
}
Thanks to ikegami for comments.
Just for the curiosity's sake, here it is in two passes with regex
sub compare_rx {
my #data = map { (split ':', $_)[1] } <DATA>;
$final_hash{eto} = [ map { /([^,;]+)/g } #data ];
$final_hash{pro} = [ map { /;([^,;]+)/g } #data ];
return 1;
}
This picks all characters which are not , or ;, using the negated character class, [^,;]. So that is up to the first either of them, left to right. It does this globally, /g, so it keeps going through the string, collecting all fields that are "left of" , or ;. Then it cheats a bit, picking all [^,;] that are right of ;. The map is used to do this for all lines of data.
If %result_hash is needed build it instead of #data and then pull the values from it with my #values = values %hash_result and feed the map with #values.
Or, broken line by line (again, you can build %result_hash instead of taking $data directly)
my (#eto, #pro);
while (<DATA>) {
my ($data) = /:(.*)/;
push #eto, $data =~ /([^,;]+)/g;
push #pro, $data =~ /;([^,;]+)/g;
}

Perl compare individual elements of two arrays

I have two files with two columns each:
FILE1
A B
1 #
2 #
3 !
4 %
5 %
FILE 2
A B
3 #
4 !
2 &
1 %
5 ^
The Perl script must compare column A in both both files, and only if they are equal, column B of FIlE 2 must be printed
So far I have the following code but all I get is an infinite loop with # from column B
use strict;
use warnings;
use 5.010;
print "enter site:"."\n";
chomp(my $s = <>);
print "enter protein:"."\n";
chomp(my $p = <>);
open( FILE, "< $s" ) or die;
open( OUT, "> PSP.txt" ) or die;
open( FILE2, "< $p" ) or die;
my #firstcol;
my #secondcol;
my #thirdcol;
while ( <FILE> )
{
next if $. <2;
chomp;
my #cols = split;
push #firstcol, $cols[0];
push #secondcol, $cols[1]."\t"."\t".$cols[3]."\t"."\t"."\t"."N\/A"."\n";
}
my #firstcol2;
my #secondcol2;
my #thirdcol2;
while ( <FILE2> )
{
next if $. <2;
my #cols2 = split(/\t/, $_);
push #firstcol2, $cols2[0];
push #secondcol2, $cols2[4]."\n";
}
my $size = #firstcol;
my $size2 = #firstcol2;
for (my $i = 0; $i <= #firstcol ; $i++) {
for (my $j = 0; $j <= #firstcol2; $j++) {
if ( $firstcol[$i] eq $firstcol2[$j] )
{
print $secondcol2[$i];
}
}
}
my (#first, #second);
while(<first>){
chomp;
my $foo = split / /, $_;
push #first , $foo;
}
while(<second>){
chomp;
my $bar = split / / , $_;
push #second, $bar;
}
my %first = #first;
my %second = #second;
Build a hash of the first file as %first and second file as %second with first column as key and second column as value.
for(keys %first)
{
print $second{$_} if exists $second{$_}
}
I couldn't check it as I am on mobile. hope that gives you an idea.
I assume that column A is ordered and that you actually want to compare the first entry in File 1 to the first entry in File 2, and so on.
If that's true, you have nested loop that you don't need. Simplify your last while as such:
for my $i (0..$#firstcol) {
if ( $firstcol[$i] eq $firstcol2[$i] )
{
print $secondcol2[$i];
}
}
Also, if you're at all concerned about the files being of different length, then you can adjust the loop:
use List::Util qw(min);
for my $i (0..min($#firstcol, $#firstcol2)) {
Additional Note: You aren't chomping your data in the second file loop while ( <FILE2> ). That might introduce a bug later.
If your files are called file1.txt and file2.txt the next:
use Modern::Perl;
use Path::Class;
my $files;
#{$files->{$_}} = map { [split /\s+/] } grep { !/^\s*$/ } file("file$_.txt")->slurp for (1..2);
for my $line1 (#{$files->{1}}) {
my $line2 = shift #{$files->{2}};
say $line2->[1] if ($line1->[0] eq $line2->[0]);
}
prints:
B
^
equals in column1 only the lines A and 5
without the CPAN modules - produces the same result
use strict;
use warnings;
my $files;
#{$files->{$_}} = map { [split /\s+/] } grep { !/^\s*$/ } do { local(#ARGV)="file$_.txt";<> } for (1..2);
for my $line1 (#{$files->{1}}) {
my $line2 = shift #{$files->{2}};
print $line2->[1],"\n" if ($line1->[0] eq $line2->[0]);
}

Output only last value

I have some code that reads from a file, and outputs the Fibonacchi numbers. E.g: 5 = 1, 1, 2, 3, 5
How can I make my code ONLY print out the last value?
Thanks
#!/usr/bin/perl
use strict;
my $fibFile = shift;
if (!defined($fibFile)) {
die "[*] No file specified...\n";
}
open (FILE, "<$fibFile");
my #numbers = <FILE>;
foreach my $n (#numbers) {
my $a = 1;
my $b = 1;
for (0..($n - 1)) {
print "$a\n";
($a, $b) = ($b,($a + $b));
}
print "\n";
}
close (FILE);
I suggest using a subroutine to take a chunk of code out of the loop
sub fib {
my $n = shift();
my #fib = (1, 1);
push #fib, $fib[-1] + $fib[-2] while #fib < $n;
#fib[0 .. $n-1];
}
for my $n (1 .. 5) {
printf "%d = %s\n", $n, join ', ', fib $n;
}
Do you need to recalculate the Fibonacci series for every value in the file? If not then just move the #fib array declaration outside the subroutine and the data won't need to be recalculated.
I'm sorry I didn't answer the question! To print out only the last value in the sequence, change the loop limit in your code to $n-2 and move the line print "$a\n"; outside the loop to replace the line print "\n";

How can add values in each row and column and print at the end in Perl?

Below is the sample csv file
date,type1,type2,.....
2009-07-01,n1,n2,.....
2009-07-02,n21,n22,....
and so on...
I want to add the values in each row and each column and print at the end and bottom of each line. i.e.
date,type1,type2
2009-07-01,n1,n2,.....row_total1
2009-07-02,n21,n22,....row_total2
Total,col_total1,col_total1,......total
Please suggest.
Less elegant and shorter:
$ perl -plaF, -e '$r=0;$r+=$F[$_],$c[$_]+=$F[$_]for 1..$#F;$_.=",$r";END{$c[0]="Total";print join",",#c}'
Quick and dirty, but should do the trick in basic cases. For anything more complex, use Text::CSV and an actual script.
An expanded version as it's getting a little hairy:
#! perl -plaF,
$r=0;
$r+=$F[$_], $c[$_]+=$F[$_] for 1..$#F;
$_.=",$r";
END { $c[0]="Total"; print join ",", #c }'
Here is a straightforward way which you can easily build upon depending on your requirements:
use strict;
use warnings;
use 5.010;
use List::Util qw(sum);
use List::MoreUtils qw(pairwise);
use Text::ParseWords;
our ($a, $b);
my #header = parse_csv( scalar <DATA> );
my #total = (0) x #header;
output_csv( #header, 'row_total' );
for my $line (<DATA>) {
my #cols = parse_csv( $line );
my $label = shift #cols;
push #cols, sum #cols;
output_csv( $label, #cols );
#total = pairwise { $a + $b } #total, #cols;
}
output_csv( 'Total', #total );
sub parse_csv {
chomp( my $data = shift );
quotewords ',', 0, $data;
}
sub output_csv { say join ',' => #_ }
__DATA__
date,type1,type2
2009-07-01,1,2
2009-07-02,21,22
Outputs the expected:
date,type1,type2,row_total
2009-07-01,1,2,3
2009-07-02,21,22,43
Total,22,24,46
Some things to take away from above is the use of List::Util and List::MoreUtils:
# using List::Util::sum
my $sum_of_all_values_in_list = sum #list;
# using List::MoreUtils::pairwise
my #two_arrays_added_together = pairwise { $a + $b } #array1, #array2;
Also while I've used Text::ParseWords in my example you should really look into using Text::CSV. This modules covers more bizarre CSV edge cases and also provides correct CSV composition (my output_csv() sub is pretty naive!).
/I3az/
Like JB's perlgolf candidate, except prints the end line totals and labels.
#!/usr/bin/perl -alnF,
use List::Util qw(sum);
chomp;
push #F, $. == 1 ? "total" : sum(#F[1..$#F]);
print "$_,$F[-1]";
for (my $i=1;$i<#F;$i++) {
$totals[$i] += $F[$i];
}
END {
$totals[0] = "Total";
print join(",",#totals);
};
Is this something that needs to be done for sure in a Perl script? There is no "quick and dirty" method to do this in Perl. You will need to read the file in, accumulate your totals, and write the file back out (processing input and output line by line would be the cleanest).
If this is a one-time report, or you are working with a competent user base, the data you want can most easily be produced with a spreadsheet program like Excel.
Whenever I work with CSV, I use the AnyData module. It may add a bit of overhead, but it keeps me from making mistakes ("Oh crap, that date column is quoted and has commas in it!?").
The process for you would look something like this:
use AnyData;
my #columns = qw/date type1 type2 type3/; ## Define your input columns.
my $input = adTie( 'CSV', 'input_file.csv', 'r', {col_names => join(',', #columns)} );
push #columns, 'total'; ## Add the total columns.
my $output = adTie( 'CSV', 'output_file.csv', 'o', {col_names => join(',', #columns)} );
my %totals;
while ( my $row = each %$input ) {
next if ($. == 1); ## Skip the header row. AnyData will add it to the output.
my $sum = 0;
foreach my $col (#columns[1..3]) {
$totals{$col} += $row->{$col};
$sum += $row->{$col};
}
$totals{total} += $sum;
$row->{total} = $sum;
$output->{$row->{date}} = $row;
}
$output->{Total} = \%totals;
print adDump( $output ); ## Prints a little table to see the data. Not required.
undef $input; ## Close the file.
undef $output;
Input:
date,type1,type2,type3
2009-07-01,1,2,3
2009-07-03,31,32,33
2009-07-06,61,62,63
"Dec 31, 1969",81,82,83
Output:
date,type1,type2,type3,total
2009-07-01,1,2,3,6
2009-07-03,31,32,33,96
2009-07-06,61,62,63,186
"Dec 31, 1969",81,82,83,246
Total,174,178,182,534
The following in Perl does what you want, its not elegant but it works :-)
Call the script with the inputfile as argument, results in stdout.
chop($_ = <>);
print "$_,Total\n";
while (<>) {
chop;
split(/,/);
shift(#_);
$sum = 0;
for ($n = 0; 0 < scalar(#_); $n++) {
$c = shift(#_);
$sum += $c;
$sums[$n] += $c;
}
$total += $sum;
print "$_,$sum\n";
}
print "Total";
for ($n = 0; $n <= $#sums; $n++) {
print "," . $sums[$n];
}
print ",$total\n";
Edit: fixed for 0 values.
The output is like this:
date,type1,type2,type3,Total
2009-07-01,1, 2, 3,6
2009-07-02,4, 5, 6,15
Total,5,7,9,21