Set and Get array from cookies with js-cookie and JSON.parse - javascript

I'm using js-cookie to store data and get them back, I'm trying to sore an array variable but I have trouble mantaining its format. This is the process that create, retrive, change and save data of the cookie, it works but just the first time, as I'm not able to
// store array in cookie
Cookies.set('points', '0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0', { expires: 30 });
// get data from cookie into a variable (becomes a string)
points = Cookies.get('points');
// convert to object with (length 12)
points = JSON.parse("[" + points + "]");
// change value of the array in the varable position
points[playerId]++;
// save data in cookie
Cookies.set('points', points, {expires: 30});
This works only the first time, any subsequent time I get error and the array become length 1. I'm sure it's because I'm missing squarebrackets but if I try:
Cookies.set('points', '[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]', { expires: 30 });
the variable becomes an object with lenght 1 and it does not work.

The reason it fails the second time is that you pass to Cookies.set an array as second argument, making assumptions that this will end up as a comma separated string in the cookie. But js-cookie will in that case do the conversion to string by adding the square brackets.
So the very quick solution is to change this:
Cookies.set('points', points, {expires: 30});
to:
Cookies.set('points', points.toString(), {expires: 30});
However, it is better to encode and decode with JSON.stringify and JSON.parse without doing any string manipulation "yourself", like this:
var points = Array(12).fill(0);
Cookies.set('points', JSON.stringify(points), { expires: 30 });
var points = JSON.parse(Cookies.get('points'));
points[0]++;
Cookies.set('points', JSON.stringify(points), {expires: 30});
// ...etc

Related

What is the best way to generate a random number based on 3 different probabilities [duplicate]

I would like to randomly select one element from an array, but each element has a known probability of selection.
All chances together (within the array) sums to 1.
What algorithm would you suggest as the fastest and most suitable for huge calculations?
Example:
id => chance
array[
0 => 0.8
1 => 0.2
]
for this pseudocode, the algorithm in question should on multiple calls statistically return four elements on id 0 for one element on id 1.
Compute the discrete cumulative density function (CDF) of your list -- or in simple terms the array of cumulative sums of the weights. Then generate a random number in the range between 0 and the sum of all weights (might be 1 in your case), do a binary search to find this random number in your discrete CDF array and get the value corresponding to this entry -- this is your weighted random number.
The algorithm is straight forward
rand_no = rand(0,1)
for each element in array
if(rand_num < element.probablity)
select and break
rand_num = rand_num - element.probability
I have found this article to be the most useful at understanding this problem fully.
This stackoverflow question may also be what you're looking for.
I believe the optimal solution is to use the Alias Method (wikipedia).
It requires O(n) time to initialize, O(1) time to make a selection, and O(n) memory.
Here is the algorithm for generating the result of rolling a weighted n-sided die (from here it is trivial to select an element from a length-n array) as take from this article.
The author assumes you have functions for rolling a fair die (floor(random() * n)) and flipping a biased coin (random() < p).
Algorithm: Vose's Alias Method
Initialization:
Create arrays Alias and Prob, each of size n.
Create two worklists, Small and Large.
Multiply each probability by n.
For each scaled probability pi:
If pi < 1, add i to Small.
Otherwise (pi ≥ 1), add i to Large.
While Small and Large are not empty: (Large might be emptied first)
Remove the first element from Small; call it l.
Remove the first element from Large; call it g.
Set Prob[l]=pl.
Set Alias[l]=g.
Set pg := (pg+pl)−1. (This is a more numerically stable option.)
If pg<1, add g to Small.
Otherwise (pg ≥ 1), add g to Large.
While Large is not empty:
Remove the first element from Large; call it g.
Set Prob[g] = 1.
While Small is not empty: This is only possible due to numerical instability.
Remove the first element from Small; call it l.
Set Prob[l] = 1.
Generation:
Generate a fair die roll from an n-sided die; call the side i.
Flip a biased coin that comes up heads with probability Prob[i].
If the coin comes up "heads," return i.
Otherwise, return Alias[i].
Here is an implementation in Ruby:
def weighted_rand(weights = {})
raise 'Probabilities must sum up to 1' unless weights.values.inject(&:+) == 1.0
raise 'Probabilities must not be negative' unless weights.values.all? { |p| p >= 0 }
# Do more sanity checks depending on the amount of trust in the software component using this method,
# e.g. don't allow duplicates, don't allow non-numeric values, etc.
# Ignore elements with probability 0
weights = weights.reject { |k, v| v == 0.0 } # e.g. => {"a"=>0.4, "b"=>0.4, "c"=>0.2}
# Accumulate probabilities and map them to a value
u = 0.0
ranges = weights.map { |v, p| [u += p, v] } # e.g. => [[0.4, "a"], [0.8, "b"], [1.0, "c"]]
# Generate a (pseudo-)random floating point number between 0.0(included) and 1.0(excluded)
u = rand # e.g. => 0.4651073966724186
# Find the first value that has an accumulated probability greater than the random number u
ranges.find { |p, v| p > u }.last # e.g. => "b"
end
How to use:
weights = {'a' => 0.4, 'b' => 0.4, 'c' => 0.2, 'd' => 0.0}
weighted_rand weights
What to expect roughly:
sample = 1000.times.map { weighted_rand weights }
sample.count('a') # 396
sample.count('b') # 406
sample.count('c') # 198
sample.count('d') # 0
An example in ruby
#each element is associated with its probability
a = {1 => 0.25 ,2 => 0.5 ,3 => 0.2, 4 => 0.05}
#at some point, convert to ccumulative probability
acc = 0
a.each { |e,w| a[e] = acc+=w }
#to select an element, pick a random between 0 and 1 and find the first
#cummulative probability that's greater than the random number
r = rand
selected = a.find{ |e,w| w>r }
p selected[0]
This can be done in O(1) expected time per sample as follows.
Compute the CDF F(i) for each element i to be the sum of probabilities less than or equal to i.
Define the range r(i) of an element i to be the interval [F(i - 1), F(i)].
For each interval [(i - 1)/n, i/n], create a bucket consisting of the list of the elements whose range overlaps the interval. This takes O(n) time in total for the full array as long as you are reasonably careful.
When you randomly sample the array, you simply compute which bucket the random number is in, and compare with each element of the list until you find the interval that contains it.
The cost of a sample is O(the expected length of a randomly chosen list) <= 2.
This is a PHP code I used in production:
/**
* #return \App\Models\CdnServer
*/
protected function selectWeightedServer(Collection $servers)
{
if ($servers->count() == 1) {
return $servers->first();
}
$totalWeight = 0;
foreach ($servers as $server) {
$totalWeight += $server->getWeight();
}
// Select a random server using weighted choice
$randWeight = mt_rand(1, $totalWeight);
$accWeight = 0;
foreach ($servers as $server) {
$accWeight += $server->getWeight();
if ($accWeight >= $randWeight) {
return $server;
}
}
}
Ruby solution using the pickup gem:
require 'pickup'
chances = {0=>80, 1=>20}
picker = Pickup.new(chances)
Example:
5.times.collect {
picker.pick(5)
}
gave output:
[[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 1, 1],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 1]]
If the array is small, I would give the array a length of, in this case, five and assign the values as appropriate:
array[
0 => 0
1 => 0
2 => 0
3 => 0
4 => 1
]
"Wheel of Fortune" O(n), use for small arrays only:
function pickRandomWeighted(array, weights) {
var sum = 0;
for (var i=0; i<weights.length; i++) sum += weights[i];
for (var i=0, pick=Math.random()*sum; i<weights.length; i++, pick-=weights[i])
if (pick-weights[i]<0) return array[i];
}
the trick could be to sample an auxiliary array with elements repetitions which reflect the probability
Given the elements associated with their probability, as percentage:
h = {1 => 0.5, 2 => 0.3, 3 => 0.05, 4 => 0.05 }
auxiliary_array = h.inject([]){|memo,(k,v)| memo += Array.new((100*v).to_i,k) }
ruby-1.9.3-p194 > auxiliary_array
=> [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4]
auxiliary_array.sample
if you want to be as generic as possible, you need to calculate the multiplier based on the max number of fractional digits, and use it in the place of 100:
m = 10**h.values.collect{|e| e.to_s.split(".").last.size }.max
Another possibility is to associate, with each element of the array, a random number drawn from an exponential distribution with parameter given by the weight for that element. Then pick the element with the lowest such ‘ordering number’. In this case, the probability that a particular element has the lowest ordering number of the array is proportional to the array element's weight.
This is O(n), doesn't involve any reordering or extra storage, and the selection can be done in the course of a single pass through the array. The weights must be greater than zero, but don't have to sum to any particular value.
This has the further advantage that, if you store the ordering number with each array element, you have the option to sort the array by increasing ordering number, to get a random ordering of the array in which elements with higher weights have a higher probability of coming early (I've found this useful when deciding which DNS SRV record to pick, to decide which machine to query).
Repeated random sampling with replacement requires a new pass through the array each time; for random selection without replacement, the array can be sorted in order of increasing ordering number, and k elements can be read out in that order.
See the Wikipedia page about the exponential distribution (in particular the remarks about the distribution of the minima of an ensemble of such variates) for the proof that the above is true, and also for the pointer towards the technique of generating such variates: if T has a uniform random distribution in [0,1), then Z=-log(1-T)/w (where w is the parameter of the distribution; here the weight of the associated element) has an exponential distribution.
That is:
For each element i in the array, calculate zi = -log(T)/wi (or zi = -log(1-T)/wi), where T is drawn from a uniform distribution in [0,1), and wi is the weight of the I'th element.
Select the element which has the lowest zi.
The element i will be selected with probability wi/(w1+w2+...+wn).
See below for an illustration of this in Python, which takes a single pass through the array of weights, for each of 10000 trials.
import math, random
random.seed()
weights = [10, 20, 50, 20]
nw = len(weights)
results = [0 for i in range(nw)]
n = 10000
while n > 0: # do n trials
smallest_i = 0
smallest_z = -math.log(1-random.random())/weights[0]
for i in range(1, nw):
z = -math.log(1-random.random())/weights[i]
if z < smallest_z:
smallest_i = i
smallest_z = z
results[smallest_i] += 1 # accumulate our choices
n -= 1
for i in range(nw):
print("{} -> {}".format(weights[i], results[i]))
Edit (for history): after posting this, I felt sure I couldn't be the first to have thought of it, and another search with this solution in mind shows that this is indeed the case.
In an answer to a similar question, Joe K suggested this algorithm (and also noted that someone else must have thought of it before).
Another answer to that question, meanwhile, pointed to Efraimidis and Spirakis (preprint), which describes a similar method.
I'm pretty sure, looking at it, that the Efraimidis and Spirakis is in fact the same exponential-distribution algorithm in disguise, and this is corroborated by a passing remark in the Wikipedia page about Reservoir sampling that ‘[e]quivalently, a more numerically stable formulation of this algorithm’ is the exponential-distribution algorithm above. The reference there is to a sequence of lecture notes by Richard Arratia; the relevant property of the exponential distribution is mentioned in Sect.1.3 (which mentions that something similar to this is a ‘familiar fact’ in some circles), but not its relationship to the Efraimidis and Spirakis algorithm.
I would imagine that numbers greater or equal than 0.8 but less than 1.0 selects the third element.
In other terms:
x is a random number between 0 and 1
if 0.0 >= x < 0.2 : Item 1
if 0.2 >= x < 0.8 : Item 2
if 0.8 >= x < 1.0 : Item 3
I am going to improve on https://stackoverflow.com/users/626341/masciugo answer.
Basically you make one big array where the number of times an element shows up is proportional to the weight.
It has some drawbacks.
The weight might not be integer. Imagine element 1 has probability of pi and element 2 has probability of 1-pi. How do you divide that? Or imagine if there are hundreds of such elements.
The array created can be very big. Imagine if least common multiplier is 1 million, then we will need an array of 1 million element in the array we want to pick.
To counter that, this is what you do.
Create such array, but only insert an element randomly. The probability that an element is inserted is proportional the the weight.
Then select random element from usual.
So if there are 3 elements with various weight, you simply pick an element from an array of 1-3 elements.
Problems may arise if the constructed element is empty. That is it just happens that no elements show up in the array because their dice roll differently.
In which case, I propose that the probability an element is inserted is p(inserted)=wi/wmax.
That way, one element, namely the one that has the highest probability, will be inserted. The other elements will be inserted by the relative probability.
Say we have 2 objects.
element 1 shows up .20% of the time.
element 2 shows up .40% of the time and has the highest probability.
In thearray, element 2 will show up all the time. Element 1 will show up half the time.
So element 2 will be called 2 times as many as element 1. For generality all other elements will be called proportional to their weight. Also the sum of all their probability are 1 because the array will always have at least 1 element.
I wrote an implementation in C#:
https://github.com/cdanek/KaimiraWeightedList
O(1) gets (fast!), O(n) recalculates, O(n) memory use.

Indefinite Count Up

I'm trying to create a counter that indefinitely counts up from that moment I upload it. I used countUp.js to generate this:
var options = {
  useEasing: false,
  useGrouping: false,
  separator: ',',
  decimal: '.',
};
var demo = new CountUp('myTargetElement', 0, 100000000, 0, 100000000, options);
if (!demo.error) {
  demo.start();
} else {
  console.error(demo.error);
}
It counts up to 10000000 at a rate of 1 per second. I just need it to live somewhere/keep counting so I can pull the counter's current value at any time.
Any ideas?

Got an index that I need to match with another array

So I have a bar chart with for example this array:
[12, 32, 42, 32, 43, 0, 0, 0, 5, 0, 0, 0]
So I have this array from markers from a google map with a length of 6, so from index 0 to 5.
I have an highlight event on the bar chart that when fired, it gives me the pointindex. I use the pointindex to get the marker from the google maps array, like: "gmap.markers[pointindex]". This works fine for the first 5 values from the bar chart array. but the ninth value from the bar chart array has the pointindex 8, but my google maps array only has 0 till 5 as index. So it doesn't work anymore. Basicly the pointindex 8 is basicly the index 5 from the google maps marker array.
This problem would also occur with as example these values.
Bar chart array: [12, 32, 42, 32, 43, 0, 7, 0, 5, 0, 0, 0]
Google map marker array with a length of 7, so index from 0 to 6.
I have this code for the highlight of a bar in the chart, this event is fired and I do stuff with the marker to highlight a marker(basically change the icon.
chart.bind('jqplotDataHighlight',
function (ev, seriesIndex, pointIndex, data) {
marker = gmap.markers[pointIndex];
if (marker !== null && marker !== undefined) {
marker.setIcon('https://maps.gstatic.com/mapfiles/ms2/micons/blue-dot.png');
}
}
);
As I explained when there is empty data in a pointindex of the bar chart array, the index of the bar doesn't match anymore with the index of the google marker.
I thought in my head to copy the bar chart array to a new array and remove all values that contain "0". and than compare the value from the bar chart array with the new array without "0" values's to see what the index is in the new array. So that it would match again with the google map marker index. But then you have the problem that if the bar chart array has 2 values that are the same, then this method doesn't work anymore.
Does anyone know a solution to this, in my head the problem is really simple, but I can't figure it out in code.
So #Nina's solution works in principle but not in terms of UI interaction.
Basically you need to see if the bar that was clicked has a value, and if it does, which index it maps to in Nina's filtering solution. Once you have the index that the value lies in from the filtered array, you can map that to your markers array.
To do this:
I think your best bet is, when building the bar chart to maintain an array with an object that is like { filteredIndex: N }. This array will be same length as the bar chart and tells you which index it maps to in the filtered array. If the data is 0, then the filteredIndex will be null.
You can do this simply by iterating your data and pushing the value to the filtered array if it's value is greater than 0, and maintaining the index, in a separate mapping array.
Thanks #Alex for the idea, and thanks #NinaScholz for the help, but I think you misunderstood my question, I know it was a vague question, sorry.
I have solved it by doing this.
when the page is loaded I do this:
var barArray = PF('dataChart').plot.data[0];
var mappingArray = new Array(barArray.length);
var j = 0;
for (var i = 0; i < barArray.length; i++) {
if (barArray[i] !== 0) {
mappingArray[i] = j;
j++;
} else {
mappingArray[i] = null;
}
}
And the listener now does this:
chart.bind('jqplotDataHighlight',
function (ev, seriesIndex, pointIndex, data) {
marker = gmap.markers[mappingArray[pointIndex]];
if (marker !== null && marker !== undefined) {
marker.setIcon('https://maps.gstatic.com/mapfiles/ms2/micons/blue-dot.png');
}
}
);
So basically I make a new array that is as long as the chart array, if the value from the chart array has a value that is not 0, I set the value of this index in the mappingArray to a counted up index, that matches with the google markers index. If this can be done better, please do tell!
Here is the solution with the preserved index and the right index for sparse access.
var barChart = [12, 32, 42, 32, 43, 0, 7, 0, 5, 0, 0, 0],
filtered = [];
barChart.forEach(function (a, i) {
a && filtered.push({ value: a, index: i });
});
document.write('<pre>' + JSON.stringify(filtered, 0, 4) + '</pre>');

Adding a variable to a variable set in JavaScript

So I'm making my incremental game, Periodic Alchemy, and I'm trying to make something that ensures that a save keeps all the new variables in another version. In order to save, I take a variable, "player", which includes all my variables inside it, and make it into a Base64 string that you can copy.
In almost every new version, however, I add new variables. If a player uses their old save on a new version with new variables, then those new variables are lost and the new functions break. So when you load the game, the game checks to see if that old save_data contains certain variables, and if a variable is not included, the game adds that variable to the save data. I don't know, however, how to do that. Here is my "player" code:
var player = {
upQuarkClicks: 0,
electronClicks: 0,
downQuarkClicks: 0,
auto_up_quark_clicks: 0,
auto_electron_clicks: 0,
auto_down_quark_clicks: 0,
auto_proton_clicks: 0,
auto_neutron_clicks: 0,
up_quark_click_cost: 15,
electron_click_cost: 15,
down_quark_click_cost: 15,
proton_click_cost: 15,
neutron_click_cost: 15,
upgrade_up_quark_1_cost: 50,
upgrade_electron_1_cost: 50,
upgrade_down_quark_1_cost: 50,
auto_up_quark_clicks_amount: 1,
auto_electron_clicks_amount: 1,
auto_down_quark_clicks_amount: 1,
auto_proton_clicks_amount: 1,
auto_neutron_clicks_amount: 1,
upgrade_up_quark_click_1_bought: "False",
upgrade_electron_click_1_bought: "False",
upgrade_down_quark_click_1_bought: "False",
proton_up_quark_cost: 2,
proton_down_quark_cost: 1,
neutron_up_quark_cost: 1,
neutron_down_quark_cost: 2,
protonClicks: 0,
neutronClicks: 0,
hydrogen2DeuteriumClicks: 0,
hydrogen1ProtiumClicks: 0,
hydrogen3TritiumClicks: 0,
helium3Clicks: 0,
helium4Clicks: 0,
lithium6Clicks: 0,
lithium7Clicks: 0
};
So, let's say I added a new variable: lithium_7_bought: "false". This new variable is not in the old save data, and so is erased by the load. How do I add it in if it is not found?
Something like this should work:
if (!("lithium_7_bought" in player))
{
player.lithium_7_bought = "false";
}

Google Area Chart is not taking value as int

I'm using Google's Area Chart to dispaly a graph.
For some reason I'm not ble to use values past into the function:
I have the following code:
function SetAreaChartData(valueString) {
//This works
AreaChartData.setValue(0, 0, 'SomeName');
AreaChartData.setValue(0, 1, 500);
AreaChartData.setValue(0, 2, 500);
AreaChartData.setValue(0, 3, 500);
//This does not work
// First I must "cast" the input to string in order to use the .split function
var str = new String(valueString);
// Then I split the string in order to get an array of string
val = str.split(",");
AreaChartData.setValue(0, 0, 'SomeName');
AreaChartData.setValue(0, 1, val[0]*1); //Multiply by one to cast it to integer
AreaChartData.setValue(0, 2, val[1]*1);
AreaChartData.setValue(0, 3, val[2]*1);
}
I've also tried using parseInt(val[0]), but that is not helping either.
Why won't .setValue recognize val[0] as an integer?

Categories