wave assignment and multiple return statements

I wonder if there is some way to assign the elements of two waves simultaneously with one function, something like the following (silly) MWE:

function [variable out1, variable out2] sub(variable in)
    out1 = in/2
    out2 = in*2
    return [out1, out2]
end

function main()
    make/O/N=(2,2) W_in=p*q+1, W_out1, W_out1
    [W_out1[][], W_out2[][]] = sub(W_in[p][q])
end

Main() doesn't compile. The alternative is to do an explicit loop in Main(), or to split sub() into to two separate functions. Neither seems attractive. The actual use-case here is to split a string expression to return two values.

 

 

In case the two returns is not possible, with what you want, would it work to put the return the two strings in a 1x2 text wave function?

W_out1[][] = splittextwave(w_in[p])[q]

function [Wave out1, Wave out2] sub(Wave in)
    out1 = in/2
    out2 = in*2
    return [out1, out2]
end

function main()
    make/O/N=(2,2) W_in=p*q+1, W_out1, W_out2
    [W_out1, W_out2] = sub(W_in)
end

I haven't thought about any performance consequences of this, but if you change to inputting and returning waves instead of variables, it will compile and I think do what you want. 

JJ, I had considered something like that, to return a one point, two layer wave, but I didn't get this to work.

Ben, the wave approach will not work in my case, which is given below. I want to extract data and error values from string expressions in which the error is reported in terms of last significant digit. The strings are copied & pasted into a 2D text wave. The main function works, I just hoped I could get rid of the nested loops, mainly for aesthetic reasons.

function GetDataAndError(wave/T txt2d)
    variable nRows=DimSize(txt2d,0)
    variable nCols=DimSize(txt2d,1)
   
    Make/D/O/N=(nRows, nCols), W_values, W_errors
    CopyDimlabels txt2D, W_values, W_errors
   
    variable i, k, value, error
    for(i=0; i<nRows; i++)
        for(k=0; k<nCols; k++)
           
            [value, error] = SplitExpression(txt2d[i][k])
            W_values[i][k] = value
            W_errors[i][k] = error
           
        endfor
    endfor
end

function [variable value, variable error] SplitExpression(string str)
       
    // split the input string, such as e.g. "3.25(4)", into two numeric parts
    // ignore any other non-numeric characters that may be in the string, as in e.g. "3.25 (4)#"
    string sValue, sError
    splitstring/E=".*?([0-9.]+).*?\(\s*(\d+)\s*\).*" str, sValue, sError
   
    // does value has a decimal point, if yes how many digits behind it?   
    variable PosDecimalPoint = strsearch(sValue, ".", 0)
    variable nDigits = PosDecimalPoint == -1 ? NaN : (strlen(sValue) - PosDecimalPoint -1)
    value = str2Num(sValue)
   
    if (PosDecimalPoint == -1)
        // if value has no decimal point, error is absolut
        error = str2num(sError)
    else
        // if yes, scale error according to digits after decimal of data value
        error = str2num(sError)/10^(nDigits)
    endif
   
    return [value, error]
end


// sample data:
Make/O/T txt = {{"23652 (23)"," 0.125(3)"}, {"125.5(9)","2.3(11)a)"}}

 

ok, I think I do it like this, but thanks for the comments!

function GetDataAndError(wave/T txt2d)
    variable nRows=DimSize(txt2d,0)
    variable nCols=DimSize(txt2d,1)
   
    Make/D/O/N=(nRows, nCols), W_values, W_errors
    CopyDimlabels txt2D, W_values, W_errors
   
    W_values = SplitExpression(txt2D[p][q], 0)
    W_errors = SplitExpression(txt2D[p][q], 1)
end

function SplitExpression(string str, int mode)
    // split the input string, such as e.g. "3.25(4)", into two numeric parts
    // ignore any other non-numeric characters that may be in the string, as in e.g. "3.25 (4)#"
    variable Value, Error
    string sValue, sError
    splitstring/E=".*?([0-9.]+).*?\(\s*(\d+)\s*\).*" str, sValue, sError
   
    // does value has a decimal point, if yes how many digits behind it?   
    variable PosDecimalPoint = strsearch(sValue, ".", 0)
    variable nDigits = PosDecimalPoint == -1 ? NaN : (strlen(sValue) - PosDecimalPoint -1)
    value = str2Num(sValue)
   
    if (PosDecimalPoint == -1)
        // if value has no decimal point, error is absolut
        error = str2num(sError)
    else
        // if yes, scale error according to digits after decimal of data value
        error = str2num(sError)/10^(nDigits)
    endif
   
    variable out=nan
    out = (mode ? error : value)
   
    return out
end

 

Seems like overhead to go through the split expression function twice.

function GetDataAndError(wave/T txt2d)
    variable nRows=DimSize(txt2d,0)
//    variable nCols=DimSize(txt2d,1)
   
    Make/D/O/N=(nRows, 2), W_values, W_errors
    CopyDimlabels txt2D, W_values, W_errors
    Make/D/O/N=(nRows,2)/FREE rvalues
   
    rvalues[][]= SplitExpression(txt2D[p][q],0)[q]
    W_values = rvalues[p][0]
    W_errors = rvalues[p][1]
    return 0
end

function/WAVE SplitExpression(string str, int mode)
    // split the input string, such as e.g. "3.25(4)", into two numeric parts
    // ignore any other non-numeric characters that may be in the string, as in e.g. "3.25 (4)#"
    variable Value, Error
    string sValue, sError
    splitstring/E=".*?([0-9.]+).*?\(\s*(\d+)\s*\).*" str, sValue, sError
   
    // does value has a decimal point, if yes how many digits behind it?  
    variable PosDecimalPoint = strsearch(sValue, ".", 0)
    variable nDigits = PosDecimalPoint == -1 ? NaN : (strlen(sValue) - PosDecimalPoint -1)
    value = str2Num(sValue)
   
    if (PosDecimalPoint == -1)
        // if value has no decimal point, error is absolut
        error = str2num(sError)
    else
        // if yes, scale error according to digits after decimal of data value
        error = str2num(sError)/10^(nDigits)
    endif
   
    make/N=(2)/D/FREE rwave
    rwave[0] = value
    rwave[1] = error
   
    return rwave
end

 

JJ, thanks, but I think your version will not work for the general case of a 2D text wave as input, at least it doesn't for the example data in my second post. Nonetheless, it inspired my to go back to the earlier thought to return a one point two layer wave. I initially didn't get this to work, because I couldn't fit the r-index into the function call, and it just didn't occur to me at the time that it must be outside (as in your example with [q]), but yes, of course! 

Here the 3D return wave version:

function GetDataAndError(wave/T txt2d)
    variable nRows=DimSize(txt2d,0)
    variable nCols=DimSize(txt2d,1)
     
    Make/D/O/N=(nRows,nCols,2)/FREE rvalues
    rvalues = SplitExpression(txt2D[p][q])[r]
   
    SplitWave/O/NAME="W_values;W_errors" rvalues
    return 0
end

function/WAVE SplitExpression(string str)
    // split the input string, such as e.g. "3.25(4)", into two numeric parts
    // ignore any other non-numeric characters that may be in the string, as in e.g. "3.25 (4)#"
    variable Value, Error
    string sValue, sError
    splitstring/E=".*?([0-9.]+).*?\(\s*(\d+)\s*\).*" str, sValue, sError
   
    // does value has a decimal point, if yes how many digits behind it?  
    variable PosDecimalPoint = strsearch(sValue, ".", 0)
    variable nDigits = PosDecimalPoint == -1 ? NaN : (strlen(sValue) - PosDecimalPoint -1)
    value = str2Num(sValue)
   
    if (PosDecimalPoint == -1)
        // if value has no decimal point, error is absolut
        error = str2num(sError)
    else
        // if yes, scale error according to digits after decimal of data value
        error = str2num(sError)/10^(nDigits)
    endif
   
    make/N=(1,1,2)/D/FREE rwave
    rwave[0][0][0] = value
    rwave[0][0][1] = error
   
    return rwave
end

 

 

 

In reply to by jjweimer

jjweimer wrote:

Seems like overhead to go through the split expression function twice.

...
    rvalues[][]= SplitExpression(txt2D[p][q],0)[q]

the implicit loop also runs SplitExpression twice for each string.

Tony -- I wonder about the administrative overhead of what I see as (loading + running + unloading)x2 (as per Chris' original) versus (loading + (running)x2 + unloading). Both run twice, but the second option loads/unloads once (as far as I understand the implicit behavior).

ChrLie's original runs a little faster than the revised version (for me the second method is about 30% slower). jjweimer's version, after adjusting to work on all the values in the 2D input wave, runs at about the same speed as ChrLie's revised version.

multithreading (by simply adding the threadsafe keyword before the function definition and multithread before the wave assignment) will make a difference, even for as few as 100 values in the input wave. About a factor of three speed difference for me.

implicit loops are appealingly succinct, but not necessarily more efficient than multiple assignments. I have caught myself putting conditionals in an implicit loop that don't depend on p [q, r,..], which is just bad.