Map - Remove All Where Key Matches Pattern

Anyone removed entries from a map, e.g. one built from a json string, using a pattern / condition on the key value?

I've read the JSON below into a map and can't seem to get the removeAll function to work like some online examples explain for Groovy. I want to remove all the "image" entries or all the "background" entries. Any tips?

{
"background01": "http://www.bom.gov.au/products/radar_transparencies/IDR043.background.png",
"background02": "http://www.bom.gov.au/products/radar_transparencies/IDR043.locations.png",
"image1": "http://www.bom.gov.au/radar/IDR043.T.202110292344.png",
"image2": "http://www.bom.gov.au/radar/IDR043.T.202110292349.png",
"image3": "http://www.bom.gov.au/radar/IDR043.T.202110292354.png",
"image4": "http://www.bom.gov.au/radar/IDR043.T.202110292359.png",
"image5": "http://www.bom.gov.au/radar/IDR043.T.202110300004.png",
"image6": "http://www.bom.gov.au/radar/IDR043.T.202110300009.png",
"image7": "http://www.bom.gov.au/radar/IDR043.T.202110300014.png"
}

The second example here may help: groovy removeAll closure not removing elements in list - Stack Overflow

Your closure logic could check:

it.key against some subString matching and/or fixed list of values, depending on whether you know ahead of time what entries you might be looking for.

1 Like

Hmmm... Still a little unsure what you mean, if I want to remove all entries from the Map with a prefix of "image" or a prefix of "background" as the key value, any tips on how to apply the stack overflow example?

Probably worth emphasising this is a map and not a list... Am guessing you / they mean to get the keyset to use in the function...

And thanks for responding btw, sounds a little demanding in my initial response...

Ah, it looks like Map's removeAll was only added in Groovy 2.5, and Hubitat seems to be on 2.4.x.

You could try brute forcing it something like this. Someone else may have a more elegant idea:

def input =
        [
        background01: "http://www.bom.gov.au/products/radar_transparencies/IDR043.background.png",
        background02: "http://www.bom.gov.au/products/radar_transparencies/IDR043.locations.png",
        image1: "http://www.bom.gov.au/radar/IDR043.T.202110292344.png",
        image2: "http://www.bom.gov.au/radar/IDR043.T.202110292349.png",
        image3: "http://www.bom.gov.au/radar/IDR043.T.202110292354.png",
        image4: "http://www.bom.gov.au/radar/IDR043.T.202110292359.png",
        image5: "http://www.bom.gov.au/radar/IDR043.T.202110300004.png",
        image6: "http://www.bom.gov.au/radar/IDR043.T.202110300009.png",
        image7: "http://www.bom.gov.au/radar/IDR043.T.202110300014.png"
        ]
    
    def newKeys = []
    input.keySet().each
    {
        if(it.substring(0, 5) != "image")
        {
            newKeys += it
        }
    }
    
    def output = input.subMap(newKeys)
    
    log.debug output
2 Likes

That's the kind of info I was missing, it's a pain when there's so many instructions available but are not applicable. Thanks @tomw.

1 Like

And I think the keySet().each bit is ultimately what I need....

not sure whether "it" is being resolved....

Never had an issue with it being resolved but another option using the iteration method would be

if(!it.startsWith("image"))
2 Likes

That's what I tried (I thought), but maybe I need to sleep on it and try it with more night time hours behind me....

1 Like

What about

def output = input.findAll {!it.key.startsWith("image")}

Though this of course doesn't remove anything from input. Does that matter for you?
(Removing is also possible, but too annoying to type on a phone :wink: )

2 Likes

That's what I would have thought would work.... Can only hope my attention to detail is not what it should be, and I will correct this during more more appropriate hours later this morning....

I'm thinking I may need to adjust my approach to just get something working, like you point out...

(Fwiw, if you want to remove items from the map you are iterating over, that's a bit of a problem (untill remove all is introduced). So you'll have to resort to cloning up front or so. )

1 Like

The one I posted worked when I tested it. I would do what @thebearmay posted in place of the substring part in my demo, since his is more intuitive (and therefore less error prone).

1 Like

Back home. Just to ("probably" unnecessarily :wink: ) elaborate. Here is a version that removes the items from the original Map instance. (it works as findAll returns a new Map instance)

class TestSnippets extends GroovyTestCase {
    void testMapRemove() {
        def input =
                [
                        background01: "http://www.bom.gov.au/products/radar_transparencies/IDR043.background.png",
                        background02: "http://www.bom.gov.au/products/radar_transparencies/IDR043.locations.png",
                        image1      : "http://www.bom.gov.au/radar/IDR043.T.202110292344.png",
                        image2      : "http://www.bom.gov.au/radar/IDR043.T.202110292349.png",
                        image3      : "http://www.bom.gov.au/radar/IDR043.T.202110292354.png",
                        image4      : "http://www.bom.gov.au/radar/IDR043.T.202110292359.png",
                        image5      : "http://www.bom.gov.au/radar/IDR043.T.202110300004.png",
                        image6      : "http://www.bom.gov.au/radar/IDR043.T.202110300009.png",
                        image7      : "http://www.bom.gov.au/radar/IDR043.T.202110300014.png"
                ]
        input.findAll { !(it.key =~ "^image.*") } each { input.remove it.key }
        //input.retainAll {it.key =~"^image.*"} for later groovy
        assert input.size() == 7
    }
}
3 Likes

Not that the other options don't work, but was able to get the findAll setup working from @jasper 's last post, thanks for that.

EDIT: Also funny that I now look back at the scraping of the shtml file I use as the source of the radar image links.... using a little known function over a map... called findAll().... :blush:

Simon

2 Likes

Download the Hubitat app