3

How do you convert a string to lowercase in gnuplot?
This is a gnuplot string handling question.

Example:- I wish to check a user typed parameter in a gnuplot script....

if (tolower(ARG2) == "ohms") {.....

so by accepting "ohms", "Ohms", or "OHMS".

The preference is to not need to use an external "system" command so that the script is more portable. My current best solution is

  arg2 = system("awk 'BEGIN { print toupper(\"".ARG2."\") }'")

and then test the new string variable "arg2", but awk (or other program) may not be generally available on a non unix system, making the gnuplot script less portable.

I cannot see any enhanced gprintf % format specifiers that modifies string presentation - it seems gprintf is only for converting values.

mjp
  • 93
  • 10
  • On my Ubuntu, `awk` is not capable to upper/lower non-ASCII letters. Are you trying to check if a string contains a substring in Bash? – Wiktor Stribiżew Aug 22 '17 at 06:12
  • Try [`if [[ ${ARG2,,} == "ohms" ]]; then echo YES; fi`](https://ideone.com/9FsuTg) – Wiktor Stribiżew Aug 22 '17 at 06:17
  • Or `arg2=system("${ARG2,,}")` – Wiktor Stribiżew Aug 22 '17 at 06:39
  • I am trying to avoid a system call so the gnuplot script might be more portable to non unix systems. In this case ARG1 is a number and ARG2 is the units which might be dB or Ohms, so I need to do different plotting math. Non-ASCII would fail the match regardless of case, so I guess no problem as the script should default to the no units case - in this case. – mjp Aug 23 '17 at 10:51
  • `if [[ ${ARG2,,} == "ohms" ]]; then echo YES; fi` seems to not be gnuplot syntax - I get an error message - expecting expression. – mjp Aug 23 '17 at 10:53
  • Gnuplot has no string functions for lower/uppercase conversion. You cannot even get the ASCII code for a character... – Christoph Sep 06 '17 at 20:51
  • Many thanks for the authoritative answer. Searching was unhelpful for me, so having this up might now help others more quickly, at least as of Gnuplot 5.0. – mjp Sep 08 '17 at 07:20

3 Answers3

2

I am not aware that gnuplot offers uppercase or lowercase functions.

Edit: to my opinion, my original macro solution and the second approach don't have any advantage compared to the latest version. In order to keep it short and not confusing people, I removed the old solutions.

The attempt below is a function which converts the argument into uppercase or lowercase without external tools as desired from the OP. Characters which are not found in the list are kept unchanged.

Script:

### uppercase/lowercase function for gnuplot
reset session

UpperCases= "ABCDEFGHIJKLMNOPQRSTUVWXYZÄÁÂÀËÉÊÈÏÍÎÌÖÓÔÒÜÚÛÙŸÝ"
LowerCases= "abcdefghijklmnopqrstuvwxyzäáâàëéêèïíîìöóôòüúûùÿý"

# upper/lowercase for characters
ucc(c) = ((ucc_i=strstrt(LowerCases, c)) ? UpperCases[ucc_i:ucc_i] : c)
lcc(c) = ((lcc_i=strstrt(UpperCases, c)) ? LowerCases[lcc_i:lcc_i] : c)

# upper/lowercase for strings
uc(s) = (uc_s='', sum[uc_i=1:strlen(s)] (uc_s=uc_s.ucc(s[uc_i:uc_i]),1),uc_s)
lc(s) = (lc_s='', sum[lc_i=1:strlen(s)] (lc_s=lc_s.lcc(s[lc_i:lc_i]),1),lc_s)

s = "...thE qUick brOWn foX jUmPs oVeR The LazY Dog!"
print "Input:     ", s
print "Uppercase: ", uc(s)
print "Lowercase: ", lc(s)
### end of script

Result:

Input:     ...thE qUick brOWn foX jUmPs oVeR The LazY Dog!
Uppercase: ...THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG!
Lowercase: ...the quick brown fox jumps over the lazy dog!
theozh
  • 22,244
  • 5
  • 28
  • 72
  • Well, done. I was sure there would be a non-recursive solution, but could not think of a way. I never considered long strings (or efficiency), but 247 is a bit limiting - so I think using this sum[] way is a much better general solution. – mjp Jan 14 '19 at 19:19
  • hmm, in the meantime I found one possible (minor) limitation of this solution which is the use of variables. Here `tmp`, `i` and `w`. Although `tmp` in `ucchar(c)` and `lcchar(c)` could be avoided as your solution shows. In the very unfortunate case if the code around this function uses one of these variables, the variables would be be modified. I am not sure how `i` and `w` could be avoided in the definition of the function. Alternatively, you would have to find variable names which are for sure _not_ used in the sorrounding code. Or maybe you know another way around? – theozh Jan 14 '19 at 19:53
  • I was trying to avoid variables - recalculating the position parameters and the beauty of recursion..... In this situation I tend to hope for safety and use a name prefixed by the function name, e.g. "toupper_w". If there was a way of having an optional function parameter..... – mjp Jan 14 '19 at 20:05
  • good idea to prefix "internal" or "local" variables with the function name. With this, the code get's a bit longer but variable name conflicts are highly unlikely to occur. I changed the code. – theozh Jan 14 '19 at 21:13
2

The full function Macro solution (Thanks theozh) has me thinking again of how to implement this as a function. The idea of using a lookup table to convert characters by equating an ordinal number was a great idea. Encapsulating single character case conversion into a function was the start, and then along with recursion, it has made it possible to handle full strings as I had first looked for. I hope this is now a tidy solution for all. Share and enjoy.

# GNUPLOT string case conversion
# string_case.gnu   M J Pot, 14/1/2019

# Index lookup table strings
UCases="ABCDEFGHIJKLMNOPQRSTUVWXYZ"
LCases="abcdefghijklmnopqrstuvwxyz"

# Convert a single character
# Char to convert is added to string so it is always found to default other chars
toupperchr(c)=substr( UCases.c, strstrt(LCases.c, c), strstrt(LCases.c, c) )
tolowerchr(c)=substr( LCases.c, strstrt(UCases.c, c), strstrt(UCases.c, c) )

# Convert whole strings
# Conversion first char (or return null), and recurse for the remaining
toupper(s) = s eq ""  ?  ""  :  toupperchr(s[1:1]).toupper(s[2:*])
tolower(s) = s eq ""  ?  ""  :  tolowerchr(s[1:1]).tolower(s[2:*])

Addition: Improved solution

This is a re-work of the recursive case conversion as self contained functions. A little more effort has resolved the excessive stack usage of the first solution. I had only been considering strings of single words when I had the problem. Note:- the single character conversion has been made more robust.

# GNUPLOT string case conversion
# string_case.gnu   M J Pot, 29/1/2019
# toupper(), tolower() functions

# Index lookup table strings
UCases="ABCDEFGHIJKLMNOPQRSTUVWXYZ"
LCases="abcdefghijklmnopqrstuvwxyz"

# Convert a single character
# Char to convert is added to string so it is always found to default other chars
# Null strings are returned null
toupperchr(c)= c eq ""  ?  ""  :  substr( UCases.c, strstrt(LCases.c, c), strstrt(LCases.c, c) )
tolowerchr(c)= c eq ""  ?  ""  :  substr( LCases.c, strstrt(UCases.c, c), strstrt(UCases.c, c) )

# Divide & conquer
# A simple linear recursive call uses too much stack for longer strings.
# This undertakes a binary tree division to make the stack growth order log_2(length)
toupper(s) = strlen(s) <= 1 ? toupperchr(s) : toupper( substr(s,1,strlen(s)/2) ) . toupper( substr(s,(strlen(s)/2)+1,strlen(s)) ) 
tolower(s) = strlen(s) <= 1 ? tolowerchr(s) : tolower( substr(s,1,strlen(s)/2) ) . tolower( substr(s,(strlen(s)/2)+1,strlen(s)) ) 
mjp
  • 93
  • 10
  • Great! Glad, that I could trigger the accepted solution. I forgot that gnuplot can handle recursion. However, the nice recursion function limits the maximum length of the string to 247 characters, with longer strings you will get an Error: `stack overflow` ;-). Well, 247 characters should be enough for most cases. For the (not so convenient) macro solution I haven't seen a limit, but tested only up to 10'000 characters. – theozh Jan 14 '19 at 07:57
  • based on your solution, I added a cleaner non-recursive solution to my answer. – theozh Jan 14 '19 at 14:55
  • Have sorted the long string problem with the recursive solution, thanks for making me think about this more @theozh. I am thinking this is now the best solution. – mjp Jan 28 '19 at 20:25
  • Very good! There seems to be "no" string length limitation anymore (tested only up to 100'000 characters). For short string lengths (<500) there is no big time difference between our versions, but for larger there is a clear speed advantage for your recursive solution. I don't know about the memory usage. So, take back the accepted answer ;-) – theozh Jan 29 '19 at 19:14
  • OK, thanks @theozh for the extra testing - I only tried a few thousand :-) I'll guess this is now the best answer. – mjp Jan 30 '19 at 04:08
0

When wishing to compare a string variable independent of case to a known string constant, only a subset of characters need to be converted. This is not an answer to the general situation of case conversion in gnuplot, but may work for many situations.

arg2 = ARG2
# As we want limited comparisons strstrt(”string”, ”key”) can be used....
# Substitute the first occurrence of a character [pat], with another character [repl]
# Can be used to convert an expected word's case, one character at a time
subchr(src, pat, repl) = strstrt(src, pat)?src[*:strstrt(src, pat)-1].repl.src[strstrt(src, pat)+1:*]:src
arg2 = subchr(arg2, "o", "O")
arg2 = subchr(arg2, "h", "H")
arg2 = subchr(arg2, "m", "M")
arg2 = subchr(arg2, "s", "S")
arg2 = subchr(arg2, "d", "D")
arg2 = subchr(arg2, "b", "B")
if ( arg2[1:2] eq "DB" ) {
  # In terms of dB
  .....
  }
else {
  if ( arg2 eq "OHMS" ) {
    .....
    }
  }

The solution is to write a gnuplot string function subchr() which replaces a single matching character, only if found (the ternary ?), and to call it for each of the characters to be converted. Fortunately, gnuplot string range specifiers appear well behaved when indexing before (0) and beyond (stringlength+1), returning null for that region. This allows us to return the string with each character upgraded.

This avoids the need for a system() call to a program like awk, etc.

mjp
  • 93
  • 10