Pick random value from firebase snapshot - javascript

I'm using firebase's foreach to get each child in a tree from this url
Objective, when the page loads grab a random item from firebase and show it
data structure
grabbit (table name)
active (for active items for sale)
category (the category of the item ie womensClothes, MensShoes etc)
unique id of the item
On page load go into http://gamerholic.firebase.com/grabbit/active
and grab any one of the categories and return it..
Script
var grabbitRef = new Firebase('https://gamerholic.firebaseIO.com/grabbit/active/');
grabbitRef.on('value', function(snapshot) {
if(snapshot.val() === null) {
alert("invalid");
} else {
// get snap shot data:
snapshot.forEach(function(snapshot) {
var name = snapshot.name();
alert(name);
});
}
});
After I have a random category say "electronics", I get a new snapshot and have it return any random item that's in electronics
var grabbitRef = new Firebase('https://gamerholic.firebaseIO.com/grabbit/active/'+name);
grabbitRef.on('value', function(snapshot) {
if(snapshot.val() === null) {
alert("invalid");
} else {
// get snap shot data:
snapshot.forEach(function(snapshot) {
var id = snapshot.name();
alert(id);
});
}
});
with the id I can now get the details of the item
var grabbitRef = new Firebase('https://gamerholic.firebaseIO.com/grabbit/active/'+name+'/'+id);

It's not possible to grab a random item from the list in Firebase, unfortunately. You can do limit(1) to grab the first item, or endAt().limit(1) to grab the last item.
If you're using forEach, you can also grab all the items like you are and then picking one at random, using Math.random. For example:
var i = 0;
var rand = Math.floor(Math.random() * snapshot.numChildren());
snapshot.forEach(function(snapshot) {
if (i == rand) {
// picked random item, snapshot.val().
}
i++;
});

One way to implement this is to store an additional child attribute with each element of your list that will take on a random value. Let's call the name of this attribute "randomVal".
To pick your random entry you would use orderByChild("randomVal") and then limit the query to returning exactly one entry. Immediately upon fetching this entry, you would write a new random value into the "randomVal" element.
Beyond the additional bookkeeping work, the biggest downside of this approach is that it requires a write to the database every time you want to select a random element. I'm new to firebase, so I don't know how significant this drawback is.
Also, make sure to index this part of the database on "randomVal" to improve the query performance.

Related

Firebase: Atomically pop random element from realtime database

My firebase realtime database begins with a small list of unique elements in arbitrary order. Users can fetch an element(s) from this list, atomically popping it from the database, such that no other user can possess the same element. Users can also return their popped element to the list. In this way, the elements currently held by users, and those left in the database, are conserved. The list is small enough (max 27 elements) that I can efficiently load the entire database contents into memory if needed.
I am struggling to express this behaviour into my web (pure javascript) firebase application. I have seen firebase transactions, but I'm not sure how to use these such that the popped child is selected psuedo-randomly.
Here is an inadequate attempt which violates atomicity (users may end up popping/getting the same element)
function popRandElem() {
// fetch all elements currently in db list
db.ref('list').get().then( (snap) => {
// choose random element
var elems = snap.val();
var keys = Object.keys(elems);
var choice = keys[ keys.length * Math.random() << 0 ];
// remove chosen element from db
db.ref('list').child(choice).remove();
return elems[choice];
}
}
myElem = popRandElem();
function restoreElem() {
db.ref('list').push(myElem);
myElem = null;
}
How can I adapt this example such that popRandElem atomically pops from the database?
This turned out to be straightforward with transactions, using the optional second callback to obtain the successfully popped element.
function popRandElemAsynch() {
var choice = null;
db.ref('list').transaction(
// repeats with updated list until run without collision
function( list ) {
// discard previous repetition choice
choice = null;
// edge-case of list emptied during transac repeats
if (!list)
return list;
// choose and remember a random element
var keys = Object.keys(list);
choice = keys[ keys.length * Math.random() << 0 ];
// remove the element
delete list[choice];
return list;
},
// runs once after above has run for final time
function() {
// choice is the final uniquely popped element
// if it is null, list was emptied before collision-free pop
someFunc(choice);
},
// don't trigger premature events from transaction retries
false
);
}

Filter an array based on another array. (Using React)

The goal is to filter an array based on the slots the user has selected.
For example an array has slots for 7pm-9pm,10pm-12pm and so on.
Now the user selects 7pm-9pm, so now I want to filter the array which have 7ppm-9pm or is the users wants
7pm-9pm and 10pm-11pm so the data should be based on 7pm-9pm and 10pm-11pm
Here is how I store the values
This is the original array
data :[
{
name:"something",
phone:"another",
extraDetails : {
// some more data
slots : [
{item:"6PM-7PM"},
{item:"7PM-8pm}
]
}
},{
// Similarly more array with similar data but somewhere slots might be null
}
]
Now for example we have this array
slots:[{6PM-7PM,9PM-10PM,11PM-12AM}]
Now this should filter all those which includes timeslots of 6PM-7PM,9PM-10PM,11PM-12AM
or if the user selects
slots:[{6PM-7PM}]
We should still get the results that includes 6pm-7pm more or else don't matter.
First, I'd suggest using this for your slots representation for simplicity, but you can alter this approach depending on your actual code:
slots: ['6PM-7PM', '9PM-10PM', '11PM-12PM']
Then you can iterate through your data and use filter:
const querySlots = ['6PM-7PM', '9PM-10PM', '11PM-12PM'];
const matchedPersonsWithSlots = data.filter( (person) => {
let i = 0;
while ( i < person.extraDetails.slots.length ) {
if (querySlots.includes(person.extraDetails.slots[i]) return true;
i += 1;
}
return false;
});
matchedPersonsWithSlots will then have all the people that have a slot that matches one of the slots in your query, because if any of the query slots are in a person's list of slots, then it's included in the result set.
EDIT to include a different use case
If, however, every slot in the query array must be matched, then the filtering has to be done differently, but with even less code.
const matchedPersonsWithAllSlots = data.filter(person =>
querySlots.every((qSlot)=>person.extraDetails.slots.includes(qSlot)));
The above will go through each person in your data, and for each of them, determine whether the person has all of your query slots, and include them in the result list, only if this is true.

Trying to delete a fields array in firestore but my method is not working

So I am trying to delete the field Notes[1], selectedNote has a value of the selected array I need to delete.
window.addEventListener("load", function load(event) {
document.getElementById("deleteNotes").onclick = function() {
console.log("you did click atleast");
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
let user = firebase.auth().currentUser;
let userInfo = db.collection("Users").doc(user.uid);
userInfo.get().then(function(doc) {
if (doc.exists) {
let selectedNote = document.getElementById("noteSelect")
.selectedIndex;
console.log(selectedNote);
var cityRef = db.collection("Users").doc(user.uid);
cityRef.update({
Notes: FieldValue.delete().arrayRemove(selectedNote)
});
}
});
}
});
};
});
So I am trying to use this
cityRef.update({
Notes: FieldValue.delete().arrayRemove(selectedNote)
});
to delete the selectedNote which is the array 1 for example inside of Notes. I don't want the entire Notes field deleted but just the selected array inside the Notes field. For some reason I am struggling to get it working. Any help would be appreciated <3
Firebase arrays doesn't use a indexes for their arrays, instead they require the value of the array entry. this does mean you can't get away with duplicated data.
you will have to fetch the array "Notes" first and return its value from its index
Although, I have heard that the arrays can come back out of order because they don't rely on an index.
change this line;
Notes: FieldValue.delete().arrayRemove(selectedNote)
to this;
Notes: FieldValue.delete().arrayRemove(doc.data().Notes[selectedNote])
I would recommend you pull the value of the array from a stored variable locally to ensure the values match pre and post edit.

Firebase: Does orderByKey return an ordered snapshot

This sounds like a silly question but the docs say:
orderByKey
orderByKey() returns firebase.database.Query
Generates a new Query object ordered by key.
The query object is ordered by key which means i can do:
ref.orderByKey().limitToLast(1)
To get the last in order.
But if I do:
ref.limitToLast(1).on('child_added', function(s)
{
ref.orderByKey().limitToLast(2).once('value').then(function(snapshot)
{
var val = snapshot[Object.keys(snapshot)[0]];
});
});
Will val always be the second last? The docs don't specifically say the snapshot is ordered. Should I just continue to sort it myself to be sure?
Is there a better way to get the second last in order or the last if there is only one every time a child is added? Basically i want the one before the one that was just added.
Thanks!
You have this query:
ref.orderByKey().limitToLast(2)
This query orders the child nodes by key and then returns the last two items.
To access the results in order, use Snapshot.forEach():
ref.orderByKey().limitToLast(2).once('value').then(function(snapshot)
{
snapshot.forEach(function(child) {
console.log(child.val());
});
});
The first time through the loop will give you the second-to-last item, so if you want to capture that:
ref.orderByKey().limitToLast(2).once('value').then(function(snapshot)
{
var isFirst = true;
snapshot.forEach(function(child) {
if (isFirst) {
console.log(child.val());
isFirst = false;
}
});
});

Cant remove duplicate items in collection in VueJS

I am trying to remove duplicate items from a collection that I request via an API in Laravel.
This is my code:
computed: {
// slice the array of data to display
filteredList() {
/* NEW PART */
var tips = this.dublicate;
/* END NEW PART */
tips = this.items.filter(item => {
return item.tip.toLowerCase().includes(this.search.toLowerCase())
})
return tips.slice(0, this.display);
},
dublicate() {
var filtered_array = [];
for(var i =0; i < this.items.length; i++) {
if(this.items[i].tip.toLowerCase() != this.items[i+1].tip.toLowerCase()) {
filtered_array.push(this.items[i])
}
}
return filtered_array;
}
}
}
If I remove the code within the NEW PART comments, everythin works fine.
In the NEW PART I am trying to remove duplicate content, based on the items tip attribute.
If the tip attribute is the same as the next items tip attribute, it should be excluded from the tips array, which is returned as a v-for="tips in filteredList".
However, I just get an empty array with this new part. What am I doing wrong?
I get the following from Vue Devtools:
dublicate:"(error during evaluation)"
filteredList:"(error during evaluation)"
An example data from items, that are from an API request:
(This is the data that I get, when I dont try to remove duplicates, which works)
As this is in VueJS, I cant use the answer provided here.
You are looking past the end of the array with i + 1. You need to push the last item without looking for the one after it (because there isn't one). I think using filter is more straightforward than building an array with a for loop.
dublicate() {
return this.items.filter((a, i) =>
i === this.items.length - 1 ||
a.tip.toLowerCase() !== this.items[i + 1].tip.toLowerCase()
);
}

Categories