No element is created in map for assigned key

Just starting out? Need help? Post your questions and find answers here.
Mistrel
Addict
Addict
Posts: 3415
Joined: Sat Jun 30, 2007 8:04 pm

No element is created in map for assigned key

Post by Mistrel »

This has a couple of examples so I'm going to start off with this header so that I'm not duplicating it every time:

Code: Select all

Structure SomeStruct
  a.l
EndStructure

NewMap testMap.SomeStruct()
We can add an empty element to a map like this:

Code: Select all

AddMapElement(testMap(),"1")

Debug "Map size: "+Str(MapSize(testMap()))
and like this:

Code: Select all

testMap("1")

Debug "Map size: "+Str(MapSize(testMap()))
Note that in both of these examples the map size is "1", as expected.

Because this is an array of structures, and elements are allocated for us, we can expect that the pointer to the map element is a pointer to the structure. We can prove this:

Code: Select all

Structure SomeStruct
  a.l
EndStructure

NewMap testMap.SomeStruct()

testMap("1")
*element.SomeStruct=testMap("1")
*element\a=1234

Debug "Map size: "+Str(MapSize(testMap()))

Debug PeekI(*element)
This outputs the expected value of "1234".

Allocating a structure just for the sake of copying it into an identical structure in the map is wasteful and pointless. There should be no reason to do this:

Code: Select all

b.SomeStruct

b\a=1234

testMap("1")=b

Debug "Map size: "+Str(MapSize(testMap()))
So what's the problem?

Notice that in my earlier example I had to assign and acquire the pointer to the element in two lines:

Code: Select all

testMap("1")
*element.SomeStruct=testMap("1")
This is because if it's done on a single line, while we do get a pointer, the map doesn't actually assign it to an entry in the map:

Code: Select all

*element.SomeStruct=testMap("1")

Debug "Map size: "+Str(MapSize(testMap()))

If Not FindMapElement(testMap(),"1")
  Debug "No entry for 1"
EndIf
Note that the result is a map size of "0" and a failure to find the element by its key.

It can be proven that the result of this assignment is the same as acquiring it by its key:

Code: Select all

testMap("1")
*element.SomeStruct=testMap("1")

Debug "Pointer to map element: "+Str(*element)

*element=testMap("1")

Debug "Pointer to map element: "+Str(*element)
To summarize, I would expect the following to provide a valid pointer (which it appears to already do) but also assign it to the map as well (which is doesn't do for some reason) without having to perform the key lookup twice:

Code: Select all

Structure SomeStruct
  a.l
EndStructure

NewMap testMap.SomeStruct()

;/ Not actually assigned to the map
*a=testMap("key")

;/ Structure was allocated and a pointer returned
Debug *a

;/ Map size is "0"
Debug "Map size: "+Str(MapSize(testMap()))
User avatar
Josh
Addict
Addict
Posts: 1183
Joined: Sat Feb 13, 2010 3:45 pm

Re: No element is created in map for assigned key

Post by Josh »

Where in the help does it say that MyMap("AnyKey") returns a pointer to the element? The help clearly states that you can use this command as an Lhs command:
Help wrote:This function isn't mandatory when dealing with maps, as elements are automatically added when affecting a value to them.
I think Fred mentioned it in the forum often enough that a pointer should only be fetched with @, unless the help says something different.

Use AddMapElement() as provided by Pb and everything will work fine.
Last edited by Josh on Thu Oct 17, 2019 1:55 pm, edited 1 time in total.
sorry for my bad english
User avatar
kenmo
Addict
Addict
Posts: 1967
Joined: Tue Dec 23, 2003 3:54 am

Re: No element is created in map for assigned key

Post by kenmo »

This has been discussed before, although I don't have any specific links.

The explicit way to add a map element is AddMapElement().
The shorthand way is to just to testMap("key") = "value"
BUT that only works if testMap("key") is the variable being assigned, the left hand side of the assingment!

If you just read it, like *a = testMap("key"), it returns 0 if the element does not already exist.
This doubles as a shorthand to check if it already exists, as opposed to explicitly calling FindMapElement().


So, the shorthand way can be described as "return pointer if it exists, or else zero" but you are expecting "create element if it doesn't exist, and return pointer always".

They're two different behaviors, not a bug.


EDIT -- OK, I misread part of your post. I thought it was returning 0.
I see now that *a is returning a non-zero pointer, but the map size remains 0. THAT does look like a bug to me!

EDIT 2 -- See freak's post below. I was giving wrong information here! (It does not return 0 on not found, it returns a "dummy" pointer)
Last edited by kenmo on Thu Oct 17, 2019 8:13 pm, edited 1 time in total.
User avatar
skywalk
Addict
Addict
Posts: 3972
Joined: Wed Dec 23, 2009 10:14 pm
Location: Boston, MA

Re: No element is created in map for assigned key

Post by skywalk »

This has been discussed before. Any "touching" of a map("somerandomkey") will add a null map entry.
I agree MapSize(map()) should be incrementing even for null values.
That is a bug.

Code: Select all

Structure SomeStruct
  a.l
EndStructure
NewMap testMap.SomeStruct()
;testmap("key0")\a = -999
*a=testMap("key")                           ; Map infers assignment on touch
*a=testMap("key1")                          ; Map infers assignment on touch
Debug *a                                    ; Structure was allocated and a pointer returned
Debug "Map size: "+Str(MapSize(testMap()))  ; Map size is "0"
Debug Str(testMap("key")\a) + "~Z"          ; No error
Debug Str(testMap("key1")\a) + "~Z"         ; No error
testMap("key")\a = -1                       ; 
testMap("key1")\a = 1                       ; 
Debug "Map size: "+Str(MapSize(testMap()))  ; Map size is "2"
Debug Str(testMap("key")\a) + "~Z"          ; No error
Debug Str(testMap("key1")\a) + "~Z"         ; No error
The nice thing about standards is there are so many to choose from. ~ Andrew Tanenbaum
freak
PureBasic Team
PureBasic Team
Posts: 5929
Joined: Fri Apr 25, 2003 5:21 pm
Location: Germany

Re: No element is created in map for assigned key

Post by freak »

Reading from a map never creates a new element. This is by design. Reading a data structure should not modify it.

It is especially useful for non-structured maps where you can read them in an expression and simply get back the default value (0 or an empty string) if the element does not exist. If reading a non-existing element always created a new element then you couldn't do this. The same is true for structured elements.
skywalk wrote:This has been discussed before. Any "touching" of a map("somerandomkey") will add a null map entry.
That is not true. Read-only access to a map element that does not exist returns a pointer to a static "dummy"-element. It does not add anything to the map. This is done so you can write expressions like the following without getting an access violation every time the element does not exist:

Code: Select all

testMap("key")\a
Without this internal dummy element, you would get a crash if "key" does not exist in the map. The way it is, you just get back the default value for "a".

The fact that you can get a pointer to this dummy element via the following is more of a side-effect of how access to a structure without specifying an element works in PB in general (it returns the structure pointer). It is not something you should do with maps.

Code: Select all

*a = testMap("key")
If you want to explicitly add an element or find one or get 0 then use AddMapElement() or FindMapElement() respectively. Direct access to map elements via an expression work differently and that is intentional.

Anyway, there is no bug here.
quidquid Latine dictum sit altum videtur
User avatar
kenmo
Addict
Addict
Posts: 1967
Joined: Tue Dec 23, 2003 3:54 am

Re: No element is created in map for assigned key

Post by kenmo »

freak wrote:Read-only access to a map element that does not exist returns a pointer to a static "dummy"-element. It does not add anything to the map. This is done so you can write expressions like the following without getting an access violation every time the element does not exist:

Code: Select all

testMap("key")\a
Interesting! Still, for any serious code I will use FindMapElement() to explicitly check first.
Little John
Addict
Addict
Posts: 4519
Joined: Thu Jun 07, 2007 3:25 pm
Location: Berlin, Germany

Re: No element is created in map for assigned key

Post by Little John »

freak wrote:Reading from a map never creates a new element. This is by design.
Well, in the following code a new map element is created (PB 5.71 LTS (x64) on Windows 10).
Is this actually intended?

Code: Select all

EnableExplicit

NewMap MyMap.s()

If MyMap("hello") = ""      ; <-- adds "hello" to the Map
EndIf   

ForEach MyMap()
   Debug MapKey(MyMap())
Next
User avatar
DoubleDutch
Addict
Addict
Posts: 3219
Joined: Thu Aug 07, 2003 7:01 pm
Location: United Kingdom
Contact:

Re: No element is created in map for assigned key

Post by DoubleDutch »

From what @Freak said above, this looks like a bug.
https://deluxepixel.com <- My Business website
https://reportcomplete.com <- School end of term reports system
User avatar
skywalk
Addict
Addict
Posts: 3972
Joined: Wed Dec 23, 2009 10:14 pm
Location: Boston, MA

Re: No element is created in map for assigned key

Post by skywalk »

freak wrote:
skywalk wrote:This has been discussed before. Any "touching" of a map("somerandomkey") will add a null map entry.
That is not true. Read-only access to a map element that does not exist returns a pointer to a static "dummy"-element. It does not add anything to the map.
My example was only following this thread. The "touch" I was referring to is shown in Little John's example. There is definitely mischief afoot. :?: But, now I know PB intentionally allows calling a mapkey that does not exist without creating an error. It was counterintuitive to me.

Code: Select all

Structure SomeStruct
  a.l
EndStructure
NewMap testMap.SomeStruct()
;testmap("key0")\a = -999
*a=testMap("key")                           ; Map infers assignment on touch
*a=testMap("key1")                          ; Map infers assignment on touch
Debug *a                                    ; Structure was allocated and a pointer returned
Debug "Map size: "+Str(MapSize(testMap()))  ; Map size is "0"
Debug Str(testMap("key")\a) + "~Z"          ; No error
Debug Str(testMap("key1")\a) + "~Z"         ; No error
testMap("key")\a = -1                       ; 
testMap("key1")\a = 1                       ; 
Debug "Map size: "+Str(MapSize(testMap()))  ; Map size is "2"
Debug Str(testMap("key")\a) + "~Z"          ; No error
Debug Str(testMap("key1")\a) + "~Z"         ; No error
NewMap MyMap.s()
If MyMap("hello") = ""                      ; Adds "hello" to MyMap()
  ; And increments MapSize().
EndIf
Debug MyMap("hello2")                       ; Does NOT increment MapSize()
Debug "Map size: "+Str(MapSize(MyMap()))    ; Map size is "1"
ForEach MyMap()
  Debug MapKey(MyMap())
Next
The nice thing about standards is there are so many to choose from. ~ Andrew Tanenbaum
Mistrel
Addict
Addict
Posts: 3415
Joined: Sat Jun 30, 2007 8:04 pm

Re: No element is created in map for assigned key

Post by Mistrel »

freak wrote:Without this internal dummy element, you would get a crash if "key" does not exist in the map. The way it is, you just get back the default value for "a".
I think the result as a default but invalid value is subjective and wrong. The fact that the action I was performing was in error and no warning or error was raised simply pushed the error elsewhere in the code where it was out of context with the actual problem.

I don't see a problem with there being a crash if an element doesn't exist as that's what the debugger is for. There are plenty of other instances where interacting with a map or list in an invalid state will cause a crash. So why is this one considered special?

If you want the map to "never crash" and to also act consistently, then add the item to the list and return a valid pointer and not a dummy one.
#NULL
Addict
Addict
Posts: 1440
Joined: Thu Aug 30, 2007 11:54 pm
Location: right here

Re: No element is created in map for assigned key

Post by #NULL »

This dummy thing is weird. I would rather have a crash.
I guess it's because a string element map returning an integer zero on failure would trouble PBs type system. What will a function do that expects a string, gets passed a string map element, but finally receives 0? The compiler can't handle that.

These also add an element to the map:

Code: Select all

EnableExplicit

NewMap MyMap.s()

Procedure proc(s.s)
EndProcedure

Procedure procPtr(*s.String)
EndProcedure

proc(MyMap("hello1"))
Debug MapSize(MyMap())    ; 1

Trim(MyMap("hello2"))
Debug MapSize(MyMap())    ; 2

If Trim(MyMap("hello3")) = ""
EndIf
Debug MapSize(MyMap())    ; 3

If MyMap("hello4") = ""
EndIf
Debug MapSize(MyMap())    ; 4

procPtr( @ MyMap("hello5"))
Debug MapSize(MyMap())    ; 5


ForEach MyMap()
  Debug MapKey(MyMap())  ; hello1-5s
Next
User avatar
DoubleDutch
Addict
Addict
Posts: 3219
Joined: Thu Aug 07, 2003 7:01 pm
Location: United Kingdom
Contact:

Re: No element is created in map for assigned key

Post by DoubleDutch »

I don't think it should cause a crash (that would now break existing programs) - but it shouldn't create a new element.
https://deluxepixel.com <- My Business website
https://reportcomplete.com <- School end of term reports system
nsstudios
Enthusiast
Enthusiast
Posts: 274
Joined: Wed Aug 28, 2019 1:01 pm
Location: Serbia
Contact:

Re: No element is created in map for assigned key

Post by nsstudios »

I definitely agree that doing map("key") inside if statements or such shouldn't add an element, and furthermore, I'd love to see a way to disallow element creation with the shorthand completely (perhaps with an optional flag), as it can result in unwanted behavior.
We can do EnableExplicit to make sure we don't end up creating new variables by accident, so it would be nice to have a way to prevent creating map elements by accident, too.
Post Reply