Remove Item at specific index from dynamic array

Hello,

I am new in programming with natural, coming from a JAVA/Node background. I am currently going through all the tutorial material I can find on the SAG website but the specific task I am trying to accomplish is not further shown in the official DOCS nor the video series.

Basically, I want to perform a simple task which is removing a dynamic arrays value at a specific index given via the INPUT statement.

In other languages this would look something like this

array := array.removeAtIndex(#input)

I could not find any way of doing this the “natural” way. So far I wrote a small program but I am stuck at the DELETE or ALTER part of the array.

DEFINE DATA LOCAL 
1 #ARRAY (A45/1:*)
1 #ITEM_COUNT (I4)
1 #LOOP-INDEX (I4)
1 #DEL_INDEX (I2)
1 #VALUE (A45)

END-DEFINE

/* ADD 3 TESTVALUES TO #ARRAY
FOR #LOOP-INDEX =1 TO 3
  PERFORM ADD_ITEM
END-FOR

/*REMOVE AT INDEX
PERFORM DELETE_AT_INDEX

DEFINE SUBROUTINE ADD_ITEM
#ITEM_COUNT := *OCC(#ARRAY(*))
WRITE 'ITEM COUNT BEFORE ADD_ITEM:' #ITEM_COUNT 'LBOUND' *LBOUND(#ARRAY) 'UBOUND' *UBOUND(#ARRAY) /
#ITEM_COUNT := #ITEM_COUNT+1
EXPAND ARRAY #ARRAY TO (1:#ITEM_COUNT)
COMPRESS 'NEWVALUE' #LOOP-INDEX TO #ARRAY(#ITEM_COUNT)
END-SUBROUTINE

DEFINE SUBROUTINE DELETE_AT_INDEX
WRITE '****************ARRAY CONTENT***********************'//
FOR #LOOP-INDEX = 1 TO *UBOUND(#ARRAY)
  WRITE 'INDEX=' #LOOP-INDEX(AD=L) 'VALUE=' #ARRAY(#LOOP-INDEX)
END-FOR
INPUT 'INDEX TO BE DELETED:' #DEL_INDEX
/******************************************************************
/*TO-DO REDUCE ARRAY (REMOVE AT IDNEX #DEL_INDEX) DISPLAY NEW ARRAY
/****************************************************************** 

END-SUBROUTINE

END

I would very much appreciate if someone could help me out here or at least push me into the right direction.

Thank you in advance

Other languages likely implement this either by

  • Their “array” is actually a linked list ; you remove an item by attaching the links from it’s neighbours to each other to cut it out (which is fast)
  • They copy the entire array to a new array without the removed item (which is perceived as an OK spend of computing power and memory, because these are languages that aren’t from the 1970s)

The docs for REDUCE read like Natural changes the value of the top index and deallocates the memory at the end of the array, which means to remove an item at index N you have to copy all items above index N to one index lower, THEN do a REDUCE.

It might be easier and faster to just blank items you don’t want to process and then write loops that only process non-blank items.

1 Like

First of all, I believe you really mean X-ARRAYS.

Anyways Try the following program for educational purposes:

** XXARR01

DEFINE DATA

LOCAL

01 #ARRAY (N2/) / X-ARRAY

1 #I (I4)

1 #J (I4)

1 #K (I4)

1 #L (I4)

1 #ITEM_COUNT (I4)

1 #LOOP-INDEX (I4)

1 #DEL_INDEX (I2)

1 #VALUE (N2)

END-DEFINE

WRITE TITLE *PROGRAM 65T *PAGE-NUMBER

FOR #I = 1 TO 5

PERFORM #ADD_ITEM

LOOP

1 Like

Thank you for your quick response. I will try that. I made the mistake thinking I can treat an dynamic array like a List of other languages like Java. Thank you very much.

here’s one solution:

DEFINE DATA LOCAL 
1 #ARRAY (A45/1:*)
1 #ITEM_COUNT (I4)
1 #LOOP-INDEX (I4)
1 #DEL_INDEX (I2)
1 #VALUE (A45)
1 #TOP_INDEX (I2)

END-DEFINE

/* ADD TESTVALUES TO #ARRAY
#TOP_INDEX := 5
EXPAND ARRAY #ARRAY TO (1:#TOP_INDEX)
FOR #LOOP-INDEX =1 TO #TOP_INDEX
  PERFORM ADD_ITEM
END-FOR

/*REMOVE AT INDEX
PERFORM DELETE_AT_INDEX

DEFINE SUBROUTINE ADD_ITEM
#ITEM_COUNT := *OCC(#ARRAY(*))
WRITE 'ITEM COUNT BEFORE ADD_ITEM:' #ITEM_COUNT 'LBOUND' *LBOUND(#ARRAY) 'UBOUND' *UBOUND(#ARRAY) /
#ITEM_COUNT := #ITEM_COUNT+1
COMPRESS 'NEWVALUE' #LOOP-INDEX TO #ARRAY(#LOOP-INDEX)
END-SUBROUTINE

DEFINE SUBROUTINE DELETE_AT_INDEX
WRITE '****************ARRAY CONTENT***********************'//
FOR #LOOP-INDEX = 1 TO *UBOUND(#ARRAY)
  WRITE 'INDEX=' #LOOP-INDEX(AD=L) 'VALUE=' #ARRAY(#LOOP-INDEX)
END-FOR
INPUT 'INDEX TO BE DELETED:' #DEL_INDEX
#TOP_INDEX := *UBOUND(#ARRAY)
if #DEL_INDEX < #TOP_INDEX  /* if eq, then just erase top index, nothing to shift. 
  #ARRAY(#DEL_INDEX:#TOP_INDEX -1) := #ARRAY(#DEL_INDEX+1:#TOP_INDEX)
end-if
#TOP_INDEX := #TOP_INDEX - 1
RESIZE ARRAY #ARRAY TO (1:#TOP_INDEX)

/******************************************************************
/*TO-DO REDUCE ARRAY (REMOVE AT IDNEX #DEL_INDEX) DISPLAY NEW ARRAY
/****************************************************************** 
FOR #LOOP-INDEX = 1 TO *UBOUND(#ARRAY)
  WRITE 'INDEX=' #LOOP-INDEX(AD=L) 'VALUE=' #ARRAY(#LOOP-INDEX)
END-FOR

END-SUBROUTINE

END

note that I try to minimize the EXPAND/REDUCE/RESIZE operations as an EXPAND to add one occurrence (for example) means a new array is allocated with room for one more array entry and values copied from original array to new one. In a large loop, that is a lot of CPU. If you know the size of the array in advance, then just allocate that with one EXPAND/RESIZE statement. If you don’t know it, use a reasonably large estimate, then only allocate more room if that is used up (and allocate larger chunks that just 1 if it makes sense). If needed, once the array is loaded, use REDUCE/RESIZE to free up unused elements.

Thanks for answering, you saved my day.