Brian Dunning's FileMaker Custom Functions

ListPosition ( ListValues ; SearchValue )

Finds a value in a list and returns the position.

  Average rating: 4.5 (32 votes) Log in to vote

Joseph Arzate   Joseph Arzate
IT Professional Consultants
http://www.itprofessionalconsultants.net

Share on Facebook Share on Twitter

  Sample input:
ListPosition ( "Apple¶Grape¶Orange¶Cherry¶Orange" , "Orange")
  Sample output:
3
5

  Function definition: (Copy & paste into FileMaker's Edit Custom Function window)

/* INSTRUCTIONS / COMMENT / EXAMPLES - BEGIN
______________________________________________________________________
IT Professional Consultants Inc.
www.itprofessionalconsultants.net
Author: Joseph Arzate (9.5.13)
----------------------------------

Function: ListPosition ( ListValues, SearchValue)

[ListValues] - Expecting a "¶" delimited list of values. List submitted should not begin with MORE than 1 ¶ or the function will be inaccurate.
[SearchValue] - The value being searched upon in the List list provided. Any ¶ found in the SearchValue are removed.
-----------------------------------

Function Result: Returns a number of the position of the "SearchValue" in the list provided. If the "SearchValue" is found more than 1 time returns a ¶ delimited list of the position in the order it is found in the list. Returns 0 when "SearchValue" not found. This is a recursive function and will stop once the "SearchValue" no longer appears on the list in the order it is submitted.

Any ¶ characters submitted in the 'SearchValue' are removed and a provision has been added to remove any leading and trailing ¶ from the 'ListValues' if it exists. However, if more than 1 ¶ is submitted at the START of the 'ListValues' the CF will be inaccurate. Example: ListPosition( "¶¶Apple¶Grape¶Orange¶Cherry" ; "Orange" ) will result in "4" rather than "3".

Why this CF Created? Needed an easy way to search and analyze a value in a ¶ delimited list. Can be used with the GetValue function to grab the value from a list. Can also be used to count the number of times a value exists in a list by pairing with FM ValueCount function.

This function is more accurate than simply using 'PatternCount' due to the way PatternCount works in a value list.
Example: PatternCount( "123¶1234" , "123")
FM Result: 2

Using the CF with the same above example: ValueCount( ListPosition("123¶1234" , "123") )
Result: 1
-----------------------------------
Example
-----------------------------------
Example A - Simple Result
Syntax: ListPosition ( "Apple¶Grape¶Orange¶Cherry" , "Orange")
Result: "3"

Example A.2 - Multiple Result
Syntax: ListPosition ( "Apple¶Grape¶Orange¶Cherry¶Orange" , "Orange")
Result: "3¶5"

Example B - Count. If you want to know how many times a value exists in the list; wrap function with FM ValueCount function.
Syntax: ValueCount( ListPosition ( "Apple¶Orange¶Cherry¶Orange" , "Orange") )
Result: "2"

INSTRUCTIONS / COMMENT / EXAMPLES - END
______________________________________________________________________

CODE BEGIN - Function: ListPosition ( ListValues, SearchValue)
*/

Let ([
ListValues = Trim( Case( Left(ListValues ; 1 ) = ¶ ; Right ( ListValues; Length (ListValues)- 1 ) ; ListValues ) ) ; //House Cleaning. Remove any leading and trailing ¶ from the list submitted.
SearchValue =Trim( Substitute ( SearchValue ; ¶ ; "" ) ) ; //House Cleaning. Remove any ¶ from the Search Value.
EXISTS = not IsEmpty( FilterValues ( SearchValue; ListValues ) ) ; //Check to see if the value exists.
ListCount=ValueCount( ListValues ) ; //Count the List provided
POS= Position ( ¶ & ListValues & ¶ ; ¶ & SearchValue & ¶ ; 1 ; 1 ) ; //Position of the first occurence of the search value. IMPORTANT to pad with ¶ to ensure unique value is found.
LineNo = PatternCount( Left( ListValues ; POS ) ; ¶ ) + 1 ; //Line Position where the value is found.
FIRST= Case( LineNo = 1; 1 ;0) ; //Boolean result to see if the value found is in the first position of the list.
LAST= Case( LineNo = ListCount ; 1 ;0) ; //Boolean result to see if the value is the last position.
RemovedValue="x." & SearchValue & ".x" ; //On each iteration the found value is replaced with this value to prevent it from being processed again.
NewList= //This variable will produce a new List replacing the 'SearchValue' with 'RemovedValue' to ensure it moves to the next iteration of the 'SearchValue'
Case( FIRST =1 ; RemovedValue & ¶ & RightValues(ListValues ; ListCount - 1) ;
LAST <> 1; LeftValues( ListValues ; (LineNo - 1 ) ) & RemovedValue & ¶ & RightValues(ListValues ; ListCount - LineNo ) ;
LAST = 1 ; "" ; //Value found is the last item on list. No need to create a NewList, setting NewList to NULL causes function to exit.
) ;
FOUND = PatternCount( ¶ & NewList & ¶ ; ¶ & SearchValue & ¶ ) ] ; //Checks to see if the SearchValue exists in the newly constructed list. Used to determine if the function should exit or continue to process.

Case( exists = 0 ; 0; //Value not Found Exit.
LineNo & Case( FOUND > 0 ; ¶ & ListPosition( NewList; SearchValue ) ) //Value found and 'SearchValue' still on the list. Continue to evaluate list. Recursion will only occur if the value exists in the newly created list.
)
)

/* CODE - END: Function: ListPosition ( ListValues, SearchValue) */

 

Comments

Bart Bartholomay   Bart Bartholomay, South Florida
Sep 6, 2013
in the example you cite in the commented portion of the function:

"Example: ListPosition( "¶¶Apple¶Grape¶Orange¶Cherry" ; "Orange" ) will result in "4" rather than "3". "

I believe the correct answer should be "5" not "4" owing to the fact that you didn't seem to account for the first value, which contains a NULL just like the second value.
 
Joseph Arzate   Joseph Arzate, Los Angeles, CA
Sep 6, 2013
Thanks for the comment Bart, however, the function will remove the first ¶ return from the ValueList. The second ¶ return is considered as a valid position resulting in 4. The Line Position is evaluated from counting the number of ¶ to the 'Target' SearchValue.
 
Patrick   Patrick, Utah
Mar 24, 2015
I tried to use this function but it wouldn't let me because it uses itself. "LineNo & Case( FOUND > 0 ; ¶ & ListPosition( NewList; SearchValue ))
 
Joseph Arzate   Joseph Arzate, Los Angeles, CA
Mar 24, 2015
Hi Patrick,

This is a recursive function, so it will continue to call itself until all the items on the list have been processed. The CF will exit once it finishes all the items submitted to it.

This line of code:
"LineNo & Case( FOUND > 0 ; ¶ & ListPosition( NewList; SearchValue ))

Will only be true when it finds an item on the list.
 
Patrick   Patrick, Utah
Mar 25, 2015
Maybe I didn't make it clear enough. When I'm creating the custom function, It stops and says this function could not be found and highlights what I just showed.
 
Joseph Arzate   Joseph Arzate, Los Angeles, CA
Mar 26, 2015
Got it... I think your problem is that you have not renamed the custom function to "ListPosition" - by default FM will name the new function as "New Function".
 
John Davis   John Davis, Houston
Jun 30, 2015
Kudos for your well coded function. Most custom functions like this (finding values in a list) fail because of the "double sequential entry" bug, when more than one of the same value is in the list sequentially.

For example, using functions like Position, PatternCount, etc. to search for ( ¶ & "Charlie" & ¶ ) in the value list "Alpha¶Bravo¶Charlie¶Charlie¶Charlie¶Charlie's Angels¶Echo" will give incorrect results, because for example, the ¶ after the first "Charlie" has already been evaluated, so that the 2nd "Charlie" ( ¶ & "Charlie" & ¶ ) is not found.

I've always modified the value list by substituting "¶¶" for every ¶, which will then give accurate results. But I like how you replace each found result with a RemovedValue before searching for the next result.
 
John Davis   John Davis, Houston
Jun 30, 2015
Made some speed optimizations to the code.

According to this article -
http://filemakerhacks.com/2014/12/08/fm-13-anti-deduping-part-2/
- FilterValues is slow, PatternCount is faster, but Position is the fastest, since it doesn't have to evaluate the entire list, as it only finds the first match.

His test of the three functions on a 10K found set in a local file was:
FilterValues: 21 seconds
PatternCount: 10 seconds
Position: 6 seconds

Now considering this custom function, the FilterValues function in the EXISTS variable is really unnecessary, since there is already a POS variable which finds the position of the first occurrence anyway. No reason to re-evaluate the list with FilterValues at every recursion!

The PatternAccount function in the FOUND variable is also unnecessary, as all we need to know is if at least one occurrence of the SearchValue exists, so the Position function will do fine here, too.

So, here are the changes to make to this function to optimize it for speed:

1) Move the POS variable line directly above the EXISTS variable line.

2) Change the code for the EXISTS variable to:

EXISTS = GetAsBoolean ( POS ) ; //Check to see if the search value exists.

3) Change the code for the FOUND variable to:

FOUND = Position ( ¶ & NewList & ¶ ; ¶ & SearchValue & ¶ ; 1 ; 1 ) ] ; //Checks to see if the SearchValue exists in the newly constructed list. Used to determine if the function should exit or continue to process.
 
Edgar Ramirez   Edgar Ramirez, Veramar
Nov 2, 2016
This test below returns the wrong results
ListPosition ( "¶¶a¶b¶¶c¶d¶e¶¶f¶d¶" ; "d")
returns "6¶9"
when the expected result is "7¶11"
 
Eric   Eric, SJSU
Sep 13, 2018
There are enough situations where I want the value after the one I'm searching for (and I'm too lazy to post process the returned value numbers), that I added a "valueShift" as a parameter.

Thus:
Function: ListPosition ( ListValues; SearchValue; valueShift)

[ListValues] - Expecting a "¶" delimited list of values. List submitted should not begin with MORE than 1 ¶ or the function will be inaccurate.
[SearchValue] - The value being searched upon in the List list provided. Any ¶ found in the SearchValue are removed.
[valueShift] - Shift the value positions returned by the number valueShift. —eric
...
Case
(
exists = 0 ; 0; //Value not Found Exit.
LineNo + valueShift & Case( FOUND > 0 ; ¶ & ListPosition( NewList; SearchValue; valueShift ) ) //Value found and 'SearchValue' still on the list. Continue to evaluate list. Recursion will only occur if the value exists in the newly created list.
) ...
 

Log in to post comments.

 

Note: these functions are not guaranteed or supported by BrianDunning.com. Please contact the individual developer with any questions or problems.

Under construction. Email me your wish list for improvements.