16

Consider the following XML data structure:

<MediaItems>
    <item url="media/somefolder/pic1.jpg" id="1">
        <groups>
            <group>1</group>
            <group>2</group>
        </groups>
    </item>
    <item url="media/somefolder/pic2.jpg" id="2">
        <groups>
            <group>3</group>
            <group>7</group>
        </groups>
    </item>
</MediaItems>

Since my XML data structure/file can potentially scale to 10000 or perhaps 100000+ media item elements, I need to be able to access the individual items, in the parsed Go map (or what structure to use here?), like we do with map[key]type - but I need to be able to use either the url or the id as a key, and I can't figure out how to create a map with 2 keys pointing to the same value.

From the parsed XML data structure above, I need to parse it in Go and store it in a type like:

map[string, string]MediaItem

Where the keys should be url and id, so I'd be able to get the item with id 1 doing myMap["1"] or myMap["media/somefolder/pic1.jpg"]. Both should return the corresponding MediaItem.)

I can't wrap my head around how to implement this, or maybe there a better way to achieve the same?

3 Answers 3

23

Better solution would be to use struct with two field as a key:

type key struct {
    url string
    id  int
}

m := make(map[key]MediaItem)
m[key{url: "http://...", id: 2}] = MediaItem{}
Sign up to request clarification or add additional context in comments.

1 Comment

Note that this is when you want to use both keys at the same to find a value and not when you want to be able to find the value using either one of the keys.
13

Staying with the map type, you can use 2 (3) different solutions:

With 2 maps

Easiest would be to build 2 maps, 1 where the keys are the urls, and 1 where the keys are the ids:

var byUrlMap map[string]*MediaItem
var byIdMap map[string]*MediaItem

Note that you should store pointers instead of structs for example to avoid duplicating the values.

If you need a MediaItem by id:

mi := byIdMap[id]

Similarly by url:

mi2 := byUrlMap[url]

With key prefixes

Another option can be to prefix the actual key values, but this is not so efficient, but as a result, you'll have only one map.

For example you could prefix URL keys with "url:" and ids with "id:" and store the same pointer value of course for both the url and id keys, for example:

var miMap = make(map[string]*MediaItem)

mi := &MediaItem{}
miMap["url:http://something"] = mi
miMap["id:1"] = mi

And getting an element:

mi2 := miMap["id:" + id]   // By id
mi3 := miMap["url:" + url] // By url

Using keys "as-is"

This is something similar to "With key prefixes": if you have guarantee that the URLs and ids will never be the same (meaning you will never have an id that is the same as another item's url and vice versa), you can simply use both keys without prefixes, and set the same value (pointer) to them.

The purpose of key prefixes was to make sure a final url key will never be the same as a final id key (achieved by using different prefixes for these 2 types of keys). But if this condition is naturally true (e.g. the string value of a numeric id will never be a valid url), we don't really need the prefixes:

var miMap = make(map[string]*MediaItem)

mi := &MediaItem{}
miMap["http://something"] = mi
miMap["1"] = mi

And getting an element:

mi2 := miMap[id]  // By id
mi3 := miMap[url] // By url

3 Comments

Thanks icza for your great answer. I like your last proposal "using keys as-is", but turning the above XML data structure proved much harder than I'd ever have imagined. Unmarshalling nested elements to an array is easy, but do you have any idea how I get it unmarshalled into the desired map[string]*MediaItem with the correct keys? This is where I am now: play.golang.org/p/ZJQsTzBO-l
@Dac0d3r Unmarshal the XML into an array (slice), and then loop over the slice and build the map. You do not have to unmarshal directly into the final map.
Thanks man. I found just this play.golang.org/p/eA4_caHLyk , I guess it's also what you just propose. :)
0

You can use the go module: https://github.com/aeimer/go-multikeymap

With this you can store the data like:

# create map
bm := bikeymap.New[string, string, MediaItem]()

# store item
bm.Put("1", "media/somefolder/pic1.jpg", <You MediaItem here>)

# retrieve
itemByA := bm.GetByKeyA("1")
itemByB := bm.GetByKeyB("media/somefolder/pic1.jpg")

Disclaimer: I'm the maintainer.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.