Unexpected Random behaviour in Recursive Function.

Hi, I'm working on a rule that selects random values from a list. This rule must pick a given number of values and it may not pick the same one twice.

Logically what I'm using then is a recursive rule so that I can pass values forward to check what the acceptable values are.

However I'm having a strange behaviour which I believe is the fault of the random number generation in Appian. The first recursion will perform exactly as expected however every successive call will just select the item that is at the front of the list.

Can anyone explain this issue?

 

The code looks like this and it rather betrays the less than professional use case for this rule.

/*Blakej3_Minesweeper_recursivelyFindMine_B*/
load(
  /*Find index of the array to target.*/
  local!index:roundup(
      rand() * (length(ri!boardIds)),
      0
    ),
  if(
    contains(
      ri!mines,
      ri!boardIds[local!index]
    ),
    /*If the index has already been added to mines try again.*/
    rule!Blakej3_Minesweeper_recursivelyFindMine_B(
      boardIds: ri!boardIds,
      mines: ri!mines
    ),
    /*Otherwise update the values and restart.*/
    {
      mines:  append(ri!mines, ri!boardIds[local!index]),
      boardIds: remove( ri!boardIds, local!index )
    }
  )
)

The rule to generate each mine is also recursive, this is both a legacy and a helpful way of removing values incase the list is prepopulated, it looks like this.

/*Blakej3_Minesweeper_recursivelyFindMines*/
if(
  /*Abort recursion at the end or if there are no more values.*/
  and(
    ri!calls >= 1,
    not(rule!APN_isEmpty(ri!boardIds))
  ),
  load(
    /*Generate the next mine*/
    local!nextMine: rule!Blakej3_Minesweeper_recursivelyFindMine_B(
      boardIds: ri!boardIds,
      mines: ri!mines
    ),
    /*Recurse*/
    rule!Blakej3_Minesweeper_recursivelyFindMines(
      mines: local!nextMine.mines,
      boardIds: local!nextMine.boardIds,
      calls: ri!calls-1
    )
  ),
  /*At the end of the chain return the values.*/
  {
    mines: ri!mines,
    boardIds: ri!boardIds
  }
)

Both mines and boardIds are dictionaries in the form of {x:_, y:_}

  Discussion posts and replies are publicly visible

Parents
  • Your local!index variable doesn't get updated because it's in a load(). Change that to with() and it should work.
  • Afraid not. That doesn't explain the problem with the behaviour as if it wasn't being reevaluated it would just take the same index value each time rather than taking a random one and then always taking the 1st.

    However to be thorough I did try this. Even after both loads are changed to with it doesn't change how the process works.

    I think the problem with your solution is that despite appearances each local!index is created in its own separate Load() and as theres no user interaction it actually never receives a signal telling it to refresh the value anyway.
  • +1
    Certified Lead Developer
    in reply to jonathanb0001

    I think the root of this issue lies in your implementation somewhere. I whipped up a simple recursive rule that uses rand() and returns ri!numTimes random numbers from 1 - ri!range. Running this rule results in a list of different random numbers each time. This seems to rule out any issues with rand() and recursion.

     

    if(
      ri!numTimes = 0,
      null,
      append(
        roundup(rand() * ri!range),
        rule!this_rule(
          numTimes: ri!numTimes - 1,
          range: ri!range
        )
      )
    )

  • It's now working however I can't find a satisfying reason why.
    I added a length parameter which tracked the range for the random number and it was constantly 1.
    I expanded the lists of dictionaries for boardIds by adding a forEach statement which just returned each item. Now it works... My best guess is that Appian was struggling with list/dictionaries (Maybe it was seeing a list of list of dictionaries? However in that case I'd have expected it to append all of them at once.). And that the for each restructured it into something it found easier to handle.
    Yeah, It doesn't really make sense to me.
  • You could also use reduce which is recursive, to remove a number of entries from the whole list, and then use difference to get the ones which have been removed.  In this case the array of integers passed to reduce is only used to control the number of times it is called. Have the Navy given you a call yet ;)

    difference(
      ri!inputArray,
      reduce(
        rule!removeRandArrayEntry,
        ri!inputArray,
        enumerate(
          ri!numTimes
        )
      )
    )

  • Wow been a while since this thread was active. Thanks for the response that's a really interesting approach that I'd never considered.

    I'm a little concerned about using an expression like that due to the nature of the random function being applied multiple times.
    Indexing a single value against a list guarentees that a single value will be selected however I don't see how your rule can ensure that one and only one value will be selected for each time the rule runs over the list.
    That just sets off all sorts of alarm bells in my head.

    Didn't know the Navy was looking for me. Jokes on them I don't own a boat so they'll be searching for a long time :P

Reply
  • Wow been a while since this thread was active. Thanks for the response that's a really interesting approach that I'd never considered.

    I'm a little concerned about using an expression like that due to the nature of the random function being applied multiple times.
    Indexing a single value against a list guarentees that a single value will be selected however I don't see how your rule can ensure that one and only one value will be selected for each time the rule runs over the list.
    That just sets off all sorts of alarm bells in my head.

    Didn't know the Navy was looking for me. Jokes on them I don't own a boat so they'll be searching for a long time :P

Children
No Data