Syntax for map key removal

807 views
Skip to first unread message

snilsson

unread,
Dec 19, 2010, 1:29:06 PM12/19/10
to golan...@googlegroups.com
The go syntax for removing a key from a map is somewhat awkward:

m[key] = possiblyBigAndComplexType{}, boolExpr
 
To remove a value you need to conjure up a another value of the same type; a value that is never used.

Should the composite literal above create a new value even when boolExpr evaluates to false at compile-time? Doing so would be wasteful. Not doing so would introduce a new meaning to composite literals.

The ability to conditionally add or remove a key isn't all that useful and is easily achieved with an if statement.

The syntax is similar to that for putting an element into a map. This strikes me as strange; to remove an element from a collection you get it out, you don't put it out. (Unless it's a fire or a flame war.)

If you think about removal as a special case of getting an element out, you end up with something like this:

 v, ok = m[key,*]
 
The value of ok is true if the key was present and the value of v is m[key]. (The asterisk is just a placeholder, not a serious suggestion.)

Instead of

m[key] = possiblyBigAndComplexType{}, false
you get
_ = m[key,*]

and

if v, ok := m[key]; ok {
m[key] = possiblyBigAndComplexType{}, false
fmt.Printf("Removed %v\n", v)
}

now reads

if v, ok := m[key,*]; ok {
fmt.Printf("Removed %v\n", n)
}

What's your thoughts on this?

Ostsol

unread,
Dec 19, 2010, 3:59:30 PM12/19/10
to golang-nuts
I think that if I'm going to be performing frequent removals from a
map and if the value type name is large, I'll just create a method to
do it:

type CrazyMap map[int]CrazyLongTypeNameFromHell
func (c CrazyMap) Remove(k int) { c[k] = CrazyLongTypeNameFromHell{},
false }

-Daniel

Ryanne Dolan

unread,
Dec 19, 2010, 4:25:03 PM12/19/10
to golan...@googlegroups.com
I don't like the current syntax either.  It's one of those idioms that I find myself commenting all the time: "// remove item from map".  It isn't intuitively readable by anyone not intimate with the language.

We have several built-in functions already.  Why not:

ok := remove (m, key)

Is that so very different from len, append, copy, etc?

The current syntax, to me, sounds like someone was overly eager to apply the multiple-return-values feature.

Thanks.
Ryanne

--
www.ryannedolan.info

peterGo

unread,
Dec 19, 2010, 5:24:15 PM12/19/10
to golang-nuts
snilsson,

_ as value when deleting from a map
http://groups.google.com/group/golang-nuts/browse_thread/thread/55758d25014282db/

Peter

Mick Killianey

unread,
Dec 20, 2010, 2:56:48 PM12/20/10
to golang-nuts
+1 to the request for some kind of remove syntax that doesn't ask for
an object.

While the subject is map removal, another thing I'd like to get from
back from this operation is either a bool or an os.Error that tells me
whether or not the remove was successful. Right now, I have to do
*three* map accesses to remove anything that's reference counted:

if _, present := m[k]; present {
m[k] = dummy, false
if _, removeFailed := m[k]; !removeFailed {
decrementRefCount()
}
}

It would be great if something like remove(m, k) was adopted that
could return something that indicated whether or not the key was
present. It's too bad this couldn't be combined into one statement:

old_value, was_present := m[k] := new_value, will_be_present

From the other thread, re: the assumption that map removal is
rare...isn't a map the natural data structure for implementing any
kind of cache, where there will (eventually) be just as many
insertions as removals?

Mick


On Dec 19, 8:59 pm, Ostsol <ost...@gmail.com> wrote:
> I think that if I'm going to be performing frequent removals from a
> map and if the value type name is large, I'll just create a method to
> do it:
>
> type CrazyMap map[int]CrazyLongTypeNameFromHell
> func (c CrazyMap) Remove(k int) { c[k] = CrazyLongTypeNameFromHell{},
> false }

A minor point: this makes the (not-always-true) assumption that you
can construct the object. I may have a struct from another package
that I can't construct because of hidden fields:

package foo
type Record struct { hidden string }
func New() {
return &Foo{hidden: NastyCodeThatDoesUnwantedWork()}
}

So that the only way to remove an entry from a map[int]foo.Record
(without invoking the nasty constructor) is to do *two* map
accesses...lookup first, then remove, like:
if v, ok := m[k]; ok {
m[k] := v, false
}

It's not fatal, but it is sure is awkward.

chris dollin

unread,
Dec 20, 2010, 3:13:56 PM12/20/10
to Mick Killianey, golang-nuts
On 20 December 2010 19:56, Mick Killianey <mickey.k...@gmail.com> wrote:
> +1 to the request for some kind of remove syntax that doesn't ask for
> an object.
>
> While the subject is map removal, another thing I'd like to get from
> back from this operation is either a bool or an os.Error that tells me
> whether or not the remove was successful.   Right now, I have to do
> *three* map accesses to remove anything that's reference counted:
>
>    if _, present := m[k]; present {
>         m[k] = dummy, false
>         if _, removeFailed := m[k]; !removeFailed {
>             decrementRefCount()
>         }
>    }
>

I don't understand. When might removeFailed be true (for a Go map
as they is currently spoke)?

Chris

--
Chris "allusive" Dollin

Jessta

unread,
Dec 20, 2010, 5:01:06 PM12/20/10
to Mick Killianey, golang-nuts
On Tue, Dec 21, 2010 at 6:56 AM, Mick Killianey
<mickey.k...@gmail.com> wrote:
> A minor point: this makes the (not-always-true) assumption that you
> can construct the object.  I may have a struct from another package
> that I can't construct because of hidden fields:
>
>  package foo
>  type Record struct { hidden string }
>  func New() {
>     return &Foo{hidden: NastyCodeThatDoesUnwantedWork()}
>  }

Here you are returning a *foo.Record.
You can't actually return a struct that has private fields, if you
want code outside your package to be able to use it.

> So that the only way to remove an entry from a map[int]foo.Record
> (without invoking the nasty constructor) is to do *two* map
> accesses...lookup first, then remove, like:
>     if v, ok := m[k]; ok {
>        m[k] := v, false
>     }

Since this is actually a map[int]*foo.Record, you can just do:
m[k] = nil,false


- jessta

--
=====================
http://jessta.id.au

Mick Killianey

unread,
Dec 21, 2010, 9:27:32 AM12/21/10
to golang-nuts
That'll teach me to write tricky code on my mobile phone, away from a
compiler...being stuck at airports makes you do silly things. ;-)

Jessta, I completely agree that you can use nil to remove if it was a
map[int]*StructWithHiddenFields (value type is a pointer), but I was
deliberately trying to create a map[int]StructWithHiddenFields (value
type is a struct)

I was trying to point out that you *can* use the latter kind of map
outside of the struct's package in the limited sense that you can send
it to and receive it from functions, print the map's contents, and get
the map's length, but you can't remove from it using nil.

However, I totally missed the fact that you *also* can't lookup from
it, so the double-access code I wrote doesn't work. In fact, you
can't insert into the map or iterate over its entries, either, so yup,
you're right that it's not really usable as a map (beyond printing and
getting length) and Daniel's right that you'd have to provide a method
in the package that declares the map.

On Dec 20, 10:01 pm, Jessta <jes...@jessta.id.au> wrote:
> On Tue, Dec 21, 2010 at 6:56 AM, Mick Killianey
>

Mick Killianey

unread,
Dec 21, 2010, 10:44:07 AM12/21/10
to golang-nuts
On Dec 20, 8:13 pm, chris dollin <ehog.he...@googlemail.com> wrote:
> On 20 December 2010 19:56, Mick Killianey <mickey.killia...@gmail.com> wrote:
>
> > +1 to the request for some kind of remove syntax that doesn't ask for
> > an object.
>
> > While the subject is map removal, another thing I'd like to get from
> > back from this operation is either a bool or an os.Error that tells me
> > whether or not the remove was successful.   Right now, I have to do
> > *three* map accesses to remove anything that's reference counted:
>
> >    if _, present := m[k]; present {
> >         m[k] = dummy, false
> >         if _, removeFailed := m[k]; !removeFailed {
> >             decrementRefCount()
> >         }
> >    }
>
> I don't understand. When might removeFailed be true (for a Go map
> as they is currently spoke)?

Good question...I don't know of any, now, but I thought that immutable
maps were on the roadmap. If that's true, won't there have to be some
condition under which it fails?

If it can't fail, you're totally right, it's only a double-lookup.
Still, in some other languages, that would be costly...are lookups
that cheap in Go, or is the compiler is able to optimize these in
practice, so that the cost of the double-lookup is insignificant?

chris dollin

unread,
Dec 21, 2010, 12:44:10 PM12/21/10
to Mick Killianey, golang-nuts
On 21 December 2010 15:44, Mick Killianey <mickey.k...@gmail.com> wrote:

> If it can't fail, you're totally right, it's only a double-lookup.
> Still, in some other languages, that would be costly...are lookups
> that cheap in Go, or is the compiler is able to optimize these in
> practice, so that the cost of the double-lookup is insignificant?

Were I writing map lookup, I'd consider remembering the
last key used and where the value was, so that an immediate
relookup could get away with an equality check. But that's
probably only wise for small keys (the last times I wrote map
code, the keys were pointers-to-objects and one forced
comparision a pretty low overhead).

André Moraes

unread,
Dec 21, 2010, 3:12:15 PM12/21/10
to golang-nuts
Maybe this solve the problem.

package main

import ("fmt")

func main() {

var mp map[string]string
mp = make(map[string]string)
mp["teste"] = "teste"

var pmp *map[string]string
pmp = &mp

// initial map
key, has := (*pmp)["teste"]

if has {
fmt.Printf("%s in map\n",key)
} else {
fmt.Printf("%s not in map\n",key)
}

// purged map
// purge a map is just point the pointer
// to a new map.
// gc will collect the unused memory
var mp2 map[string]string
mp2 = make(map[string]string)
pmp = &mp2
key, has = (*pmp)["teste"]

if has {
fmt.Printf("%s in map\n", key)
} else {
fmt.Printf("%s not map\n",key)
}
}

--
André Moraes
http://andredevchannel.blogspot.com/

chris dollin

unread,
Dec 21, 2010, 3:36:39 PM12/21/10
to André Moraes, golang-nuts
2010/12/21 André Moraes <and...@gmail.com>:

> Maybe this solve the problem.

As I daid elsewhere, only if you /consistently/ use a pointer-to-map
for maps you (might) want to purge.

Since maps are reference types already, this seems ...
redundant.

Chris

--
Chris "allusive" Dollin

yy

unread,
Dec 21, 2010, 4:15:23 PM12/21/10
to chris dollin, André Moraes, golang-nuts
2010/12/21 chris dollin <ehog....@googlemail.com>:

> 2010/12/21 André Moraes <and...@gmail.com>:
>> Maybe this solve the problem.
>
> As I daid elsewhere, only if you /consistently/ use a pointer-to-map
> for maps you (might) want to purge.
>

I don't think so. This other program has a similar result to André's example:

package main

import ("fmt")

func main() {
var mp map[string]string

// initial map


mp = make(map[string]string)
mp["teste"] = "teste"

key, has := mp["teste"]


if has {
fmt.Printf("%s in map\n",key)
} else {
fmt.Printf("%s not in map\n",key)
}

// purged map


mp = make(map[string]string)

key, has = mp["teste"]


if has {
fmt.Printf("%s in map\n", key)
} else {
fmt.Printf("%s not map\n",key)
}
}


--
- yiyus || JGL . 4l77.com

Steven

unread,
Dec 21, 2010, 4:48:06 PM12/21/10
to yy, chris dollin, André Moraes, golang-nuts
Have you read the replies for when that was originally posted? They were quite clear. This only works if you're only storing the map locally. Since maps are a reference type, its perfectly reasonable to give the map to another part of the program to modify. If you want to clear a map given to you from an external source, you have to do a whole lot more leg work to have the change impact all non-local copies. Showing that creating a new map works locally is a non-starter. You need to take a wider perspective.

chris dollin

unread,
Dec 22, 2010, 1:00:18 AM12/22/10
to yy, André Moraes, golang-nuts

If you had passed mp to another function, which had stored
it somewhere it was used from, all the reassigning in the world
to mp would make no different to that saved map.

André Moraes

unread,
Dec 22, 2010, 6:01:58 AM12/22/10
to golang-nuts
What about this:

package main

import "fmt"

func main() {
var mp map[string]string

mp = make(map[string]string)
mp["teste"] = "teste"

// purge the map using it's own values.
for k,v := range(mp) {
mp[k] = v, false
}
v, has := mp["teste"]
if has {
fmt.Printf(v)
} else {
fmt.Printf("not in")
}
}

In this case, I remove the values within the for loop, and don't need
to create a new value since I pick only the value that is already in
the map.
The map is completely purged.

I know that usually is a bad thing to change a collection while you
are in a iteration but in this case it works without any problem
(tested on golang.org browser compiler).

chris dollin

unread,
Dec 22, 2010, 6:40:07 AM12/22/10
to André Moraes, golang-nuts
2010/12/22 André Moraes <and...@gmail.com>:

> I know that usually is a bad thing to change a collection while you
> are in a iteration but in this case it works without any problem
> (tested on golang.org browser compiler).

I'd rather the spec was specific about it working rather than
relying on a small test like that. (I don't read the spec as being
explicit enough on this point, but that may be because I'm used
to specs where less is taken for granted.)

André Moraes

unread,
Dec 22, 2010, 6:46:55 AM12/22/10
to golang-nuts
To make things "granted"

Copy the key and values to slices with the size of the map.
Iterate over the slice and remove the keys from the map.

Isn't elegant, but solves the problem for now (until somebody make a
purge built-in or says that removing while iterating is valid).

roger peppe

unread,
Dec 22, 2010, 6:47:47 AM12/22/10
to chris dollin, André Moraes, golang-nuts
2010/12/22 chris dollin <ehog....@googlemail.com>:

i think this is explicit enough:
"The iteration order over maps is not specified. If map entries that
have not yet been reached are deleted during iteration, the
corresponding iteration values will not be produced. If map entries
are inserted during iteration, the behavior is
implementation-dependent, but the iteration values for each entry will
be produced at most once."

if it was invalid to alter a map during iteration, then the spec would
need to say so,
but it does not. in fact, it explicitly talks about altering the map
during iteration,
which would seem to bless the idiom posted by Andre.

André Moraes

unread,
Dec 22, 2010, 6:51:43 AM12/22/10
to golang-nuts
Looks like inserting is a problem, but removing is not.

Since the value is removed after reading looks like it's OK to do that.

chris dollin

unread,
Dec 22, 2010, 7:09:13 AM12/22/10
to roger peppe, André Moraes, golang-nuts
On 22 December 2010 11:47, roger peppe <rogp...@gmail.com> wrote:
> 2010/12/22 chris dollin <ehog....@googlemail.com>:
>> 2010/12/22 André Moraes <and...@gmail.com>:
>>
>>> I know that usually is a bad thing to change a collection while you
>>> are in a iteration but in this case it works without any problem
>>> (tested on golang.org browser compiler).
>>
>> I'd rather the spec was specific about it working rather than
>> relying on a small test like that. (I don't read the spec as being
>> explicit enough on this point, but that may be because I'm used
>> to specs where less is taken for granted.)
>
> i think this is explicit enough:
> "The iteration order over maps is not specified. If map entries that
> have not yet been reached are deleted during iteration, the
> corresponding iteration values will not be produced. If map entries
> are inserted during iteration, the behavior is
> implementation-dependent, but the iteration values for each entry will
> be produced at most once."

Yes, I read that.

> if it was invalid to alter a map during iteration, then the spec would
> need to say so,
> but it does not. in fact, it explicitly talks about altering the map
> during iteration,
> which would seem to bless the idiom posted by Andre.

Well, I'd like to think so too. And it's maybe just the effect of the
Java DANGER WILL ROBINSON specifications for data structures
like maps, but I'd like to see something more explicit in the way of
comfort. Something like:

The iteration order over maps is not specified. It is allowed to add
and remove elements from the map during the iteration. Deleting
an unproduced element means it will not be produced. Newly inserted
elements will be produced at the whim of the implementation, but
at most once.

Reply all
Reply to author
Forward
0 new messages