1

Looking for a one liner code either in java or cfm, where i do not need to loop over te array of structs to use te structfind to get the value from it.

right now looking at it,

Coldfusion - How to loop through an Array of Structure and print out dynamically all KEY values?

where i can loop over and get the value of the key match

but trying to check if something like this can be done

<cfset myvalue = structfindvaluefromAnything(myarrayofstruct,"infor")>
James A Mohler
  • 11,060
  • 15
  • 46
  • 72
  • It's not obvious to me what you're expecting returned, perhaps you could include sample input data and its expected return value? Are you trying to match on existence of key names, or match on values regardless of key name? If you have an array of 10 structs, and 5 of the structs contain matches, what sort of return format are you hoping for? – Sev Roberts Aug 11 '20 at 18:50
  • if anyone of them has the value of "infor", it should return me, the very first instance find will work, no matter if it is appearing in other or not, if it finds anyone of the value with "infor", my puspose is solved – user13895102 Aug 11 '20 at 18:57
  • So do you want to return the whole of the first struct that contains any key with the value "infor"? Or do you want to return the first value of any key named "infor"? – Sev Roberts Aug 11 '20 at 19:19
  • 1
    One of the cool things about modern ColdFusion is the ability to use closures instead of loops. MUCH faster. – Shawn Aug 13 '20 at 17:06

4 Answers4

2

If you really want to do it in one line then you could use ArrayFilter() in combination with StructFindValue().

Adapting from the Adobe docs for ArrayFilter - https://helpx.adobe.com/coldfusion/cfml-reference/coldfusion-functions/functions-a-b/arrayfilter.html - something like this:

<cfscript>
     superheroes=[
           {"name":"Iron Man","member":"Avengers"},
           {"name":"Wonder Woman","member":"Justice League"},
           {"name":"Hulk","member":"Avengers"},
           {"name":"Thor","member":"Avengers"},
           {"name":"Aquaman","member":"Justice League"}
     ];
 
    avengers=ArrayFilter(superheroes,function(item){ 
        return ArrayLen(StructFindValue( item, "Avengers"));
    });
    writeDump(var=avengers, label="all matches");

    writeDump(var=ArrayLen(avengers) ? avengers[1] : "Not found", label="first match only");

    writeDump(var=structFindValue({"a":superheroes}, "Avengers", "all"), label="without arrayFilter");
</cfscript>
Sev Roberts
  • 1,295
  • 6
  • 7
2

I like Sev's approach. I would change it slightly

<cfscript>
     superheroes=[
           {"name":"Iron Man","member":"Avengers"},
           {"name":"Spider-Man","member":"Avengers"},
           {"name":"Wonder Woman","member":"Justice League"},
           {"name":"Hulk","member":"Avengers"},
           {"name":"Thor","member":"Avengers"},
           {"name":"Aquaman","member":"Justice League"}
     ];
 
     avengers = superheroes.filter(function(item) { 
        return item.member ==  "Avengers";
    });
     writeDump(avengers);
</cfscript>
James A Mohler
  • 11,060
  • 15
  • 46
  • 72
  • 1
    That's effectively the Adobe example which I was lifting from - albeit using `array.filter()` instead of `ArrayFilter()` - however the reason I adapted it is because the OP's question does not imply to me that we know which key name we are searching for the match within - hence the comments requesting clarification. – Sev Roberts Aug 11 '20 at 19:41
  • 1
    If this is Lucee 5, you can also make it a lot shorter and use Arrow Functions. `avengers = superheroes.filter( (item) => ( item.member == pickme ) );` where `pickme` is your filtering value that you pass in. – Shawn Aug 13 '20 at 16:57
  • 1
    @SevRoberts For all intents and purposes, `.filter()` is the same as `ArrayFilter()` when applied to an array. – Shawn Aug 13 '20 at 16:59
  • And also, technically, you don't have to check the length of the array. You can Elvis it. `writeDump(var= avengers[1] ?: "Not found", label="first match only");` – Shawn Aug 13 '20 at 17:03
  • was able to do it, but it matched the value but i need to find the value of the another structure within the same struct if its there oir not – user13895102 Aug 15 '20 at 01:59
  • 1
    Here is the gist i have https://cffiddle.org/app/file?filepath=3e26c1ac-d5db-482f-9bb2-995e6cabe704/49b3e106-8db9-4411-a6d4-10deb3f8cb0e/24e44eba-45ef-4744-a6e6-53395c09a344.cfm – user13895102 Aug 15 '20 at 02:03
  • 1
    Based on your gist, I think I see what you're after. Another answer incoming. – Shawn Aug 17 '20 at 21:04
0

I believe the function available for this nearly exactly what you were hoping for...

StructFindValue(struct, value [, scope])

Searches recursively through a substructure of nested arrays, structures, and other elements for structures with values that match the search key in the value parameter.

Returns an array that contains structures keys whose values match the search key value. If none are found, returns an array of size 0.

Dan Roberts
  • 4,664
  • 3
  • 34
  • 43
  • Note that you have to pass a struct to StructFindValue(); an array of structs like in the question would throw an error. Although I suppose you could stuff the array of structs inside another struct, then traverse down through to the `owner` of the match. – Sev Roberts Aug 11 '20 at 19:33
0

Based on the gist you provided above (https://cffiddle.org/app/file?filepath=3e26c1ac-d5db-482f-9bb2-995e6cabe704/49b3e106-8db9-4411-a6d4-10deb3f8cb0e/24e44eba-45ef-4744-a6e6-53395c09a344.cfm), I think you've clarified your expectations a little bit.

In your gist, you say you want to be able to search an array of structs and find the row that has a "name" key with a value of "form". Then, you want to take the value of the "value" key that's associated with that struct in the array row. If there is no value then return 0.

You wanted to be able to do this in a single line of code, and the above answers do accomplish that. My answer essentially builds on those.

As demonstrated in the earlier answers, you still want to use closure functions to filter down your final output. Those are very quick and essentially built to do what you're trying to do.

The Fiddle that I worked with is here: https://cffiddle.org/app/file?filepath=b3507f1d-6ac2-4900-baed-fb3faf5a3b3a/e526afc2-bb85-4aea-ad0e-dcf38f52b642/75d88d2b-f990-44c1-9d9f-22931bf9d4d7.cfm

I've done two things with this.

First, I worked it as if you expected to encounter multiple records for your filtering value, and then turn those into a comma-delimited list. If you need another structure, the reduce() function in my code can be modified to handle this.

Second, I worked it as if you expected to encounter only one filtered record, returning only a single value.

The first thing I did, which is mostly the same in both methods, and which is essentially the same as the previous answers, is to filter your original array for just the value you want.

This is done like this:

myResult = originalArray.filter( 
    function(itm){ 
        return itm?.name=="form";   /// ?. = safe-navigation operator.
    } 
)

I've broken it to multiple lines for clarity.

This will return a new array of structs consisting of your filtered rows.

But then you want to take those records and return the "value" from those rows (defaulting to 0 if no value. You can do this with a reduce().

commaDelimitedValue = 
    myResult.reduce( 
        function(prev,nxt) { 
            return prev.listappend( ( nxt.value.len() ? nxt.value : 0 ) ) ;
        }
        , ""   /// Initialization value 
    ) ;

Again, this can be written in one row, but I've included line breaks for clarity.

The reduce() function essentially just reduces your input to a single value. It follows the format of .reduce( function( previousValue, nextValue ){ return .... },<initializationValue>), where, on the first iterations, the initializationValue is substituted for previousValue, then previousValue becomes the result of that iteration. nextValue is actually the current iteration that you will derive a result from.

More at: https://coldfusion.adobe.com/2017/10/map-reduce-and-filter-functions-in-coldfusion/

In my assumption here, you could possibly have multiple rows returned from your filter(). You take those rows and append the value to a commma-delimited list. So you would end up with a result like 20,10,0,0 - representing 4 rows in your filtered results.

I also check for a length of the value and default it to 0 if it's an empty string. Above, I said that you could just use an Elvis Operator (:?) on that, but that doesn't work for a simple value like an empty string. Elvis works with NULLs, which the earlier array did have.

To put this back to one line, you can chain both of these functions. So you end up with:

myFinalResult =  
    myOriginalArray.filter( 
        function(itm){ 
            return itm?.name=="form";
        } 
    )
    .reduce(   
        function(prev,nxt) { 
            return prev.listappend( ( nxt.value.trim().len() ? nxt.value : 0 ) ) ;
        }
        , ""
    ) 
;

Again, that code is doing a lot, but it is still essentially one line. The final result from that would again be something like "20,10,0,0" for 4 rows with 2 defaulted to 0.

If you only expect your filter to return a single row, or if you only want a single value, you can simplify that a little bit.

myFinalResult = myOriginalArray.filter( function(itm){ return itm?.name=="fm" && (itm?.value.trim().len()>0) ; } )[1]["value"] ?: 0 ;

With this, I am back to using my previous trick with Elvis to default a row with no value, since I am filtering out the "form" struct with an empty-string "value". && is the same as AND. Technically this CAN filter more than one row from the original array, but the [1] will only pick the first row from the filtered rows. It also doesn't need to use a reduce(). If there's more than one row filtered, each iteration will just overwrite the previous one.

This will return a simple, single value with something like 42 - which is the last filtered value in the array, since it overwrites the previous row's value.

My Fiddle (https://cffiddle.org/app/file?filepath=b3507f1d-6ac2-4900-baed-fb3faf5a3b3a/e526afc2-bb85-4aea-ad0e-dcf38f52b642/75d88d2b-f990-44c1-9d9f-22931bf9d4d7.cfm) has some additional comments, and I set up a couple of edge cases that demonstrate the filtering and safe-navigation.


I would also like to reiterate that if this is Lucee 5+ or ACF2018+, you can shorten this further with Arrow Functions.

Shawn
  • 4,758
  • 1
  • 20
  • 29
  • I'd also caution that it can cause issues if you use some "special" words like `"form"` and `"value"` as variable names or values. Or it can cause confusion, which can be worse sometimes. :-/ – Shawn Aug 17 '20 at 22:03