Chace
Chace

Reputation: 561

Structure data to compare values using Firebase with Swift 3

I want to know if my way of structuring data is the best method or not. The reason I have wondered about this is due to the complex requirements that I need from the data.

I am trying to check if the users list of ingredients matches the recipes list of ingredients and be able to display and count them. For example:

Ingredient count

1/2 ingredients

User has

Dough

User doesn't have

Mozzarella

The data structure for my recipe currently looks like this:

{
  "RecipeData": {
    "-KlKMkBekJ6plrXI5cZV": {
      "name": "Pizza",
      "ingredients": {
        "-KkY8kL_wvxrcqMn_aP_": true,
        "-KkbS1pYW_9l4y4HPuog": true
      }
    }
  }
}

The details of the ingredients i.e. name is taken from a dictionary:

{
  "IngredientData": {
    "ingredients": {
      "-KkbS1pYW_9l4y4HPuog": {
        "name": "Dough"
      },
      "-KkY8kL_wvxrcqMn_aP_": {
        "name": "Mozzarella"
      }
    }
  }
}

Here is how it looks when a user adds their own ingredients:

{
  "users": {
    "Ss5XNpWJ5IfIuYiMqsxTnMgjNrm1": {
      "usersList": {
        "-KlKuq2xjbQcR3ZMsHF1": {
          "name": "Dough"
        }
      }
    }
  }
}

With this data structure I have found it difficult to compare the values of the users list of ingredients with the recipe's list of ingredients.

Update:

I have a Ingredient class that handles the properties inside users ingredients, I also use this to when getting the names of the ingredients inside the recipe:

class Ingredient {

    var name: String?
    var key: String?

    init(from snapshot: FIRDataSnapshot) {
        let snapshotValue = snapshot.value as? [String: Any]

        self.key = snapshot.key
        self.ref = snapshot.ref
        self.name = snapshotValue?["name"] as? String
    }

I also have an IngredientManager which has the array of ingredients inside where I am able to call it anywhere.

I have a Recipe class that handles the properties inside a recipe:

class Recipe {

    var name: String!
    var ingredients = [String]()
    var key: String


    init(from snapshot: FIRDataSnapshot) {

        let snapshotValue = snapshot.value as! [String: Any]

        self.name = snapshotValue["name"] as? String
        self.key = snapshot.key

        recipeIng(snapshot: snapshot.childSnapshot(forPath: "ingredients"))


    }
    // Function to count the number of ingredients inside recipe... this works
    func recipeIng(snapshot: FIRDataSnapshot) {

        for item in snapshot.children {

            guard let ing = item as? FIRDataSnapshot else { continue }
            // Gets the key and turns it into a string and then appends it to the array.
            guard let value = ing.key as? String else { return }

            ingredients.append(value)
        }
    }


    // Function to match the ingredients.
    func matchedIngredients() -> ([String], [String]) {

        var userHas = [String]()
        var userHasNot = [String]()

        // loops through the users ingredients
        for ingedient in IngredientManager.shared.ingredients {
            // loops through the recipe's ingredients
            for i in ingredients {

                if ingedient.name == r {
                    userHas.append(i)
                } else {
                   userHasNot.append(i)
                }
            }
        }
        return (userHas, userHasNot)
    }
}

I am able to count the number of ingredients inside a recipe but right now I am struggling to compare that with the users list of ingredients. ingredient.name is able to get the name of the users ingredients which is what I'd like to use to compare it with as the key for the users ingredients are completely different to the recipes.

This is how I display it on a label:

ingredientLbl.text = "\(recipe.matchedIngredients().0.count)/\(recipe.ingredients.count)"

Upvotes: 0

Views: 432

Answers (1)

Frank van Puffelen
Frank van Puffelen

Reputation: 598708

Due to the extra lookup, it becomes a bit of a nesting exercise. But if you read through the loops it's actually not all that difficult. In JavaScript this does the trick:

ref.child("users").once("value").then((usersSnapshot) => {
  usersSnapshot.forEach((userSnapshot) => {
    ref.child("RecipeData").once("value").then((recipesSnapshot) => {
      recipesSnapshot.forEach((recipeSnapshot) => {
        var userNeeds = [];
        recipeSnapshot.child("ingredients").forEach((recipeIngredientSnapshot) => {
          userNeeds.push(ingredientsData[recipeIngredientSnapshot.key].name); // assume the user needs this
          var recipeIngredientName = ingredientsData[recipeIngredientSnapshot.key];
          userSnapshot.child("usersList").forEach((userIngredientSnapshot) => {
            var index = userNeeds.indexOf(userIngredientSnapshot.val().name)
            if (index >= 0) {
              userNeeds.splice(index, 1);
            }
          });
          console.log("User "+userSnapshot.key+" "+(userNeeds.length?"cannot":"can")+" make "+recipeSnapshot.key+": missing ingredients "+userNeeds);
        });
      });
    });
  });
});

A working jsbin: https://jsbin.com/ziqobow/edit?js,console

The key that I do differently here is that I first assume that the user doesn't have each necessary ingredient, and then remove that assumption if it turns out they do already have the ingredient.

Upvotes: 0

Related Questions