awk diamond code golf (just for fun!)

Hey guys,

This is purely just a little bit of fun with awk. I realize this this isn't that constructive so please remove if need be.

Your goal:
Create a one line awk script that generates a diamond shape of any
size. Both the size of the diamond (measured by its middle line) and
the character used ("*" in the examples) should be stored in variables
that can be changed easily. Have fun!

Example diamond with the size of 5:

  *
 ***
*****
 ***
  *

Example diamond with the size of 10:

    **
   ****
  ******
 ********
**********
 ********
  ******
   ****
    **

My solution is below - I didn't spend too long on it so I imagine it can be improved a lot more.
Obviously DON'T LOOK HERE if you want to try this yourself from scratch without any clue!:

awk 'function g(n,v){V="";for(i=1;i<=n;i++)V=V?V sprintf("%s",v):sprintf("%s",v)}BEGIN{L=15;v="*";B=L%2?1:2;s=(L-B)/2;while(B>0){g(B,v);D=V;g(s," ");S=V;printf"%s%s%s\n", S,D,S;if(B==L){f=1};B=f?B-=2:B+=2;s=f?s+=1:s-=1}}'
4 Likes

That is fun. First attempt:

awk -v l=5 -v c='*' 'BEGIN{for(i=k=1; i>=1; i+=k) {s=s c; if(k==1) A=sprintf("% " l "s%." i-1 "s", s ,s); print A; if(i==l) k=-1}}'

Note: l is not the diamond size but the number width increases -1

--

Second attempt :

awk -v l=5 -v c='*' 'BEGIN{q=(l+1)%2; n=(l+1-q)/2; for(i=k=1; i>=1; i+=k) {s=s c; if(k==1) A=sprintf("% *s%.*s", n, s, i+q-1, s); print A; if(i==n) k=-1}}'

Now l is the length of the diamond...

Condensed:

awk -v l=10 -v c=\* 'BEGIN{q=(l+1)%2;n=(l+1-q)/2;for(i=k=1;i>=1;i+=k) {s=s c;if(k==1)A=sprintf("% *s%.*s",n,s,i+q-1,s);print A;if(i==n)k=-1}}' 
1 Like

Wow Scrutinizer, very nice! Now I have to try and get my head round your code :slight_smile:

Thanks pilnet101 :slight_smile: . Basically it increases the line length by 2 every line and memorizes the line. Then at maximum length, the loop starts to decrement, rather than increment and only prints the lines memorized in the increment phase.

With regards to the setting of the quotient q and the line n where the maximum line length occurs, and using the name m for maximum line length rather than l which is easily confused with the number 1 ), perhaps this is a bit clearer:

awk -v m=15 -v c=\* 'BEGIN{q=!(m%2);n=int(m/2);for(i=k=1;i>=1;i+=k){s=s c;if(k==1)A=sprintf("% *s%.*s",n,s,i+q-1,s);print A;if(i==n)k=-1}}'
1 Like

Yes, this is fun. :wink:

This isn't quite as compact as Scrutinizer's approach (and calculates each line as it goes instead of memorizing lines), but there are some fun games you can play with this one using multi-character strings that produce different artifacts with Scrutinizer's code and with pilnet101's code:

#!/bin/ksh
IAm=${0##*/}
if [ $# -gt 2 ]
then	printf 'USAGE:		%s [size [character]]

DESCRIPTION:	Print a diamond  of "character" characters that is "size"
		characters wide at the widest point.

OPERANDS:	size		The size of the diamond.  (Default 15).
		character	The character to print. (Default "*".)

EXAMPLES:	In addition to the obvious single character "character"
		operands, try commands like:

		    diamond 20 "<>"

		and:

		    diamond 29 "Happy New Year "\n' "$IAm" >&2
	exit 1
fi
awk -v s="${1:-15}" -v c="${2:-*}" '
BEGIN {	n = s - ((s + 1) % 2)	# # of lines to print.
	m = int((s + 1) / 2)	# middle line #.
	for(i = 1; i <= s; i++)	# S is a string of s c characters (the middle
		S = S c		# and longest line).
	w = 2 - s % 2		# The width (i.e. # of c characters to print on
				# the current line) which will be 1 or 2 on the
				# first line.
	for(i = 1; i <= n; i++){# Print each of the n lines...
		printf("%*s%.*s\n",(s - w) / 2,N,w,S)
		w += i < m ? 2 : -2	# ... and update w for the next line to
					# be printed (i.e. 2 more if we have not
					# reached the middle line yet; otherwise
					# 2 less).
	}
}'

and for the obligatory 1-liner:

awk -v s="${1:-15}" -v c="${2:-*}" 'BEGIN{n=s-((s+1)%2);m=int((s+1)/2);for(i=1;i<=s;i++)S=S c;w=2-s%2;for(i=1;i<=n;i++){printf("%*s%.*s\n",(s-w)/2,N,w,S);w+=i<m?2:-2}}'

If you store either of these in a file named diamond , make it executable, and invoke it with:

./diamond 20 '<>'

you get:

         <>
        <><>
       <><><>
      <><><><>
     <><><><><>
    <><><><><><>
   <><><><><><><>
  <><><><><><><><>
 <><><><><><><><><>
<><><><><><><><><><>
 <><><><><><><><><>
  <><><><><><><><>
   <><><><><><><>
    <><><><><><>
     <><><><><>
      <><><><>
       <><><>
        <><>
         <>

and with:

./diamond 29 'Happy New Year '

you get:

              H
             Hap
            Happy
           Happy N
          Happy New
         Happy New Y
        Happy New Yea
       Happy New Year 
      Happy New Year Ha
     Happy New Year Happ
    Happy New Year Happy 
   Happy New Year Happy Ne
  Happy New Year Happy New 
 Happy New Year Happy New Ye
Happy New Year Happy New Year
 Happy New Year Happy New Ye
  Happy New Year Happy New 
   Happy New Year Happy Ne
    Happy New Year Happy 
     Happy New Year Happ
      Happy New Year Ha
       Happy New Year 
        Happy New Yea
         Happy New Y
          Happy New
           Happy N
            Happy
             Hap
              H

Compare these outputs to the results you get with the other proposals using the same counts and strings. :smiley: With single character strings we all produce the same output.

2 Likes

Not necessarily too new nor fancy nor inventive...

awk -vW=10 'function PR(){T=sprintf("%*.*d",W+i-1,i*2-1,0);gsub("0","*",T);print T};BEGIN{for(i=1;i<=W;i++)PR();for (i=W-1;i>0;i--)PR()}'

I really like Scrutinizer's trick with the iterator reversal if i==n !

EDIT: With it, my approach becomes

awk -vW=5 'BEGIN{for(i=k=1;i>=1;i+=k) {T=sprintf("%*.*d",W+i-1,i*2-1,0);gsub("0","*",T);print T;if(i==W)k=-1}}'
1 Like

Interesting effect when applied to Don Cragun's text example:

awk -vW=30 -vL="Happy New Year" 'BEGIN{for(i=k=1;i>=1;i+=k) {printf("%*.*s\n",(W/2)+i,i*2-1,L);if(i>=W/2)k=-1}}'
               H
              Hap
             Happy
            Happy N
           Happy New
          Happy New Y
         Happy New Yea
         Happy New Year
          Happy New Year
           Happy New Year
            Happy New Year
             Happy New Year
              Happy New Year
               Happy New Year
                Happy New Year
               Happy New Year
              Happy New Year
             Happy New Year
            Happy New Year
           Happy New Year
          Happy New Year
         Happy New Year
         Happy New Yea
          Happy New Y
           Happy New
            Happy N
             Happy
              Hap
               H

EDIT: Try this:

awk -vW=30 '-vL=Happy New Year' 'BEGIN{for(i=k=1;i>=1;i+=k) {while(length(L)<W)L=L" "L;printf("%*.*s\n",(W/2)+i,i*2-1,L);if(i>=W/2)k=-1}}'
2 Likes

Thanks for all the great replies guys. I enjoyed playing about with your script Don!

You can turn this into a little 'find the hidden character' type game to. For example, find the hidden $ in a diamond full of S's. Or one of my favorites, find the hidden ";" amongst the ":"'s! (this really gets tricky at larger sizes!) Expanding on my initial code (again a much more elegant solution exists!):

awk '
function _genLine(n,v,r)
{
 ch=v
 if (r==1) {srand(); RAND=int(1+rand()*n)}
 VAR=""
 for (i=1;i<=n;i++) {
   ch=v
   if (i==RAND && r==1) {ch=uniq}
   VAR=VAR?VAR sprintf("%s", ch):sprintf("%s", ch)
 }
}

BEGIN{
M=":" ; uniq=";" ; L="10"
C=(L%2)?L:L-1
BASE=(L%2)?1:2
SVAR=(L-BASE)/2
srand()
j=1
R=int(1+rand()*C)
while (BASE>0) {
 DLINE=VAR
 _genLine(BASE,M) ; DLINE=VAR
 if (j==R) {_genLine(BASE,M,1) ; DLINE=VAR}
 _genLine(SVAR," ") ; SLINE=VAR
 printf "%s%s%s\n", SLINE,DLINE,SLINE
 if (BASE==L) {f=1}
 BASE=(f==1)?BASE-=2:BASE+=2
 SVAR=(f==1)?SVAR+=1:SVAR-=1
 j++
 }
}'