Convert each integer to a simple ASCII graph

Viewed 308

I have a file with a bunch of integers like this:

6
2
3
4
3

The goal is to convert those integers in stats like in a videogame, for example, if the number is 6, the stats must be ******----, if the number is 4 the result must be ****------.

I tried the following piece of code but it doesn't work:

# Here I put all the int in a variable.
intNumber=`cat /home/intNumbers.txt`

# This for is a loop to print as much * as the number says.
for i in `seq 1 $intNumber`
do
  echo -n "*"
  # This for loop is for printing - until reacing 10.
  for j in `seq $intNumber 10`
  do
    echo -n "-"
  done
done
8 Answers

With Perl:

perl -ne 'print("*" x $_, "-" x (10-$_), "\n")' file

$_ contains current row

Output:

******----
**--------
***-------
****------
***-------

You may use this awk:

awk '{s = sprintf("%*s", $1, ""); gsub(/ /, "*", s); p = sprintf("%*s", 10-$1, ""); gsub(/ /, "-", p); print s p}' file

******----
**--------
***-------
****------
***-------

A more readable version:

awk '{
   s = sprintf("%*s", $1, "")
   gsub(/ /, "*", s)
   p = sprintf("%*s", 10-$1, "")
   gsub(/ /, "-", p)
   print s p
}' file

Another awk, keepin' it simple, sir:

$ awk '
BEGIN {
    s="**********----------"
}
{
    print substr(s,11-$1,10)
}' file

Output:

******----
**--------
***-------
****------
***-------

Similar for bash:

#!/bin/bash
s="**********----------"
while IFS= read -r line
do
    echo "${s:((10-$line)):10}"
done < file

A more generic approach for awk could be, for example:

$ awk -v m=10 '{            # desired maximum number of chars
    t=""                    # temp var
    for(i=1;i<=m;i++)       # loop to max
        if(i<=$1)           # up to threshold value from file
            sub(/^/,"*",t)  # prepend a *
        else                # after threshold
            sub(/$/,"-",t)  # append a -
     print t
}' file

Some input checking could be in order.

Minimizing the work you have to do per input line for efficiency:

$ cat tst.awk
BEGIN {
    lgth = 10
    curr = base = sprintf("%*s",lgth,"")
    gsub(/ /,"*",curr)
    gsub(/ /,"-",base)
}
{ print substr(curr,1,$1) substr(base,$1+1) }

$ awk -f tst.awk file
******----
**--------
***-------
****------
***-------

or borrowing @JamesBrowns idea of indexing into a single string:

$ cat tst.awk
BEGIN {
    lgth = 10
    curr = base = sprintf("%*s",lgth,"")
    gsub(/ /,"*",curr)
    gsub(/ /,"-",base)
    line = curr base
}
{ print substr(line,(lgth-$1)+1,lgth) }

$ awk -f tst.awk file
******----
**--------
***-------
****------
***-------

Don't read the entire input file into memory. Instead, process one line at a time.

The following also demonstrates how to do this more succinctly in Bash.

#!/bin/bash
ten='----------'
while IFS='' read -r num; do
    printf -v graph '%10.10s' "${ten:$num}"
    echo "${graph// /\*}"
done < intNumbers.txt

printf -v graph places the output in the variable graph, and we then use a Bash parameter substitution to replace the space padding from printf with asterisks.

Demo: https://ideone.com/wE6fpm

Doing this entirely in Bash is attractive if you end up doing this a lot; you generally want to avoid external processes especially in repeated code (though of course don't repeat yourself; put this in a function then.)

If you genuinely want to convert a file of numbers into a bunch of graphs, a single Awk process is still much better; the shell isn't particularly good at that. I'm imagining you have a different application where you occasionally need to format a number as a graph in various places in a Bash script.

A riff on tripleee's answer:

# Repeat a character a specified number of times
#
# parameters
# - character
# - count
#
# usage: strRepeat "*" 60
#
str_repeat() {
    local char=$1 count=$2
    local result
    # string of count spaces
    printf -v result "%*s" "$count" ""
    # replace spaces with the char
    echo "${result// /$char}"
}

while read -r num; do
    printf '%s%s\n' "$(str_repeat '*' "$num")" "$(str_repeat '-' $((10-num)))"
done < intNumbers.txt

Another GAWK solution:

awk -v OFS="*" '{NF=($1+1); $1=""; print gensub(/ /,"-","g",sprintf("%-10s",$0))}' file

# -v OFS="*"
# [Sets the output field separator to an asterisk]

# NF=($1+1)
# [Defines the number of fields in the line as 1 more
# than the value in the first (and only) input field.
# If you printed the first line, it would now be "6******"]

# $1=""
# [Deletes the first field in the line, leaving only asterisks]

# sprintf("%-10s",$0)
# [Formats the line as 10 characters, left-justified.
# The missing characters on the right are blank spaces]

# gensub(/ /,"-","g",sprintf("%-10s",$0))
# [Replaces all blank spaces in the sprintf-formatted
# line with hyphens]

# print
# [Prints the line as modified by gensub]

With Python:

python -c "import sys
> for s in sys.stdin: n=int(s); print('*'*n + '-'*(10-n))" < file
Related