Forum Academy Marketplace Showcase Pricing Features

How to get the particular item's cell index (item#) in RG or WF step

A tip to get the particular item’s cell index (item#) in RG or when using the list in WF (“make changes in the list” step) to address the previous or next item in the list:

  1. Take the list of types
  2. :format as text of id’s separated by comma
  3. extract with Regex all the commas before the target item’s id. Regex: ,(?=.*?item_id)
  4. count commas + 1
  5. you got the target item’s cell index (item# in the list)

P.S. I’m not sure how that performs on a very long list, but on a list of 100 items worked as a charm.

1 Like

What is your use case you needed the cell number of an item while running a workflow?

Backend API WF → I have a list of blog posts sorted by ‘likes’ number. I needed to compare a particular blog post’s ‘views’ number with the previous blog post’s ‘views’ number in that list and run the next steps based on the comparison result.

1 Like

Wouldn’t a :count of the list + 1 work the same? :thinking:

How? Tell me the WF sentence. How do you know the particular item’s place somewhere inside the list, to address the prior item? Actually, the flow, I made, was for complex financial calculations which involve many years of data. So, I needed to compare some financial data between the years, and those years were sorted by some dynamic field, not the date. The example with blog posts was to simplify understanding of the issue.

As I mentions at the beginning, it isn’t only about the cell’s number but rather item# on the list.

From what I understand of your original post, that’s pretty much what your doing? Counting the list?

I’ve been meaning to respond to this: tl;dr this is pretty freaking brilliant.

For those asking: What the OP (@gs1) explains is a very clever (in my estimation) workaround for the fact that Bubble has no native “indexOf()” function.

It takes advantage of the fact that regular expressions have an implicit “up to” function.

What @gs1’s format-then-regex does is execute what’s essentially a “first index of” function. It does this by (1) changing the list to a comma-separated list of text blobs (that we don’t care about, but it’s important that they are unique) and then (2) spitting out the separators (commas) between them. This results in a list of commas (could be any other separator, of course, but comma is the default) that represents the “spaces” between what were previously the unique items (converted to strings)… BUT ONLY UP TO the item in question (item_id). Upon finding that item, under the hood, we either stop, or continue thru the entire array, depending upon the implementation of regex.

This has the net effect of giving us the zero-based index of the item in question (by simply examining how many commas we are left with).

Consider: If the original list is just [item], the text-formatted list will have no commas in it. So, there must have only been one item. Note that, if the original list was empty, we would also get the same result, but should we need to, we could work around that with a condition. That is, we have a list of items, we can tell if it has any items or not. If it has no items, its length [its :count] will be zero. The list has no items [duh] and so we’ve already answered the question of where the item is in the list… which is to say, it cannot be present, as no items are present in the list. So we could just skip the regex step in this case.

However, if we know that our list always has items (or we determine that the original list has more than zero items based on the check described above and proceed), now the count of the commas is in fact the first zero-based index of the item in question in the list.

So, this is NOT the same as the count of the whole (prior-to-being-transformed) list. It’s a representation of where item_id first appears in the list. If item_id appears in the first position (index 0 as most languages would have it, or index 1 as Bubble would have it), there will be no commas. If item_id appears second in the list, there will be one comma (index 1 as most languages would have it, or 2 as Bubble would have it)… Etc.

To @gs1’s implied question about “is this performant?”, the answer is that it’s certainly NOT as performant as the browser’s native indexOf() function. However, it’s not much worse either. Consider that in this version, we do two things:

(1) a map function to iterate over the entire original array (list) to turn the list of Thing objects into their unique ID (in JS, psuedo-code: Array.map(item => function(get unique ID of item)). The get function may involve a database operation if Bubble doesn’t already know the unique ID of the Thing, but in most contexts it already does. So, just looping over the whole array once. (We do this all the time in JS, especially if we are lazy.)

(2) a second map function to traverse the list again, comparing the unique ID strings via regex to see if they match the condition. Depending upon the implementation of regex, we might traverse the entire list once more to arrive at the result. But a good implementation might only need to traverse the list up to the point where it identifies item_id and then automatically stops.

(3) the end result is an array, which (like Bubble’s “lists”, with their :count property) has a length property, which we can now inspect to understand the zero-based position of our target item in the original list (simply add 1 to it to get the 1-based position).

Regardless of how (2) works under-the-hood, the worst-case scenario would seem to be that we have to traverse the entire array twice to come to our understanding of where the target item is in the list.

In a native implementation of indexOf(), if we didn’t have to do the first map operation, we would only traverse the array at worst once. (If the target item is at the very END of the array – or if the item isn’t in the array – we will have to traverse the entire array once to determine this. If the target item is at the beginning of the array, we’d be done immediately.)

But in the @gs1 case, at worst it seems we’d have to do this (traverse the entire array) 2 or 3 times (depending on how regex works in a given browser’s implementation). That is, this “hack” at worst only takes 2-3x as long as a native indexOf() call, as far as I can fathom (speaking theoretically).

So, this is not terrible at all and, for any reasonable in-page operation, is VERY unlikely to be perceptibly slower than the native call. (If the original array is 100Ks of items, well 2-3x difference starts to become noticeable over the 1x case, but for numbers of items below this might well not even be detectable given that the resolution of the JavaScript date object is 1ms.)

Note that the speed of modern devices and modern, array-oriented JavaScript syntax conspire against us (in the interest of code-readability and general laziness) to encourage sequential array-based operations. For example, we might often write array.map(something).filter(something).replace_operation(something) (which will iterate over our array at least two full times) even though we know we could write the “map each item, filter each item” function in a more efficient way, because, practically speaking, it doesn’t effing matter.

So, my hat’s off to you, @gs1. :tophat: Good one.

1 Like

Wow, @keith, thank you! It’s an honor for me to get “hat’s off” from a pro like you :slightly_smiling_face:
Also, thanks for the brilliant explanation of how it works!

1 Like