Using awk to interpolate data based on if statement - bash

so I am trying to automate a data collection process by using awk to search the file for a certain pattern and plug values into the linear interpolation formula. The data in question tracks time versus position, and I need to interpolate the time at which position equals zero. Example:
100 0.5
200 0.2
300 -0.3
400 -0.7
Then, my interpolation looks like this:
interpolated_time = 200 + (0 - 0.2) * (300 - 200) / (-0.3 - 0.2)
I am going to write the script in bash and use bc calculator for the arithmetic. However, I am inexperienced with using awk and cannot figure out how to correctly search the file.
I want to do something like
awk '{if ($2 > 0) #add another statement to test if $2 < 0 on next line#}'
# If test is successful, store entries in variables or an array
The interpolation may need to be performed multiple times in one file. I may need to output all values in question to an array, and then input the paired indexes into the interpolation formula. (i.e. indices [1,2] [3,4] [5,6] are paired together for separate interpolations)
I know that awk works on a line-by-line test loop, but I don't know if there is a way to incorporate the previous or next line in the test (perhaps something like
next
or
getline
?)
Any suggestions or comments would be greatly appreciated!

This will give you the result 240
awk '{if(p2>0 && $2<0) print p1-p2*($1-p1)/($2-p2); p1=$1; p2=$2}'
doesn't handle if 0 is already in the data set and assumes transition is from positive to negative.

Related

Fastest way to index large sorted hash file

I am building a file-based index for the sorted haveibeenpwned passwords text file and it got me wondering what's the fastest way to do this?
I figured a good way to build a quickly grep-able index would be split the sorted file into 256 files named with the first two hex digits (i.e. FF.txt, FE.txt, etc). I found ripgrep rg to be about 5 times faster than grep on my computer. So I tried something like this:
for i in {255..0}
do
start=$(date +%s)
hex="$(printf '%02x' $i | tr [:lower:] [:upper:])"
rg "^$hex" pwned-passwords-ntlm-ordered-by-hash-v4.txt > ntlm/$hex-ntlm.txt
echo 0x$hex completed in $(($(date +%s) - $start)) seconds
done
This is the fastest solution I could come up with. ripgrep is able to create each file in 25 seconds. So I'm looking at about 100 minutes to create this index. When I split the job in half, and run them in parallel, each pair of files is created in 80 seconds. So it seems best to just let ripgrep work its magic and work in series.
Obviously, I won't be indexing this list too often, but it's just fun to think about. Any thoughts on a faster way (aside from using a database) to index this file?
ripgrep, like any other tool that's able to work with unsorted input files at all, is the wrong tool for this job. When you're trying to grep sorted inputs, you want something that can bisect your input file to find a position in logarithmic time. For big enough inputs, even a slow O(log n) implementation will be faster than a highly optimized O(n) one.
pts-line-bisect is one such tool, though of course you're also welcome to write your own. You'll need to write it in a language with full access to the seek() syscall, which is not exposed in bash.
You are reading through the file 256 times, doing a full file scan every time. Consider an approach that reads the file once, writing each line into an open file descriptor. I'm thinking python would be an easy choice of implementation (if that's your thing). You could optimize by keeping the file open until you hit a new hex code at the beginning of the line. If you want to be even more clever, there is no need to go through the sorted file line by line. Based on Charles Duffy's hint, you could create a heuristic for sampling the file (using seek()) to get to the next hex value. Once the program has found the byte offset of the next hex value, the block of bytes can be written to the new file. However, since this is tagged as 'bash' let's keep the solution set in that domain:
while
read line
do
hex=${line:0:2}
echo $line >> ntlm/$hex-ntlm.txt
done < pwned-passwords-ntlm-ordered-by-hash-v4.txt
I wrote a Python3 script that solves fast binary-search lookups in the hash file without having to create an index. It doesn't directly address your question (indexing) but probably solves the underlying problem that you wanted to solve with an index - to quickly look up individual hashes. This script checks hundreds of passwords in seconds.
import argparse
import hashlib
parser = argparse.ArgumentParser(description='Searches passwords in https://haveibeenpwned.com/Passwords database.')
parser.add_argument('passwords', metavar='TEST', type=str, help='text file with passwords to test, one per line, utf-8')
parser.add_argument('database', metavar='DATABASE', type=str, help='the downloaded text file with sha-1:count')
args = parser.parse_args()
def search(f: object, pattern: str) -> str:
def search(left, right: int) -> str:
if left >= right:
return None
middle = (left + right) // 2
if middle == 0:
f.seek(0, 0)
test = f.readline()
else:
f.seek(middle - 1, 0)
_ = f.readline()
test = f.readline()
if test.upper().startswith(pattern):
return test
elif left == middle:
return None
elif pattern < test:
return search(left, middle)
else:
return search(middle, right)
f.seek(0, 2)
return search(0, f.tell())
fsource = open(args.passwords)
fdatabase = open(args.database)
source_lines = fsource.readlines()
for l in source_lines:
line = l.strip()
hash_object = hashlib.sha1(line.encode("utf-8"))
pattern = hash_object.hexdigest().upper()
print("%s:%s" % (line, str(search(fdatabase, pattern)).strip()))
fsource.close()
fdatabase.close()

How can I find both identical and similar strings in a particular field in a text file in Linux?

My apologies ahead of time - I'm not sure that there is an answer for this one using only Linux command-line fu. Please note I am not a programmer, but I have been playing around with bash and python a bit over the last few years.
I have a large text file with rows and columns that resemble the following (note - fields are separated with tabs):
1074 Beetle OOB11061MNH 12/22/16 Confirmed
3430 Hightop 0817BESTYET 08/07/17 Queued
3431 Hightop 0817BESTYET 08/07/17 Queued
3078 Copland 2017GENERAL 07/07/17 Confirmed
3890 Bartok FOODS 09/11/17 Confirmed
5440 Alphapha 00B1106IMNH 01/09/18 Queued
What I want to do is find and output only those rows where the third field is either identical OR similar to another in the list. I don't really care whether the other fields are similar or not, but they should all be included in the output. By similar, I mean no more than [n] characters are different in that particular field (for example, no more than 3 characters are different). So the output I would want would be:
1074 Beetle OOB11061MNH 12/22/16 Confirmed
3430 Hightop 0817BESTYET 08/07/17 Queued
3431 Hightop 0817BESTYET 08/07/17 Queued
5440 Alphapha 00B1106IMNH 01/09/18 Queued
The line beginning 1074 has a third field that differs by 3 characters with 5440, so both of them are included. 3430 and 3431 are included because they are exactly identical. 3078 and 3890 are eliminated because they are not similar.
Through googling the forums I've managed to piece together this rather longish pipeline to be able to find all of the instances where field 3 is exactly identical:
cat inputfile.txt | awk 'BEGIN { OFS=FS="\t" } {if (count[$3] > 1) print $0; else if (count[$3] == 1) { print save[$3]; print $0; } else save[$3] = $0; count[$3]++; }' > outputfile.txt
I must confess I don't really understand awk all that well; I'm just copying and adapting from the web. But that seemed to work great at finding exact duplicates (i.e., it would output only 3430 and 3431 above). But I have no idea how to approach trying to find strings that are not identical but that differ in no more than 3 places.
For instance, in my example above, it should match 1074 and 5440 because they would both fit the pattern:
??B1106?MNH
But I would want it to be able to match also any other random pattern of matches, as long as there are no more than three differences, like this:
20?7G?N?RAL
These differences could be arbitrarily in any position.
The reason for needing this is we are trying to find a way to automatically find typographical errors in a serial-number-like field. There might be a mis-key, or perhaps a letter "O" replaced with a number "0", or the like.
So... any ideas? Thanks for the help!
you can use this script
$ more hamming.awk
function hamming(x,y,xs,ys,min,max,h) {
if(x==y) return 0;
else {
nx=split(x,xs,"");
mx=split(y,ys,"");
min=nx<mx?nx:mx;
max=nx<mx?mx:nx;
for(i=1;i<=min;i++) if(xs[i]!=ys[i]) h++;
return h+(max-min);
}
}
BEGIN {FS=OFS="\t"}
NR==FNR {
if($3 in a) nrs[NR];
for(k in a)
if(hamming(k,$3)<4) {
nrs[NR];
nrs[a[k]];
}
a[$3]=NR;
next
}
FNR in nrs
usage
$ awk -f hamming.awk file{,}
it's a double scan algorithm, finds the hamming distance (the one you described) between keys. Notice the it's O(n^2) algorithm, so may not suitable for very large data sets. However, not sure any other algorithm can do better.
NB Additional note based on the comment which I missed from the post. This algorithm compares the keys character by character, so displacements won't be identified. For example 123 and 23 will give a distance of 3.
Levenshtein distance aka "edit distance" suits your task best. Perl script below requires installing a module Text::Levenshtein (for debian/ubuntu do: sudo apt install libtext-levenshtein-perl).
use Text::Levenshtein qw(distance);
$maxdist = shift;
#ll = (<>);
#k = map {
$k = (split /\t/, $_)[2];
# $k =~ s/O/0/g;
} #ll;
for ($i = 0; $i < #ll; ++$i) {
for ($j = 0; $j < #ll; ++$j) {
if ($i != $j and distance($k[$i], $k[$j]) < $maxdist) {
print $ll[$i];
last;
}
}
}
Usage:
perl lev.pl 3 inputfile.txt > outputfile.txt
The algorithm is the same O(n^2) as in #karakfa's post, but matching is more flexible.
Also note the commented line # $k =~ s/O/0/g;. If you uncomment it, then all O's in key will become 0's, which will fix keys damaged by O->0 transformation. When working with damaged data I always use small rules like this to fix data gradually, refining rules from run to run, to the point where data is almost perfect and fuzzy match is no longer needed.

Need help understanding this implementation of tetris

I am trying to understand this implementation of Tetris.
I have a few questions.
In update_score function,
if (( score > LEVEL_UP * level)) ; then # if level should be increased
((level++)) # increment level
pkill -SIGUSR1 -f "/bin/bash $0"
What is the use of having a separate process at all for adjusting the delay? Why use SIGUSR1 and SIGUSR2?
In the draw_piece function, why multiply by 8? I don't understand how the conversion is taking place or how the concept of "rotation" is implemented here.
for ((i = 0; i < 8; i += 2)) {
# relative coordinates are retrieved based on orientation and added to absolute coordinates
((x = $1 + ${piece[$3]:$((i + $4 * 8 + 1)):1} * 2))
((y = $2 + ${piece[$3]:$((i + $4 * 8)):1}))
xyprint $x $y "$5"
...
}
Nor do I understand the syntax involving : here.
In clear_next, why is draw_next ${filled_cell//?/ } necessary instead of just ${filled_cell}? What do the // signify?
I'm a beginner to shell scripting and programming in general and I have been trying to understand this implementation of Tetris [in shell]
Somehow, I suspect you could have found easier programs to start with.
What is the use of having a separate process at all for adjusting the delay? Why use [SIGUSR1] and [SIGUSR2]?
I don't think there's a separate process for adjusting the delay, but for implementing the timer. The timer must run even while the program is waiting for the player to give input, and if the shell functions don't give any way of having a timeout on read, that must be exported to another process. So then you get what there is in the end of script, a divide into the timer, the user input handler, and the actual game logic, with output from the first two going to the last one:
(ticker & reader) | (controller)
Bash's read does have the -t flag for timeout, so if it was implemented in Bash, you might not need the extra timer process. However, putting the timer in an external process also makes it independent of the user input, the read timeout would instead reset every time user hits a button. Working around that would require some way of accurately determining the elapsed time (or using a really short timeout on read and counting the ticks).
SIGUSR1 and SIGUSR2 are just "innocent" signals that don't have a meaning to the system at large, so they can be used here. Of course you could use others, but catching SIGINT or SIGHUP would annoy users if they wanted to stop the game.
In the draw_piece function, why multiply by 8?
((x = $1 + ${piece[$3]:$((i + $4 * 8 + 1)):1} * 2))
The piece array contains the different shapes and orientations of the pieces. A piece is 4 squares large, each square needs two coordinates, so we get 8 numbers per piece/orientation. For, example, the string for the S piece is 0001111201101120, so it has two orientations:
yx yx yx yx yx yx yx yx
00 01 11 12 01 10 11 20
And the piece looks something like this:
012 012
0 xx. 0 .x.
1 .xx 1 xx.
2 ... 2 x..
The ${variable:position:length} notation picks a substring from the given variable, so the program gets the single digits it needs from the bigger string. That's a somewhat weird way of implementing an array.
In clear_next, why is draw_next ${filled_cell//?/ } necessary ...? What do the // signify?
The ${parameter/foo/bar} construct is a pattern replacement (See e.g. Bash's manual on parameter expansion, look for "replace"). Whatever matches foo in the value of parameter, is replaced with bar, and the result is expanded. With a double slash, all matches are replaced, with a single slash, only the first. The question mark matches any character as with filename globs, so that effectively makes a string of spaces as long as the original string.
For example:
$ str="hallo hallo"
$ echo "${str/a/e}"
hello hallo
$ echo "${str//a/e}"
hello hello
$ str="abc"
$ echo "x${str//?/ }x"
x x

bash awk moving average with skipping

I am trying to calculate a moving average with a data set. But in addition, I want it to skip a few number of data each time the average 'window' moves. For example, if my data set is a column from 1 to 20 and my average window is 5, then the current calculation is the average of (1-5), (2-6), (3-7), (4-8).....
But I want to skip a few data each time the window moves, say I want to skip 2. then the new average will be (1-5), (4-8), (6-10), (8-12)......
Here is the current awk file I am using, can anyone help me edit it so that I can skip a few data each time the window moves? I want to change the skip size and window size as well. Thank you very much!
#!/bin/awk
BEGIN {
N=5 # the window size
}
{
n[NR]=$1 # store the value in an array
}
NR>=N { # for records where NR >= N
x=0 # reset the sum variable
delete n[NR-N] # delete the one out the window of N
for(i in n) # all array elements
x+=n[i] # ... must be summed
print x/N # print the row from the beginning of window
}
I think your ranges are not well specified, but you wanted to achieve can be done by parallel windowing as below
awk '{sum[1]+=$1}
!(NR%5){print NR-4"-"NR, sum[1]/5; sum[1]=0}
NR>3{sum[4]+=$1}
NR>3 && !((NR-3)%5){print NR-4"-"NR, sum[4]/5; sum[4]=0}' <(seq 15)
will give, you can remove printing ranges which it there for debugging.
1-5 3
4-8 6
6-10 8
9-13 11
11-15 13
for making window size and skip count variable
awk -v w=5 -v s=3 'function pr(x) {print (NR-s-1)"-"NR, sum[x]/w; sum[x]=0}
{sum[1]+=$1}
NR>s {sum[s+1]+=$1}
!(NR%w) {pr(1)}
NR>s && !((NR-s)%w){pr(s+1)}' file
first window always start at 1, second window starts at s+1. This can be generalize for more than 2 windows as well, perhaps you can find someone to do it...
I see that you want to print MA every K ticks instead of printing for every tick (K=1). So you could add a condition NR%K==0 before printing in your existing code.
But it would be better to keep an array of N elements and overwrite them instead of deleting. Using NR%N as array index. This way, when K is not 1 and want not to calculate the MA, you will avoid checking how many elements to delete etc.
awk -v n=5 -v k=2 '{ a[NR%n]=$0 }
NR>=n && (NR-n)%k==0 { s=0; for (i in a) s+=a[i]; print NR ":\t" s/n }' file
update condition to (NR-n)%k==0 for always starting from first tick where MA is calculated (that is for NR=n).

Reading delimited value from file into a array variable

I want to read data.txt which has a 2x2 matrix number inside delimited by tab like this:
0.5 0.1
0.3 0.2
Is there any way to read this file in bash then store it into an array then process it a little then export it to a file again? Like for example in matlab:
a=dlmread('data.txt') //read file to array variable a
for i=1:2
for j=1:2
b[i][j]=a[i][j]+100
end
end
dlmwrite(b,'data2.txt') //exporting array value b to data2.txt
If the extent of your processing is to something simple like add 100 to every entry, a simple awk command like this might work:
awk '{ for(i = 1; i <= NF - 1; i++) { printf("%.1f%s", $i + 100, OFS); } printf("%.1f%s", $NF+100, ORS); }' < matrix.txt
This just loops through each row and adds 100. It's possible to do more complex operations too, but if you really want toprocess matrices there are better tools (like python+numpy or octave).
It's also possible to use bash arrays, but to do any of the operations you'd have to use an external program anyway, since bash doesn't handle floating point arithmetic.

Resources