How to handle final array element undefined

Started by cssyphus, April 11, 2024, 07:40:52 AM

Previous topic - Next topic

cssyphus

Due to how I construct some arrays** the final element can be undefined. Therefore, looping through the array causes problems when reaching the final undefined element:

#DEFINEFUNCTION udfShowArrayAsList(_arrAllSwitches)
_lst = ``
for _n = 0 to arrInfo(_arrAllSwitches, 1)-1
_lst := _arrAllSwitches[_n] : @TAB
next
_chk = askItemList(``, _lst, @TAB, @SORTED, @SINGLE)
#ENDFUNCTION

When the loop reaches the final array element, the line `_lst := _arrAllSwitches[_n] : @TAB` crashes.

What is a good way of handling this oft-encountered situation?

Is there a better way of initializing undetermined-size arrays?

---

** When I don't know ahead-of-time the number of elements an array will ultimately contain, I will do this:

_arrAllSwitches = arrDimension( 1 )
while 1
if some_condition
arrayInsert( _arrAllSwitches,  arrInfo(_arrAllSwitches, 1)-1,  1,  _newSwitchName )
endif
endwhile

This results in an array that contains exactly the correct number of elements, plus one.  I tried initializing the array using arrDimension( 0 ), but that fails immediately.

JTaylor


cssyphus

I construct the array by arrDimension()'ing with a value of '1', and then using arrayInsert() to add each new element, like so:

_arr = arrDimension( 1 )
Loop
    arrayInsert( _arr,  arrInfo(_arr, 1)-1,  1,  "New item" )
EndLoop

When loop is completed, I have an array with exactly the correct number of elements, plus one extra. I don't know of any way to avoid having the final array element be undefined when using this array-building pattern.

It would be a pain to need to test/remove the final element every time I construct an array using this pattern, but that is what I am doing now. I am wondering if there is a better way to handle it.


JTaylor

How is the value for each element obtained?   Delimited List?  File?  Can you check for the value and only insert if there is one?

Jim 

cssyphus

I am already doing something like that - but that is not quite the issue.

Whether I add one element or 9999, the array will grow in size (per arrayInsert) to hold exactly the correct number of elements, plus one. It is that final, undefined, element that is the app-crasher - because WB's array functions always generate an error when attempting to read an undefined element.

The problem, really, is how arrays are created/handled in WB. It is rare that we know ahead-of-time the number of elements the array will contain, right? So we can either use this pattern, or dim the array with a ridiculously high number of elements - resulting in many undefined elements after all data has been added. Regardless which method creates the array, attempting to read the array in a loop will crash when attempting to read an undefined element (and, of course, there is no way (that I know of...? Am I missing something?) to elegantly loop over the array from start to finish if there is even one undefined element).

To solve this for my purposes, I add one extra step after building the array - I run it through a UDF that tests for an undefined final element and removes it. But that UDF only handles one final element, and it is an extra step that other languages do not require. Here's the UDF, FWIW:

_arrSomething = udfCreateMeASkookumArray()
udfRemoveUndefinedFinalElement(_arrSomething)

#DEFINESUBROUTINE udfRemoveUndefinedFinalElement(_arr)
    intControl(73, 2, 1, 0, 0)
    for _n = 0 to arrInfo(_arr, 1)-1
        if _arr[_n] != ` qxwzyjgx` then continue
    next
    drop(_lastItemMT)
    RETURN

    :WBERRORHANDLER
        if wberrortextstring == `Uninitialized array element`
            if !isDefined(_lastItemMT)
                arrayReDim(_arr, arrInfo(_arr,1)-1)
                _lastItemMT = 1
            endif
        endif
    Return ;WBERRORHANDLER
#ENDSUBROUTINE ;udfRemoveUndefinedFinalElement



BTW, Jim, I've finally taken a look at your wbOmnibus extender and WOW! Sure wish I'd looked at it sooner. The toToast() function will be immediately implemented and I am keen to try the dialog events. I am also highly interested in the sqLite and csv functions, but will need to find time to experiment. Sure wish there was a quick-and-dirty demo video (esp. for the csv and sqLite funcs), like you made with your RAD app, but that is asking a little much when you've already put so much time into development. Very big thanks for your years of effort on this.

JTaylor

I would recommend the new SQLite Extender that Tony recently released over what I have done.   There might be a function or two that would be useful but most stuff would be better served with that one since I assume they will exist longer than I will.

Jim

JTaylor

No way of which I am aware to cleanly determine if an element is uninitialized.  Maybe WB folks will consider adding an option to ArrInfo()?

Still not sure why you have to add that extra row but probably related to how you are loading the data.  I'll stop asking questions unless you want to pursue a solution.

Jim

JTaylor

If you need further info on the CSV stuff (anything for that matter), just ask.  Not opposed to doing a video but not sure what that would look like, without some direction. WB_RAD lent itself to doing a video, with it being an application.   Happy to put together a more complete example, if that would help.  I really don't think you will have much trouble putting it to use.

I REALLY like what I did with the CSV stuff. The library I based it on is one of the more elegant pieces of work I have seen.  Plus, I was finally starting know what I was doing with C++ by that time, at least a bit.

Jim

cssyphus

You mentioned switching from using arrays to using the csv functions. I can't quite wrap my head around that. I loop through arrays all the time, but can't image how that could be replaced. A basic example showing working with a 2-dim dataset (i.e. a spreadsheet) would be very helpful.

Ditto for the sqLite or dialog functions. Just a couple of simple (but not too simple) examples would get the viewer over the hump of "Hmmmm... how does one get started with this?" and "What does using these functions look like?"

kdmoyers

I think IsDefined() will tell if an array element is defined.
The mind is everything; What you think, you become.

JTaylor

By Golly, You're Right!!!

Quote from: kdmoyers on April 11, 2024, 11:00:34 AMI think IsDefined() will tell if an array element is defined.

cssyphus

YES! You are right Kirby, that IS the solution.  That was so obvious and I didn't see it.

Successful example:

_arr = arrDimension(3)
_arr[0] = `bob`
_arr[1] = `sam`

for _n = 0 to arrInfo(_arr, 1)
    if isDefined(_arr[_n])
        display(1, ``, _arr[_n])
    endif
next
exit


Argh... all the years I put up with this just because I didn't ask sooner...

Kirby: Thanks!
Jim: Thanks!
Me: Doh!

spl

[off-topic], but might want to look into .NET Arraylist with the CLR
ObjectClrOption("useany","System.Net")
oList = ObjectClrNew("System.Collections.ArrayList")
oList.Add("Hello") 
oList.Add("World") 
oList.Add("!") 
oList.Add(500)

cTxt="My ArrayList":@CRLF
cTxt=cTxt:"Count: ":oList.Count:@CRLF 
cTxt=cTxt:"Capacity: ":oList.Capacity:@CRLF
cTxt=cTxt:"Fixed Len?: ":oList.IsFixedSize:@CRLF 
cTxt=cTxt:"Read only?: ":oList.IsReadOnly:@CRLF
cTxt=cTxt:"Item 4: ":oList.Item(3):@CRLF
oList=0
Message("",cTxt)
Exit 
Stan - formerly stanl [ex-Pundit]

JTaylor

Give me a bit of time and I will post a CSV example.  Actually found a bug so this request is helpful.  Not sure how I missed it with all the testing and personal use I have made of it.

As far as SQLite, I will not post sample code for that since I do not want to encourage use of my extender on that front since there is an "official" extender.  If I could, I would remove that from the extender but I know there are some people using it.

I think I included Dialog Extender examples with the Extender.  I will verify and put it back, if not.

Jim


Quote from: cssyphus on April 11, 2024, 09:57:09 AMYou mentioned switching from using arrays to using the csv functions. I can't quite wrap my head around that. I loop through arrays all the time, but can't image how that could be replaced. A basic example showing working with a 2-dim dataset (i.e. a spreadsheet) would be very helpful.

Ditto for the sqLite or dialog functions. Just a couple of simple (but not too simple) examples would get the viewer over the hump of "Hmmmm... how does one get started with this?" and "What does using these functions look like?"



snowsnowsnow

Yes, IsDefined() is certainly the usual way to deal with this.

Alternatively, you could use udsTry() and catch it that way.

cssyphus

This is curious:

_a = ``
_b = arrayize(_a, `|`)
_c = isDefined(_b[0])

Code errors out - isDefined() appears not to work in this case.

This also abends with an error:

_a = ``
_b = arrayize(_a, `|`)
if isDefined(_b[0])
  _c = _b[0]
endif

kdmoyers

I think these fail because array _b doesn't have any elements, and you are asking for element 0. 

I guess it could be argued that IsDefined, in particular, should be tolerant of this and simply return false?  Not sure.

In the mean time, you could check for it yourself in a piecewise fashion, by checking isdefined(_b) and then checking the value of arrinfo(_b,1) to see that it's at least 1, and then check isdefined(_b[0])
The mind is everything; What you think, you become.

td

if IsDefined(_b) > 0 then if VarType(_b) == 256 then if Arrinfo(_b, 1) then if IsDefined(_b[0]) then  _c = _b[0]
"_b[0]" is not a variable name but an identifier (variable name) plus operator that WIL attempts to evaluate as a variable name. Since the expression is invalid, it fails before the "IsDefined" function implementation is even called. As stated in the documentation, the function only accepts variable names.
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

td

FWIW, I think a case can be made for allowing IsDefined to handle out-of-bounds array references. The only issue is deciding if it is practical or not.
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

snowsnowsnow

So, as of this writing, there isn't a way to do the equivalent of:

IF 0 in myArray THEN ...

(right?)

So, as you say, IsDefined() checks to see if a variable has a value, but it doesn't handle checking whether an array has a certain element.

td

You can use ArrInfo to determine an array's maximum index.

IsDefined will check if an array element is defined. In the case of array elements "defined" means the element has been assigned a value. However, "defined" does not mean that it checks to see if the indicated index is within the allowed index range of the array. The interpreter errors before the function gets the opportunity.

The fact that it even checks array elements for assignment is a bit of a kludge but array elements are implemented mostly like WIL variables internally. From a language design and implementation perspective, it is logical.

As I mentioned earlier it might make sense to allow IsDefined to handle out-of-range array indices as an enhancement to a future release.
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

snowsnowsnow

Right.  We are saying the same thing(s).

I could see how either an extension to IsDefined() or a brand new function could be made to work.  And, yes, I get the point that when you pass (say) A[5] to IsDefined(), it gets evaluated before IsDefined() is ever invoked.  This is just the way WB works.  This is why I have more than a couple Subroutines in my toolbox that take a string to be evaluated as the parameter (i.e., if you pass "A[5]" instead of A[5}, then it won't get evaluated before the routine is called).  Hence my frequent recommendation of udsTry().

Finally, yes, since WB arrays always have indexes in the range of 0..n (for some value of n and with no gaps), using ArrInfo() should be sufficient for the task.