4

I have a file and its content looks like this:

a1
b1
c1
aa
bb
cc
aaa
bbb
ccc
d1
e1
f1
dd
ee
ff
ddd
eee
fff
g1
h1
i1
gg
hh
ii
ggg
hhh
iii

What's the best way to merge the rows with fixed interval (3 in this case) and get something like:

a1 aa aaa
b1 bb bbb
c1 cc ccc
d1 dd ddd
e1 ee eee
f1 ff fff
g1 gg ggg
h1 hh hhh
i1 ii iii

The algorithm to get the out put from the input is:

  • First we get row 1 which is a1.
  • We know the interval is 3
  • So row 1, row (1+3), row (1+3+3) should be on the same row
  • Similarly, row 2, 5, 8 should be on the same row etc.

Those a1, aa and aaa etc. are just random dummy text and they could be any random string. The point is that there is a fixed interval between a1, aa and aaa.

Currently I use emacs keyboard macro to do this task. However I want to know if there are any better ways to solve this problem. Thanks in advance.

  • @SatoKatsura OK, I'll update my question. – Just a learner Sep 26 '16 at 18:26
  • @SatoKatsura I added more details. – Just a learner Sep 26 '16 at 18:37
11

If you're on gnu/anything and the number of lines is multiple of 9, you could run

split -l9 --filter='pr -3 -s" " -t' infile

This splits the input into pieces of nine lines and each piece is piped to pr -3 -s" " -t' which columnates it... Depending on the no. of lines and their length you may need to play with pr options -w and -l. See the man page for more details.

6

Here's a simplistic solution in awk, hard-coded to pull in three sets at three-line intervals:

{
  if (NR > 1 && (NR % 9) == 0) {
    print a "\n" b "\n" c " " $0
    a=""
    b=""
    c=""
  } else if (NR % 3 == 1) {
    if (NR % 9 > 1) {
      a=a" "$0
    } else {
      a=$0
    }
  } else if (NR % 3 == 2) {
    if (NR % 9 > 2) {
      b=b" "$0
    } else {
      b=$0
    }
  } else {
    if (NR % 9 > 3) {
      c=c" "$0
    } else {
      c=$0
    }
  }
}

Save that into a file and run awk -f thatfile < input. I'm sure there are smarter ways to do it, but I don't work in awk every day.

4

It's a bit tricky. I don't know of a single utility that can do it:

This pipeline (essentially) reads 9 lines at a time and uses pr to format into 3 columns:

# there are 9 single hyphens below
paste -d: -- - - - - - - - - - < file | while read line; do
    tr : '\n' <<<"$line" | pr -s" " -T -3
done
a1 aa aaa
b1 bb bbb
c1 cc ccc
d1 dd ddd
e1 ee eee
f1 ff fff
g1 gg ggg
h1 hh hhh
i1 ii iii

This assumes you don't have a colon in your actual text.

1

One very simple, clear way in TXR:

@(repeat)
@x0
@x1
@x2
@y0
@y1
@y2
@z0
@z1
@z2
@  (output)
@x0 @y0 @z0
@x1 @y1 @z1
@x2 @y2 @z2
@  (end)
@(end)

Run:

$ txr reshape.txr data
a1 aa aaa
b1 bb bbb
c1 cc ccc
d1 dd ddd
e1 ee eee
f1 ff fff
g1 gg ggg
h1 hh hhh
i1 ii iii

There are ways to condense this, but you have to work a little harder to understand them, such as:

@(repeat)
@  (collect :times 9)
@line
@  (end)
@  (bind (x y z) @(tuples 3 line))
@  (output)
@    (repeat)
@x @y @z
@    (end)
@  (end)
@(end)

Also, here how someone who somewhat knows what they are doing in Awk might implement:

        { a[(NR-1)%9] = $0 }
!(NR%9) { print a[0], a[3], a[6]
          print a[1], a[4], a[7]
          print a[2], a[5], a[8] }

Output:

$ awk -f reshape.awk data
a1 aa aaa
[ ... ]
i1 ii iii

And if that coder finds repeated print patterns repugnant:

        { a[(NR-1)%9] = $0 }
!(NR%9) { for (i = 0; i < 3; i++)
            print a[i], a[i+3], a[i+6] }

TXR Lisp solution:

[(opip (tuples 3) (tuples 3) (mappend transpose)
       (mapcar (aret `@1 @2 @3`)) tprint)
 (get-lines)]

Run:

$ txr reshape.tl < data

On command line: use -t, drop the tprint:

$ txr -t '[(opip (tuples 3) (tuples 3) (mappend transpose)
                 (mapcar (aret `@1 @2 @3`)))
           (get-lines)]' < data

This works by putting the input through a pipeline which shores it up into triplets, then triplets of those triplets (basically 3x3 matrices made of nested lists). These matrices are individually transposed and their rows are then apppended together to make one giant list of triplets. These triplets are turned into strings with the aret partial application operator an string interpolation, and output with tprint which treats lists of strings as lines to output. The syntax

(aret `@1 @2 @3`)

expands into something resembling

(lambda (. args)
  (apply (lambda (arg1 arg2 arg3)
           `@arg1 @arg2 @arg3`)
         args))

Basically it implicitly creates a one-argument anonymous function which treats its argument as a list of arguments to apply to a 3-argument anonymous function where the @1, @2 and @3 denote the arguments. The body of the function is derived from the original quasi-string expression by substituting these special numeric parameters with machine-generated argument names.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.