-3

Input file consists of multiple lines like

   0     1     0     0     0     1     1     0     0    0 / 1    0 / 1    0 / 1
   0     1     0     1     0     0     0     0    -1    3 / 4    1 / 4    1 / 2

I would like to copy each line in the input, insert 3 copies below the original line, and modify the fractions at the end. I would expect the output to be

   0     1     0     0     0     1     1     0     0    0 / 1    0 / 1    0 / 1
   0     1     0     0     0     1     1     0     0    0 / 1    1 / 2    1 / 2
   0     1     0     0     0     1     1     0     0    1 / 2    0 / 1    1 / 2
   0     1     0     0     0     1     1     0     0    1 / 2    1 / 2    0 / 1
   0     1     0     1     0     0     0     0    -1    3 / 4    1 / 4    1 / 2
   0     1     0     1     0     0     0     0    -1    3 / 4    3 / 4    0 / 1
   0     1     0     1     0     0     0     0    -1    1 / 4    1 / 4    0 / 1
   0     1     0     1     0     0     0     0    -1    1 / 4    3 / 4    1 / 2

The modification to the fractions follows the pattern

(0,0,0)  <- original fractions
(0,+1/2,+1/2)
(+1/2,0,+1/2)
(+1/2,+1/2,0)

However, if the fraction is greater than 1

i.e. 3/4 + 1/2 = 5/4

it must have 1 subtracted from it

so 5/4 -> 1/4

Would like to add this solution to a current bash script I have. What I am showing as my "input" is the result thus far of my script. Perhaps an awk or sed command to achieve desired results?

Rob S.
  • 35
  • 7
  • As you are doing moderately sophisticated arithmetic (adding fractions and modulo 1), I would consider writing small perl or python script for this. Although it's doable in bash and awk by their own (sed I'm not sure -- probably?), readability is going to suffer greatly. – sapht Nov 10 '17 at 17:48
  • Should have mentioned I am new to scripting, consequently I am unfamiliar with perl and python – Rob S. Nov 10 '17 at 17:56
  • 1
    So are you going to ask us to do your work (or homework) every step of the way? This follows the previous questions where you asked to produce the input to this one. Work on it, we will help if you are stuck on something, but not write the whole thing! Past posts: https://stackoverflow.com/questions/47192415/bash-script-reading-line-for-if-not-present-change-line then https://stackoverflow.com/questions/47215364/reading-text-file-change-order-of-columns-of-some-lines then https://stackoverflow.com/questions/47214248/bash-text-file-editing-modifying... – Nic3500 Nov 10 '17 at 18:53
  • FYI, since you say " I am looking to join one of the computational research groups" in your profile, you might want to learn python. Python has a LOT of mathematical, statistical, graphing tools, ... modules. Bash is not great for maths, by far! Bash is more for sysadmin, not complicated logic. – Nic3500 Nov 10 '17 at 19:02
  • 2
    I posted a solution but should say this is horrible format to work with, it's neither machine nor human friendly! – karakfa Nov 10 '17 at 19:43
  • @Nic3500 Have I asked previous questions regarding this? Yes, but that is because I cannot figure out what to do and as I stated, I'm brand new to this and trying to learn from others as books I have aren't helping as expected. This isn't any specific work or assignment, I was just asked to see if it is at all possible. Also, why should I learn Python if the group doesn't use it? And karakfa thanks for your time – Rob S. Nov 10 '17 at 19:59
  • Sure, if the group does math in bash (!), they do that to. It's just that Python has a huge community of scientific developpers that created all sorts of math modules. – Nic3500 Nov 10 '17 at 20:00

3 Answers3

1

awk to the rescue!

$ awk 'BEGIN{FS=OFS="\t"} 
       function addHalf(v) {split(v,a," / ");                 # split num/denom
                            n=2*a[1]+a[2]; d=2*a[2];          # add 1/2
                            if(n>=d) n-=d;                    # modulus 1
                            while(!(n%2 || d%2)) {n/=2;d/=2}  # normalize if both even
                            return n " / " d}

     {print;
      for(i=2;i>=0;i--)                   # iterate over last three fields
        {j=NF-(i+1)%3;   k=NF-(i+2)%3;    # compute indices
         tj=$j;          tk=$k;           # save values
         $j=addHalf(tj); $k=addHalf(tk);  # modify selected indices
         print;                           # print modified line
         $j=tj;          $k=tk}}' file    # revert to saved values
karakfa
  • 66,216
  • 7
  • 41
  • 56
  • Oh my goodness you weren't kidding when you said that it was neither machine nor human friendly. But considering what the OP said about his programming experience, keeping it in AWK was probably a good choice so that the OP can understand it best. – Davy M Nov 10 '17 at 22:01
  • Upon attempting this I receive `awk: cmd. line:10: (FILENAME=input.txt FNR=1) fatal: attempt to access field -1` Should we iterate over the last 9 fields and somehow skip over the / symbol? – Rob S. Nov 13 '17 at 14:16
  • Are the fields tab separated? If not, you wouldn't have the last three fields but only one. – karakfa Nov 13 '17 at 14:36
  • to separate each "column" I just hit space the appropriate number of times – Rob S. Nov 13 '17 at 14:51
  • that's not right. How do you define your last three fields atomically then? They are also space separated. You can change the format with `unexpand -t5 file > file.tsv` change 5 to whatever number of spaces you have. Or, similarly with `sed`. The main issue is you shouldn't use a field separator that is vaild character within your fields. – karakfa Nov 13 '17 at 14:55
  • There's more possibilities than just tab-separated (which the posted example clearly isn't or the alignment of `0` above `-1` would be different). Maybe the field separator is simply 2-or-more blanks? Or maybe they're fixed-width fields? Or maybe the last 9 fields on the line are just 3 clusters of 3 data items (which is what I assumed as it's the least dependent on any specific non-default FS)? Or something else... – Ed Morton Nov 13 '17 at 17:55
  • I agree that there are many more formats but I think OP invents them on the go and these are intermediate formats for his yet unannounced next processing step. What I think make it easier is to pick a simple delimiter (highly unlikely that tab will exist within fields) and indirectly a more general advice for future. You can always pretty-print for human consumption. It also makes sense to remove spaces from the rational numbers in the last three fields. *You can lead a horse to water, but you can't make it drink* – karakfa Nov 13 '17 at 17:59
1

This will get you as far as identifying the original fractions and the deltas to be applied to each using GNU awk for true multi-dimensional arrays, gensub() and \s/\S shorthand:

$ cat tst.awk
BEGIN {
    split("\
            (0,0,0)             \
            (0,+1/2,+1/2)       \
            (+1/2,0,+1/2)       \
            (+1/2,+1/2,0)       \
        ",modRows,/[[:space:])(]+/)

    for (i=1; i in modRows; i++) {
        row = modRows[i]
        if ( row ~ /\S/ ) {
            deltas[++numRows][1]
            numCols = split(row,deltas[numRows],/,/)
        }
    }
}

{
    head = gensub(/^((\s*\S+){9})(.*)/,"\\1",1)
    tail = gensub(/^(\s*(\S+\s+){9})(.*)/,"\\3",1)
    tail = gensub(/ ([^0-9]) /,"\\1","g",tail)

    split(tail,fracts)

    for (rowNr=1; rowNr <= numRows; rowNr++) {
        printf "%s", head
        for (colNr=1; colNr <= numCols; colNr++) {
            fract = fracts[colNr]
            delta = deltas[rowNr][colNr]
            printf "%s%s", OFS, addDelta(fract,delta)
        }
        print ""
    }
}

function addDelta(oldFract,delta,       newFract) {
    newFract = "(" oldFract " + " delta ")"    # <-- do the math here!
    return newFract
}

.

$ gawk -f tst.awk file
   0     1     0     0     0     1     1     0     0    (0/1 + 0)       (0/1 + 0)       (0/1 + 0)
   0     1     0     0     0     1     1     0     0    (0/1 + 0)       (0/1 + +1/2)    (0/1 + +1/2)
   0     1     0     0     0     1     1     0     0    (0/1 + +1/2)    (0/1 + 0)       (0/1 + +1/2)
   0     1     0     0     0     1     1     0     0    (0/1 + +1/2)    (0/1 + +1/2)    (0/1 + 0)
   0     1     0     1     0     0     0     0    -1    (3/4 + 0)       (1/4 + 0)       (1/2 + 0)
   0     1     0     1     0     0     0     0    -1    (3/4 + 0)       (1/4 + +1/2)    (1/2 + +1/2)
   0     1     0     1     0     0     0     0    -1    (3/4 + +1/2)    (1/4 + 0)       (1/2 + +1/2)
   0     1     0     1     0     0     0     0    -1    (3/4 + +1/2)    (1/4 + +1/2)    (1/2 + 0)

so all you need to do is add whatever math you've figured out to do your calculations on each fraction in the indicated spot in the addDeltas() function at the bottom of the script.

Ed Morton
  • 188,023
  • 17
  • 78
  • 185
  • I receive the errors BEGIN: command not found syntax error near unexpected token `" (0,0,0) (0,+1/2,+1/2) (+1/2,0,+1/2) (+1/2,+1/2,0) ",modRows,/[[:space:]' try03.bash: line 47: ` ",modRows,/[[:space:])(]+/)' – Rob S. Nov 13 '17 at 14:33
  • Then you copy/pasted my script wrong or made some other mistake. I can't help you figure out what that mistake was since I cant see your code. – Ed Morton Nov 13 '17 at 17:40
0

The following Perl script processes the fractions in the way you asked for. It uses Number::Fraction to do the math.

#!/usr/bin/perl
use strict;
use warnings;

use Number::Fraction;

while (<>) {
    print;
    my @cols = split /(\s+)/;
    for my $modify ([0, 1, 1], [1, 0, 1], [1, 1, 0]) {
        my @fractions = map 'Number::Fraction'->new(@cols[@$_]),
            [-18, -14],[-12, -8], [-6, -2];
        my @newcols;
        for my $i (0 .. 2) {
            if ($modify->[$i]) {
                $fractions[$i] += 'Number::Fraction'->new(1, 2);
                $fractions[$i] -= 1 if $fractions[$i] >= 1;
            }
            push @newcols, $fractions[$i];
        }
        s{/}{ / }, s{^0$}{0 / 1} for @newcols;  # Format the fractions.
        print @cols[0..19], $newcols[0],
              $cols[25],    $newcols[1],
              $cols[31],    $newcols[2],
              "\n";
    }

}
choroba
  • 231,213
  • 25
  • 204
  • 289
  • an attempt will your code yields the following: `line 41: use: command not found line 42: use: command not found line 44: use: command not found line 46: syntax error near unexpected token `)' line 46: `while (<>) {'` I have checked and I have perl v5.10.1 – Rob S. Nov 13 '17 at 14:24
  • @RobS. How did you run the Perl script? Either `chmod u+x` it and run `/path/to/script input/file`, or run it with `perl path/to/script input/file`. The errors you showed came from a shell, not Perl. – choroba Nov 13 '17 at 14:29
  • now I receive `-bash: ./perl_output: bin/perl: bad interpreter: No such file or directory` and `Can't locate Number/Fraction.pm in @INC (@INC contains: /usr/local/lib64/perl5 /usr/local/share/perl5 /usr/lib64/perl5/vendor_perl /usr/share/perl5/vendor_perl /usr/lib64/perl5 /usr/share/perl5 .) at perl_output line 5. BEGIN failed--compilation aborted at perl_output line 5.` with the two methods you listed – Rob S. Nov 13 '17 at 14:47
  • You need to install Number::Fraction by running `cpan Number::Fraction`. But first, you need to configure `cpan` to install libraries properly, probably by [local::lib](http://p3rl.org/local::lib). – choroba Nov 13 '17 at 15:09