Enum - getting ordinal index?


#1

Would it be possible, with the current way Enums are implemented, to create methods that return the index of a member of an enum, or a member via index?

For example;

//Get enum value as Integer
FireModes.@@semi-auto>>Integer //Would return 0 as Firemodes.@@semi-auto is the first member of FireModes

//Get enum value by Integer
FireModes.at(0) //Would return FireModes.@@semi-auto

The second example can be achieved in some respect by creating a list that contains each of the Enum’s members in the correct order. It can also be implemented in BP by using a select node.

Returning the integer value of can be achieved by using a for loop with the List of Enum members and exiting when the provided value is equal to that in the List:

(FireModes f) Integer //Provided value, f
[
  !l : {FireModes.@@semi-auto FireModes.@@automatic} //List of Enum members, l
  !i : 0! //Index, i

  Loop
   [
     if [l.at(i) = f] or i = [l.length - 1] [exit] //Exit if matching or reached end

     i++ //Increment index
   ]
]

This has to be made for each Enum individually as does the .at() method mentioned above:

(Integer i) FireModes //Provided index, i
[
  !l : {FireModes.@@semi-auto FireModes.@@automatic} //List of Enum members, l
  l.at(i) //Get the member at the given index
]

Are there currently any alternatives to the ones provided above that would work similarly for any Enum?


Shift modifier for Copy, Paste and Cut
#2

Yes, getting the index of an enumeration and getting an enumeration by index would be possible.

Could you explain why you want to index into enums? It would help to know your end goal.

As mentioned previously, the current Enum mechanism is only temporary. It was a quick hack that was good enough to the point that the proper mechanism was put on the backburner. So we probably want to change over to the proper mechanism before we spend a bunch of effort adding stuff to this temporary mechanism.

That being said, the temporary Enum mechanism stores all the enumerations in an ordered array which could be used for indexing. However, there are a few caveats:

  • The enumerations are placed in an internal array in the order that they are declared irrespective of any value that they are assigned. If the enumerations are declared in an order that is different than their ordinal position according to their values then looking them up will be slower or possibly incorrect.
  • The values assigned to enumerations using !int() may not match their index position. If the value doesn’t match the index then looking them up will either be slower or incorrect.
  • The values assigned to enumerations using !int() may have gaps greater than 1 between enumerations. If there are gaps - you guessed it - indexing will be slow or possibly incorrect.

Determining the index

We can make an index() method that will give an enumeration’s ordinal index - FireModes.@@semi-auto.index. [The Integer() conversion method is already used and returns an enumeration’s value which may be different than its index.]

() Integer
  [
  // Pseudocode:
  // - get the value and see if it matches the enumeration at that index point
  // - if it matches return the value as the index
  // - else iterate through the enumeration array
  // - once the current enumeration is found return that index
  ]

Getting by index

Here is a class at() method that we can make. It would be called like FireModes.at(idx). [Can’t use the index operator version FireModes{idx} since the parser would confuse it with a list of FireMode objects - FireModes{}.]

(Integer index) ThisClass_
  [
  // Pseudocode:
  // - just return enumeration at the provided index position
  ]

Note that ThisClass_ is a generic type that acts as whatever type the object the routine is running on. So if ThisClass_ is used in Enum it becomes Enum and any subclass of Enum such as FireMode will use FireMode.

Incrementing and decrementing

Looking at the temporary mechanism, I see that incrementing ++ and decrementing -- enumerations currently assumes that there are no gaps in the values between enumerations. Fixing them so that they worked with gaps would make them pretty slow so we’ll wait until the proper enum mechanism is in place which can ensure they work properly and still be fast.

When?

We can probably create these methods fairly quickly and have them in the next update. We had been hoping to update sooner, though we also have been upgrading to UE4.18 which turned out to have many changes in the Slate UI which is used to create the SkookumIDE so it has taken a bit longer.


Note that there is no need to make a copy of a literal which already makes a brand new object in the first place.

So just use: !i: 0
rather than: !i: 0! which is the same as !i: Integer!copy(0)

See Expression instantiation in the Primer in the online docs for more info.


#3

Until we get a chance to put these two indexing methods in and you get the update, here are some quick and dirty versions you can use in the meantime assuming that the enumerations that you are using are declared in the correct order and their values match their index:

// Enum@index().sk in the Core overlay
() Integer
  [
  this>>Integer
  ]

I can’t see a way to make a generic at() without needing to do something more sophisticated though you can make one for a specific class such as FireMode@at():

// FireMode@at().sk
(Integer index) FireMode
  [
  FireMode!int(index)
  ]

#4

We’ve added the following methods to Enum which will be available in the next update:

  • index()

    // EAxis{none, x, y, z}
    println(EAxis.@@none.index)  // 0
    println(EAxis.@@x.index)     // 1
    println(EAxis.@@z.index))    // 3
    
  • at()

    println(EAxis.at(0))   // EAxis.@@none
    println(EAxis.at(1))   // EAxis.@@x
    println(EAxis.at(-1))  // EAxis.@@z
    
  • first()

    EAxis.first   // EAxis.@@none
    
  • last()

    EAxis.last   // EAxis.@@z
    
  • length()

    EAxis.length // 4
    
  • do()

    EAxis.do[println(enum)]
    

    Prints:

    EAxis.@@none
    EAxis.@@x
    EAxis.@@y
    EAxis.@@z