Seaching composed objects in collections more quickly than O(n)

Hi. I’m trying to improve the solution to this problem;

Every instance class B has an instance of class A.

I have a collection of Class B.

What ways can I search Class A’s properties at better than O(n)?

Real world example

An item class:

using UnityEngine;

public class Item
{
    public string Id { get; set; }
}

Every ItemSlot contains an Item or null.

using UnityEngine;

public class ItemSlot
{
    public Item Item { get; set; }
}

ItemStore has a collection of many ItemSlot.

using UnityEngine;

public class ItemStore : MonoBehaviour
{
//This collection can be anything, I don't mind
    public List<ItemSlot> ItemSlots { get; set; }
 
}

The problem
I need to query these often, and they may contain many items as I’m keeping track of lots of these ItemStores. Here’s the O(n) way, just a bunch of foreach.

using UnityEngine;

public class ItemStore : MonoBehaviour
{
    public List<ItemSlot> ItemSlots { get; set; }
}

public Item FindItem(string itemId)
{
    Item foundItem = null;

    foreach (var slot in ItemSlots)
    {
        if (slot.Item.Id == itemId)
        {
            foundItem = slot.Item;
            break;
        }
    }

    return foundItem;
}

public Item FindItem(Item item)
{
    Item foundItem = null;

    foreach (var slot in ItemSlots)
    {
        if (slot.Item == item)
        {
            foundItem = slot.Item;
            break;
        }
    }

    return foundItem;
}

The ideal solution
I was hoping for some way to make this entire nested structure searchable at O(1) like Hashset.Contains(), but I don’t know if it’s possible. A custom data structure of some kind? I think that’s beyond me, but if it can be done, I will learn to do it if I can know what sort of terminology I should be reading about and if it’s possble.

The not quite solutions

  1. My first solution, the obvious is just foreach through. O(n) and fine for a few ItemStore. Starts to add up quickly. Want to go faster if possible.

  2. My current solution is that every time an Item is updated, I take the item that was updated and add to a separate ItemMetrics class that stores the itemId and count in a Dictionary<string, int>. Now I can just query the dictionary in the metrics class instead of the ItemStore directly.

Drawback 1, if I miss an item being updated and don’t add it to the ItemMetrics dictionary to keep a perfect count, the ItemMetrics and ItemStore become out of sync. Keeping the Metrics and actual ItemStore synchronized seems unreliable and very easy to accidentally make bugs. ItemStore shifts the items around in to different ItemSlots a in almost all of its functions (there are a lot of places to miss something).

Drawback 2, the dictionary with itemId as key is great for fast search, until I want to search by a different Item property or even the Item reference itself. Now I’m back to the start and if I carry on down this route, I’d need a different copy of the data for every different query. I don’t think this is the right approach.

  1. Almost the same as option 2, but change the List in ItemStore to a dictionary<string, int> with the key being the Id of the item in the slot, thus reducing the search to O(1). Has the same drawbacks as number 2 above though.

I seem to be running in to this nested objects need to be searchable problem often. I would guess there are some solutions that I don’t know to solve it. It seems quite common. That or I’m doing something wrong to keep backing myself in to the same corner :slight_smile:

A pointer in the right direction or any suggestions for something I can try, or look up, or learn would be great.

Jasper

I think you can make a hash (or dict) keyed on a System.Type. If not you can certainly hash on the .ToString() of the typeof() the object and look it up that way.

Another approach is to make some kind of custom centralized registration/deregistration and call that in OnEnable() and OnDisable() for your objects, but again you gotta make sure it happens everywhere.

1 Like

Any data structure that will allow you to search your list faster than O(n) is only going to work on a single “metric” of the items. For example, the ID, or the name of the item, or the amount of damage it does, whatever. If you’re using Dictionaries, for example, you will need one dictionary per metric.

However there is a “hack” around this in that you can choose whatever you want for that single metric. I used to work for a place where we wanted our support team to be able to search for a user based on one of about a dozen different data points from a single search bar. Name, address, id, etc…

The way we did this was for each user, we created a “search string” the search string was pretty much a big blob of all the datas we’re interested in searching. So if the user’s id was “4” and their name was “John Smith” and their phone number was “5555555555”, the search string just looked like this:

“John Smith 5555555555 4 123 Sesame St”.

We then fed this search string into a system which takes the string, tokenizes it (every word is a token), and indexes all of it. So you end up with essentially a dictionary<string, User> with these entries:

“John” - > our user
“Smith” - > our user
“55555555555” - > our user
“4” - > our user
“Sesame” → our user
“123” → our user
“St” → our user

Then you can find that user by typing in any of the data about the user. There are a few caveats to this:

  • It needs to be a multi-dictionary. A better analogy is a Dictionary<string, HashSet>, since multiple users can share tokens (for example, John might live at 123 Sesame st, and Mary might live at 95 Sesame St)
  • Searching by multiple terms returns users who are indexed by ALL of those terms (the search terms are ANDed together)
  • Any time a user is updated you need to reindex them (Same problem as your Drawback 1)

This is essentially a poor-man’s version of Full Text Search: Full-text search - Wikipedia

1 Like

@Kurt-Dekker Thanks for the suggestions, I’ll look in to those, especially the former.

** @PraetorBlue ** Very interesting! I haven’t seen anything like this before. Thanks for sharing this.

Sorry I can’t comment much further, I’m going to try them out first :slight_smile:

Thanks for answering all the questions by the way. I read the forum on my breaks and almost always see you both answering loads of questions. I’ve learned a lot just be reading your posts this last month.

Jasper.

1 Like