Build number with addition and multiplication - arrays

I am participating in a contest and can't seem to find a good solution to this problem:
We're given a number called Target, and a sequence of natural numbers, length of the sequence unspecified.
Find out the closest we can come to Target only by using addition and multiplication between the numbers given, without using brackets '()' and each number not more than once.
examples:
$ cat test_case_1.txt
3972
87 5 47
$
$ ./program < test_case_1.txt
4089
87*47
$
$ cat test_case_2.txt
119
3 5 17 37 61 87
$
$ ./program < test_case_2.txt
119
87+17+5*3
$
$ cat test_case_3.txt
424
17 19 23 29 31
$
$ ./program < test_case_3.txt
422
31+23*17
$
$ cat test_case_4.txt
1337
23 29 31 47 53
$
$ ./program < test_case_4.txt
1326
47+31+29+23*53
$
$ cat test_case_5.txt
4291
31 47 53 67 71 79 83
$
$ ./program < test_case_5.txt
4289
71+31+53*79
$
$ cat test_case_6.txt
256952
5 7 11 17 47 67 71
$
$ ./program < test_case_6.txt
256954
5+7*11*47*71
$

Related

Deleting rows where the difference between column 1 and column 2 have a greater difference than 1

Some of the the values in columns Molecular.Weight and m.z are quite similar, often differing only by 1.0 or less. But there are some instances where its greater than 1.0. I would like to generate a new dataset that only includes the rows with a difference less than or equal to 1.0. However, it can be either column that has the higher number, so I am struggling to make an equation that works.
'data.frame': 544 obs. of 48 variables:
$ X : int 1 2 3 4 5 6 7 8 9 10 ...
$ No. : int 2 32 34 95 114 141 169 234 236 278 ...
$ RT..min. : num 0.89 3.921 0.878 2.396 0.845 ...
$ Molecular.Weight : num 70 72 72 78 80 ...
$ m.z : num 103 145 114 120 113 ...
$ HMDB.ID : chr "HMDB0006804" "HMDB0031647" "HMDB0006112" "HMDB0001505" ...
$ Name : chr "Propiolic acid" "Acrylic acid" "Malondialdehyde" "Benzene" ...
$ Formula : chr "C3H2O2" "C3H4O2" "C3H4O2" "C6H6" ...
$ Monoisotopic_Mass: num 70 72 72 78 80 ...
$ Delta.ppm. : num 1.295 0.833 1.953 1.023 0.102 ...
$ X1 : num 288.3 16.7 1130.9 3791.5 33.5 ...
$ X2 : num 276.8 13.4 1069.1 3228.4 44.1 ...
$ X3 : num 398.6 19.3 794.8 2153.2 15.8 ...
$ X4 : num 247.6 100.5 1187.5 1791.4 33.4 ...
$ X5 : num 98.4 162.1 1546.4 1646.8 45.3 ...
I had to do it in 2 parts because I couldn't figure out how to combine them but its still not giving me the right result.
The first section is supposed to filter out the values where Molecular.Weight might be greater than m.z by 1, and the second then filters out when m.z might be greater than Molecular.Weight. The first part seems to work and gives me a new dataset with around half the number of rows, but then when I do the second part on it, it gives me 1 row (and its not even correct because that one compound does fall within the 1.0 difference). Any help is super appreciated, thanks!
rawdata <- read.csv("Analysis negative + positive minus QC.csv")
filtered_data <-c()
for (i in 1:nrow(rawdata)) {
if (rawdata$m.z[i]-rawdata$Molecular.Weight[i]<1)
filtered_data <- rbind(filtered_data, rawdata[i,])
}
newdata <- c()
for (i in 1:row(filtered_data)) {
if ((filtered_data$Molecular.Weight[i] - filtered_data$m.z[i])>1)
newdata <- rbind(newdata, filtered_data[i,])
}

How to pipe the output of a sed command into a function in unix

I have this C function that I am using to output any amount of integer that is input.
/*
This program will be called function.c
*/
#include <stdio.h>
int main (void) {
int num;
while(scanf("%d",&num)==1) {
printf("You entered: %d\n",num);
}
return 0;
}
In my shell the function works like this as expected with the pipe command
$echo "1 7 3 " | ./function
Output:
You entered: 1
You entered: 7
You entered: 3
Now what I'm trying to do is use the sed command on a csv file and pipe the output into my function.
Here is my CSV file
$cat file.csv
Output:
2,2,3,3,8
Using the sed command I remove the commas
$sed 's/,/ /g' file.csv
Output:
2 2 3 3 8
Now the issue I have is when I try to use the output of the sed command to pipe the numbers into my function:
$sed 's/,/ /g' file.csv | ./function
I get no output. I don't know if there is a syntax error, but I believe I should be able to do this with a csv file.
I say you have a BOM in "file.csv".
That will stop ./function from scanning the first number.
$ hexdump -C file.csv
00000000 ef bb bf 32 2c 32 2c 33 2c 33 2c 38 0a |...2,2,3,3,8.|
0000000d
$ sed 's/,/ /g' file.csv |hexdump -C
00000000 ef bb bf 32 20 32 20 33 20 33 20 38 0a |...2 2 3 3 8.|
0000000d
$ cat file.csv
2,2,3,3,8
$ cut -b4- file.csv |sed 's/,/ /g' |./function
You entered: 2
You entered: 2
You entered: 3
You entered: 3
You entered: 8
$ sed 's/,/ /g' file.csv |cut -b4- |./function
You entered: 2
You entered: 2
You entered: 3
You entered: 3
You entered: 8

Why after the while loop I am only getting last row value?

This is the files I am reading,
#Log1
Time Src_id Des_id Address
0 34 56 x9870
2 36 58 x9872
4 38 60 x9874
6 40 62 x9876
8 42 64 x9878
#Log2
Time Src_id Des_id Address
1 35 57 x9871
3 37 59 x9873
5 39 61 x9875
7 41 63 x9877
9 43 65 x9879
This the code I wrote where I am reading line by line and then spliting it
#!usr/bin/perl
use warnings;
use strict;
my $log1_file = "log1.log";
my $log2_file = "log2.log";
open(IN1, "<$log1_file" ) or die "Could not open file $log1_file: $!";
open(IN2, "<$log2_file" ) or die "Could not open file $log2_file: $!";
my $i_d1;
my $i_d2;
my #fields1;
my #fields2;
while (my $line = <IN1>) {
#fields1 = split " ", $line;
}
while (my $line = <IN2>) {
#fields2 = split " ", $line;
}
print "#fields1\n";
print "#fields2\n";
close IN1;
close IN2;
Output I am getting
8 42 64 x9878
9 43 65 x9879
Output Desired
Time Src_id Des_id Address
0 34 56 x9870
2 36 58 x9872
4 38 60 x9874
6 40 62 x9876
8 42 64 x9878
9 43 65 x9879
Time Src_id Des_id Address
1 35 57 x9871
3 37 59 x9873
5 39 61 x9875
7 41 63 x9877
9 43 65 x9879
If I use push(#fields1 , split " ", $line); I am getting output like this,
Time Src_id Des_id Address 0 34 56 x9870 B 36 58 x9872 D 38 60 x9874 F 40 62 x9876 H 42 64 x9878
It should print whole array but printing just last row?
Also after this I need to compare both the "Times" part of both log & print in sequence way but don't know how to run both array simultaneously in while loop?
Please suggest in standard way without any modules because I need to run this in someone else server.
Following code demonstrates how to read and print log files
(OP does not specify why he splits lines into fields)
use strict;
use warnings;
use feature 'say';
my $fname1 = 'log1.txt';
my $fname2 = 'log2.txt';
my $div = "\t";
my $file1 = read_file($fname1);
my $file2 = read_file($fname2);
print_file($file1,$div);
print_file($file2,$div);
sub read_file {
my $fname = shift;
my #data;
open my $fh, '<', $fname
or die "Couldn't read $fname";
while( <$fh> ) {
chomp;
next if /^#Log/;
push #data, [split];
}
close $fh;
return \#data;
}
sub print_file {
my $data = shift;
my $div = shift;
say join($div,#{$_}) for #{$data};
}
Output
Time Src_id Des_id Address
0 34 56 x9870
2 36 58 x9872
4 38 60 x9874
6 40 62 x9876
8 42 64 x9878
Time Src_id Des_id Address
1 35 57 x9871
3 37 59 x9873
5 39 61 x9875
7 41 63 x9877
9 43 65 x9879
Let's assume that OP wants to merge two files into one with sorted lines on Time field
read files into %data hash with Time field as key
print header (#fields)
print hash values sorted on Time key
use strict;
use warnings;
use feature 'say';
my(#fields,%data);
my $fname1 = 'log1.txt';
my $fname2 = 'log2.txt';
read_data($fname1);
read_data($fname2);
say join("\t",#fields);
say join("\t",#{$data{$_}}) for sort { $a <=> $b } keys %data;
sub read_data {
my $fname = shift;
open my $fh, '<', $fname
or die "Couldn't open $fname";
while( <$fh> ) {
next if /^#Log/;
if( /^Time/ ) {
#fields = split;
} else {
my #line = split;
$data{$line[0]} = \#line;
}
}
close $fh;
}
Output
Time Src_id Des_id Address
0 34 56 x9870
1 35 57 x9871
2 36 58 x9872
3 37 59 x9873
4 38 60 x9874
5 39 61 x9875
6 40 62 x9876
7 41 63 x9877
8 42 64 x9878
9 43 65 x9879
Because #fields* gets overwritten during each loop. You need this:
while(my $line = <IN1>){
my #tmp = split(" ", $line);
push(#fields1, \#tmp);
}
foreach $item (#fields1){
print("#{$item}\n");
}
Then #fields1 contains references pointing to the splited array.
The final #fields1 looks like:
#fields1 = (
<ref> ----> ["0", "34", "56", "x9870"]
<ref> ----> ["2", "36", "58", "x9872"]
...
)
The print will print:
Time Src_id Des_id Address
0 34 56 x9870
2 36 58 x9872
4 38 60 x9874
6 40 62 x9876
8 42 64 x9878
And I guess it would be better if you do chomp($line).
But I'd like to simply do push(#fields1, $line). And split each array item when in comparison stage.
To compare the content of 2 files, I personally would use 2 while loops to read into 2 arrays just like what you have done. Then do the comparison in one for or foreach.
You can merge the log files using paste, and read the resulting merged file one line at a time. This is more elegant and saves RAM. Here is an example of a possible comparison of time1 and time2, writing STDOUT and STDERR into separate files. The example prints into STDOUT all the input fields if time1 < time2 and time1 < 4, otherwise prints a warning into STDERR:
cat > log1.log <<EOF
Time Src_id Des_id Address
0 34 56 x9870
2 36 58 x9872
4 38 60 x9874
6 40 62 x9876
8 42 64 x9878
EOF
cat > log2.log <<EOF
Time Src_id Des_id Address
1 35 57 x9871
3 37 59 x9873
5 39 61 x9875
7 41 63 x9877
9 43 65 x9879
EOF
# Paste files side by side, skip header, read data lines together, compare and print:
paste log1.log log2.log | \
tail -n +2 | \
perl -lane '
BEGIN {
for $file_num (1, 2) { push #col_names, map { "$_$file_num" } qw( time src_id des_id address ) }
}
my %val;
#val{ #col_names } = #F;
if ( $val{time1} < $val{time2} and $val{time1} < 4) {
print join "\t", #val{ #col_names};
} else {
warn "not found: #val{ qw( time1 time2 ) }";
}
' 1>out.tsv 2>out.log
Output:
% cat out.tsv
0 34 56 x9870 1 35 57 x9871
2 36 58 x9872 3 37 59 x9873
% cat out.log
not found: 4 5 at -e line 10, <> line 3.
not found: 6 7 at -e line 10, <> line 4.
not found: 8 9 at -e line 10, <> line 5.
The Perl one-liner uses these command line flags:
-e : Tells Perl to look for code in-line, instead of in a file.
-n : Loop over the input one line at a time, assigning it to $_ by default.
-l : Strip the input line separator ("\n" on *NIX by default) before executing the code in-line, and append it when printing.
-a : Split $_ into array #F on whitespace or on the regex specified in -F option.
SEE ALSO:
perldoc perlrun: how to execute the Perl interpreter: command line switches

Insert spaces in the string array

I have the following array:
declare -a case=("060610" "080813" "101016" "121219" "141422")
I want to generate another array where the elements have whitespaces inserted appropriately as:
"06 06 10" "08 08 13" "10 10 16" "12 12 19" "14 14 22"
I got till handling the elements individually using sed as:
echo '060610' | sed 's/../& /g'
But I am not being able to do it while using the array. The sed confuses the white spaces in between the elements and I am left with the output:
echo "${case[#]}" | sed 's/../& /g'
Gives me:
06 06 10 0 80 81 3 10 10 16 1 21 21 9 14 14 22
Can someone help?
You need to loop over the array, not echo it as a whole, because you don't get the grouping when you echo it.
declare -a newcase
for c in "${case[#]}"
do
newcase+=("$(echo "$c" | sed 's/../& /g')")
done
You can use printf '%s\n' "${case[#]}" | sed 's/../& /g' to get each number on a separate line and avoiding the space problem:
$ declare -a case=("060610" "080813" "101016" "121219" "141422")
$ printf '%s\n' "${case[#]}" | sed 's/../& /g'
06 06 10
08 08 13
10 10 16
12 12 19
14 14 22
If you want it back into an array, you can use mapfile

Bash random number generator where number is not in array

I am attempting to create a random number generator that generates a number between 1 and 99 but not any number that has already been generated.
In the script array1 contains the numbers already generated. To make it easier to test I have reduced the random number range to 0 - 14 and manually created an array.
I am quite new to bash scripting and am picking it up with a couple of books and the internet.
I have tried a mixture of ideas, the one that seems to make most sense is
array1=( 1 2 3 6 7 8 9 10 11 12 13 )
func1() {
for var in "${array1[#]}"
do
echo $var
done
}
rnd=$[ $RANDOM % 14 ]
until [ $rnd != func1 ]
do
rnd=$[ $RANDOM % 14 ]
done
echo $rnd
however I know the problem is on line 9 the shell sees the following code:
until [ $rnd != 1 2 3 6 7 8 9 10 11 12 13 ]
I know that the solution is that line 9 needs to be:
until [ $rnd != 1 ] && [ $rnd != 2 ] && [ $rnd != 3 ] && ...
I just don't know how to make this happen automatically from the array. The array does vary in length depending on how many numbers have been generated.
Any help will be greatly appreciated!
This is something that I found difficulty doing in bash. The approach I came up with is to have func1() return true or false and modify the array to remove the number that has been picked.
array=( {1..15} )
func1() {
local pick="$1"
found=1
total=${#array[#]}
for ((i=0;i<total;i++)); do
if (( pick == ${array[i]} )); then
echo $pick
array=( ${array[#]:0:i} ${array[#]:((i + 1)):$total})
found=0
break
fi
done
return $found
}
numbers=3
for ((x=0;x<numbers;x++)); do
until func1 $(( $RANDOM % ( ${#array[#]} ) )); do
continue
done
done
As noted in one of the comments, using the Knuth Shuffle is an excellent way to do this
#!/bin/bash
shuffle() {
local i tmp size max rand
# Code from http://mywiki.wooledge.org/BashFAQ/026
# $RANDOM % (i+1) is biased because of the limited range of $RANDOM
# Compensate by using a range which is a multiple of the array size.
size=${#array[*]}
max=$(( 32768 / size * size ))
for ((i=size-1; i>0; i--)); do
while (( (rand=$RANDOM) >= max )); do :; done
rand=$(( rand % (i+1) ))
tmp=${array[i]} array[i]=${array[rand]} array[rand]=$tmp
done
}
# Fill an array with values 1 to 99
array=({1..99});
# Shuffle the array at random
shuffle
# Echo shuffled array
echo ${array[#]}
Output
$ ./knuth
58 78 6 37 84 79 81 43 50 25 49 56 99 41 26 15 86 11 96 90 76 46 92 70 87 27 33 91 1 2 73 97 65 69 42 32 39 67 72 52 36 64 24 88 60 35 83 89 66 30 4 53 57 28 75 48 40 74 18 23 45 61 20 31 21 16 68 80 62 8 98 14 7 19 47 55 22 85 59 17 77 10 63 93 51 54 95 82 94 9 44 38 13 71 34 29 5 3 12
You can also use the -R switch to sort, if your version of sort supports it:
for x in {1..99} ; do echo $x ; done | sort -R

Resources