Check if 2 arrays have the same elements, but different order

I’m trying to make an Crafting System. In the image you can see the Player Items, and some recipes. This are arrays. What is the most performance way to check if the player has enough items to craft someting, and if he has, what recipe.

And also the Player items array doesn’t mean the Player Inventory. It means what items does the Player added to crafting, so if the Player Items has more than the reuired items.
Example if the Player wants to craft a stone hatchet that requires a stick and a rock, and the player adds also a leaf, the receipe is not valid.

Recipe valid:
Player items:

  1. Rock
  2. Stick

Recipe

  1. Rock
  2. Stick

Recipe invalid(not enough):
Player items:

  1. Rock

Recipe

  1. Rock
  2. Stick

Recipe invalid(to many):
Player items:

  1. Rock
  2. Stick
  3. Leaf

Recipe

  1. Rock
  2. Stick

And also I don’t want a performance heavy impact

Put the two lists of items into a set (HashSet in C#) and compare the sets.

I think you also have to pull the data out into an array, then sort it so that it compares correctly for equality.

1 Like

Thank you!

You can use HashSet<T>.SetEquals(IEnumerable<T>) Method (System.Collections.Generic) | Microsoft Learn to see if the two HashSets contain the same elements.

Thanks. I don’t know what HashSet is yet, but can I convert from array to it and back to array?

Yes, but it’s not quite a straightforward as good old .ToArray(). I believe this difference is because by definition HashSets are unordered (they follow the notion of a mathematical set, an unordered collection). I believe the construct is something like:

String[] stringArray = new String[stringSet.Count];
stringSet.CopyTo(stringArray);

To get it quickly into the Hashset, there’s a constructor that takes an IEnumerable:

https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.hashset-1.-ctor?view=netcore-3.1

If each list only has a single-digit number of items in it, then performance is extremely unlikely to be a serious consideration.

But for what it’s worth, you can probably save a bit of time if you start by comparing the lengths of the two lists. If the lengths are different, then the contents can’t possibly be an exact match. If the lengths are the same, then you do a full check.

To clarify Kurt’s answer about converting from a HashSet back to an array, you can get back an array with the same items, but they won’t necessarily be in the original order.

Also, when we say “convert”, we’re really talking about making a copy. If you create a HashSet from your array, you will still have the original array. There’s no need to convert back unless you added or removed stuff from the HashSet.

Yes, it has a small number of item, between 2-9. So If I shouldn’t be worried about performance impact I can just foreach checking? I mean I would use HashSets, but I should learn and experiment with them before using and I don’t have enough time. I will start using the less performance method for now because I have only 10-20 receipes with max 9 items each and I will use HashSet when I add more.

Another thing to consider is item quantities. Are you going to have recipes that require more than one of a given item? HashSets will not be able to handle that use-case directly. If you do have item quantities you might want to consider a Dictionary, which can keep track of not only which items there are, but how many of each.

If I want a recipe to require more that 1 item I just duplicate it in array, no. Like:

  1. Rock
  2. Stick
  3. Stick

And I also need a way to tell each recipe array what final item result will be crafted with the recipe

It shouldn’t be a naked array. Make it a “Recipe” object, and inside of it you can put what you want:

  • what it is called
  • what it makes
  • what items it requires (the array)
  • any other future requirements could go here too (only available on rainy tuesdays, for instance)
2 Likes

Yep this works with an array, but the hashset solution above will remove the duplicates, so it won’t work for you to compare for recipes with duplicates.

I forgot to say that the array is GameObject[ ], so the stick 1 isn’t the same scene object like the second object.

Like deriving ScriptabeObject, or normal class with [System.Serializable] or maybe a struct?

private static bool CompareArrayElements(IEnumerable<object> arrayOne, IEnumerable<object> arrayTwo, IEnumerable<object> elements) {
    return arrayOne.Contains(elements) && arrayTwo.Contains(elements);
}

Wouldn’t that work?

1 Like

Maybe it works. I’ll try it

@SmushyTaco wouldn’t it just be this? (modified and chopped-down a bit from your code above)

private static bool CompareArrayContents(IEnumerable<object> arrayOne,
                                         IEnumerable<object> arrayTwo)
{
    return arrayOne.Contains(arrayTwo) && arrayTwo.Contains(arrayOne);
}

Efficiencies aside, I think you’re onto something as far as a nice idomatic ways to express what you’re doing, and for looking up a few recipes, the performance should never be a factor.

1 Like

Yeah that’s a better approach

If the player adds 2 sticks from their inventory, are those going to be the same 2 stick objects that appear in the recipe? If not, then how exactly do you determine whether they are “the same”?

They have other name in hierarchy. Sticks are randomly placed in Start() on all map using Instantiate, so first is Stick, second is Stick(Clone), third is Stick(Clone)(1), fourth is Stick(Clone)(2), etc