Searching for compound indexes in IndexedDB - javascript

After reading here for ages, I've finally registered to ask a question. I've been messing around with IndexedDB lately and stumbled over a problem with compound indexes (I use them somilar to the example here).
I have an object in the objectstore with a string value, and a couple of integer values. E. g.:
[description:text, value1:int, value2:int, value3:int]
I created a compound index on this object like this:
("compoundIndex", ["value1" , "value2" , "value3"] , { unique: false });
In the html I got a couple of select boxes and a textfield, that allows the user to search for specific entries. The integers are passed as a keyrange to the opencursor-function on the index. Then I use indexOf(textfield) on the resulting set (like done here)
If the selectbox has a value, that value is used as upper and lower bound. If the select box is untouched, the lower range is 1 and the upper is a MAX_INT variable I declared (like described here).
sample code:
transaction = db.transaction(["schaden"] , "readonly").objectStore("schaden");
index = transaction.index("compoundIndex");
// keyrange-arrays from another function
lowerBound = [valueOneLower, valueTwoLower, valueThreeLower];
upperBound = [valueOneUpper, valueTwoUpper, valueThreeUpper];
range = IDBKeyRange.bound( lowerBound, upperBound );
index.openCursor(range).onsuccess = function(e){
var cursor = e.target.result;
if (cursor){
if (getTextfield.length == 0){
console.log("Entry found: " + cursor.value.description + ". Object: " + JSON.stringify(cursor.value));
}else if (cursor.value.bezeichnung.indexOf(getTextfield) !== -1){
console.log("Entry found: " + cursor.value.description + ". Object: " + JSON.stringify(cursor.value));
};
cursor['continue']();
};
};
I can search for entries perfectly well, when I have all values set in all the select-boxes. However, if I leave a field open, it messes up the search. Let's say I have not touched the value1-select box, and set the other boxes to 2, I'll get the lowerBound = [1,2,2] and the upperBound = [4294967295,2,2]. This will give me back all entries in my IDB, it doesn't take the 2nd and 3rd value into account.
Is this intended? Or is there a way around this? I have been searching for information about this over and over but seem to be in a dead end. My naive understanding of this API led me to believe it would take all array fields into account on the search. Since the object and therefor also the index I use are much more complex than the example above, performing searches on multiple indexes would be quite messy.
Thanks for your insights!
Edit:
To make it a little more clear after the first comments. Let's say if have the following object in the object store:
obj1 { val1 = 1 , val2 = 3 , val3 = 1 }
obj2 { val1 = 1 , val2 = 2 , val3 = 2 }
obj3 { val1 = 2 , val2 = 1 , val3 = 3 }
obj4 { val1 = 1 , val2 = 1 , val3 = 1 }
obj5 { val1 = 1 , val2 = 2 , val3 = 3 }
The index sorts it the way expected:
#1 [1,1,1] obj4
#2 [1,2,2] obj2
#3 [1,2,3] obj5
#4 [1,3,1] obj1
#5 [2,1,3] obj3
Let's assume now I search for the range (lower[1,1,1] , upper[1,1,1]) I'll get obj4. This is the behaviour when all select boxes have selected option 1.
Now if I search for an entry with val1 = 1, val2 = unknown and val3 = 1, I get the following range: lower[1,1,1] , upper[1,4294967295,1]. Expected results are obj4 [1,1,1] and obj1 [1,3,1]. Instead of these, the result is giving me 4 hits, namely obj4, obj2, obj5 and obj1 although val3 of obj2 and obj5 doesn't match the key range.

When you create an index on an array, the entries of your store only appear in the index if each element in the array that corresponds to a property in the underlying object has a defined value.
To get around this obstacle, always store defined values in your underlying object store. For example, to represent a boolean property, use an integer, where 0 is false, and 1 is true. This way, each object in the store can appear in the index. indexedDB's behavior here is quite different than truthy/falsy handling in plain old javascript (where 0 == undefined ).
The key range you specify when opening a cursor on a array-based index must use defined parameters for each element of the array.
To get around this obstacle, you must specify all boundaries, even if those boundaries are not real values (e.g. like in my example you linked to, 200 as max age works because we can safely assume no one is 200 yrs old).
So to address your question, it might be a problem in your code in that one of the parameters to your boundaries variables (either [valueOneLower, valueTwoLower, valueThreeLower] or [valueOneUpper, valueTwoUpper, valueThreeUpper]) is not defined.
Based on your comments, I suggest that you test your expectations with indexedDB.cmp. It is pretty simple to write these tests. It does not require any database connection. Here is a pretty basic example to get you started:
// Build our test values
var lower1 = 1, lower2 = 1, lower3 = 1;
var upper1 = 3, upper3 = 3, upper3 = 3;
var middle1 = 2, middle2 = 2, middle3 = 2;
var lowerBound = [lower1,lower2,lower3];
var upperBound = [upper1,upper2,upper3];
var middleValue = [middle1,middle2,middle3];
// As the linked page provides, cmp returns -1 if first is less than second, 0 if equal, 1 if first is greater than second.
var lowerVsMiddle = indexedDB.cmp(lowerBound, middleValue);
console.log('Is %s < %s ? %s', lowerBound, middleValue, lowerVsMiddle == -1);
console.log('Is %s > %s ? %s', lowerBound, middleValue, lowerVsMiddle == 1);
var upperVsMiddle = indexedDB.cmp(upperBound, middleValue);
console.log('Is %s < %s ? %s', upperBound, middleValue, upperVsMiddle == -1);
console.log('Is %s > %s ? %s', upperBound, middleValue, upperVsMiddle == 1);
You should be able to answer your questions accurately by running tests like this.
I retrieved the relevant part of the indexedDB spec for you. First note that "An Array is only a valid key if every item in the array is defined and is a valid key...". This ties into whether the object will appear in the index, and also ties into whether your key parameters to either cmp or IDBKeyRange.bound/lowerBound/upperBound will work. Second, farther down, note the following:
Values of type Array are compared to other values of type Array as follows:
Let A be the first Array value and B be the second Array value.
Let length be the lesser of A's length and B's length.
Let i be 0.
If the ith value of A is less than the ith value of B, then A is less
than B. Skip the remaining steps.
If the ith value of A is greater than the ith value of B, then A is greater than B. Skip the remaining steps.
Increase i by 1.
If i is not equal to length, go back to step 4. Otherwise continue to next step.
If A's length is less than B's length, then A is less than B. If A's length is greater than B's length, then A is greater than B. Otherwise A and B are equal.
From the KeyRange section: A key is in a key range if both the following conditions are fulfilled:
The key range lower value is undefined or less than key. It may also be equal to key if lowerOpen is false.
The key range upper value is undefined or greater than key. It may also be equal to key if upperOpen is false.
One more clarification now that I understand your question based on the comments and your further edits: Essentially indexedDB is providing a union behavior of the criteria but you want is an intersection. One way of solving this is to not think about the data in normal form at all, but to think about how to setup the data so it can be queried in the manner you want. It is interesting food for thought and I do not have an immediate answer for you.

You need another compound index for that case.
Alternatively you can use key joining as describe here YDN-DB - Incorrect results using mixed data types with SortedMerge

Related

using Short-Circuiting ( || ) in Object in JavaScript

this block of code takes the values of the scored property and puts them in duplication object as a key and how many times the name got duplicated as a value using the || operator.
I understand that the || operator will return the first truthy value or the last value if all of the values are falsy, however, I didn't understand duplication[x]++ what does the ++ sign do exactly? and why we put the (duplication[x]=1) between parentheses
const game = {
score: "4:0",
scored: ["Lewandowski", "Gnarby", "Lewandowski", "Hummels"],
};
const duplication = {};
for (let x of game.scored) {
duplication[x]++ || (duplication[x]=1) // I'm confused in this line
}
console.log(duplication);
Let's see what's happing on this line :
duplication[x]++ || (duplication[x]=1)
duplication[x]++ , first duplication[x] it will check if duplication has any with value of x, if yes then it it will perform duplication[x]++ else it will be undefined to moved to the other part of or condition
duplication[x]=1, this is a simple assignment it will assign the value 1, duplication[x] and this will create a key if not exist in the duplication object
Now if you run the below script and check the console log for each loop, it will give you clear idea what actually happing.
const game = {
score: "4:0",
scored: ["Lewandowski", "Gnarby", "Lewandowski", "Hummels"],
};
const duplication = {};
let index = 0;
for (let x of game.scored) {
console.log( `INDEX : ${index} ` , x , duplication[x] ? 'FOUND , INCREMENT CURRENT VALUE WITH ++' : 'NOT FOUND, SO ASSIGN VALUE 1' );
duplication[x]++ || (duplication[x]=1)
console.log( `INDEX : ${index} \n` , duplication);
index++;
}
console.log( 'FINAL OBJECT \n' , duplication);
The non-descriptive variable names don't really help to explain the situation. Let's start of by rewriting the code with more descriptive variable names.
const game = {
score: "4:0",
scored: ["Lewandowski", "Gnarby", "Lewandowski", "Hummels"],
};
const goals = {};
for (const player of game.scored) {
goals[player]++ || (goals[player] = 1);
}
console.log(goals);
goals[player]++ increments the goals for player by 1 and returns the old value. The tricky thing in goals[player]++ is that player might not be present in goals yet. In which case undefined is returned (which is falsy). Because the value is falsy the second operand of the OR operator will be executed. (goals[player] = 1) will set the goals for player to 1.
The code is essentially counting how often a specific name is present in the game.scored array. The presence of a name symbolises a goal made by them.
A less cryptic way of writing similar code would be:
const goals = {};
for (const player of game.scored) {
if (player in goals) {
goals[player] += 1; // add 1 to the current score
} else {
goals[player] = 1; // no score present so use 1
}
}
However I usually prefer to set a default value, this way you don't have to split the logic into two actions:
const goals = {};
for (const player of game.scored) {
goals[player] ||= 0; // assign 0 if the current value is falsy
goals[player] += 1; // add 1 to the current value
}
Note that ||= is fairly new, if you write JavaScript for older browser you can use the following instead:
if (!goals[player]) goals[player] = 0;
The first part of
duplication[x]++ || (duplication[x] = 1)
^^^^^^^^^^^^^^^^
has four parts:
a variable duplication with
a property accessor x in bracket notation
a postfix increment operator ++ and
an expression for the logical OR || operator.
The second part returns undefined at the first call with an unknown property.
The try to increment this value returns NaN, because of the following operation of duplication[x] = duplication[x] + 1. The result is is a falsy value.
This forces the expression to evaluate the right hand part of logical OR.
And because the left hand part has an expression, it needs to be evaluated first with a grouping operator (). Now the assignment takes place and the result of 1 is returned to the OR.

chineseFood[array[0]] = array[array.length-1];

I don't understand the purpose of this = sign on the sixth line in the code block below. I understand how the argument grabs each index number of the array, I just don't understand why chineseFood[array[0]] = array[array.length-1]; In other words, I don't get the purpose of the equal sign as if it were almost comparing each other to be stored in the empty object that is stored in the variable chineseFood. Could someone please clarify? It would be much appreciated.
function transformFirstAndLast(array) {
var chineseFood = {};
//takes 1st element (at index 0) and sets it to the last element (nth index): array(length-1)
chineseFood[array[0]] = array[array.length - 1];
return chineseFood;
}
console.log( transformFirstAndLast(['Orange', 'Lemon', 'Pork', 'Chicken']) );
Output Below
{Orange: "Chicken"}
The equals sign is not comparison, it is assignment. chineseFood is an object, which means that it can be treated like a dictionary, and its properties can be accessed using the [] operator instead of the . operator:
myObj = {
foo: "bar"
};
console.log(myObj["foo"]); // bar
console.log(myObj.foo); // bar
Likewise, you can also assign properties this way:
myObj = {};
myObj["foo"] = 3;
console.log(myObj["foo"]); // 3
console.log(myObj.foo); // 3
This is what your code is doing. It is retrieving the value of array[array.length-1], which is "Chicken". Then it is assigning this value to the property of chineseFood that has the name represented by array[0], which happens to be "Orange". Thus, the property named Orange on chineseFood is set to array[array.length - 1], which is why chineseFood evaluates to {Orange: "Chicken"}.
This method of accessing properties is especially useful when you don't know the name of the property you will be changing in advance, as is the case with this code, or when you want to create properties that have names that would otherwise be illegal:
myObj = {
".you can't usually use with spaces or start w/ periods": false
};
myObj[".you can't usually use with spaces or start w/ periods"] = true;
console.log(myObj[".you can't usually use with spaces or start w/ periods"]);
// there is no way to read this property the normal way
Basically what is does is:
your object is :
var obj = {Orange: "Chicken"};
And Your array is :
var arr = ['Orange','Lemon','Pork','Chicken']
What this line says is pick first element of the array and check for this prop in object and change its value to last element of array, here:
arr[0] = "orange";
So this line :
obj[arr[0]] can be seen as obj['orange'].
After that you change its value:
Obj[arr[0]] = arr[arr.length-1] which can be written as obj['orange'] = 'chicken'

How does this for/in loop work exactly?

I'm doing a beginner exercise, find the mean/median/mode/range of an array of numbers. I'm on the mode now, and found this:
var store = ['1','2','2','3','4'];
var frequency = {}; // array of frequency.
var max = 0; // holds the max frequency.
var result; // holds the max frequency element.
for(var v in store) {
frequency[store[v]]=(frequency[store[v]] || 0)+1; // increment frequency.
if(frequency[store[v]] > max) { // is this frequency > max so far ?
max = frequency[store[v]]; // update max.
result = store[v]; // update result.
}
}
It works but I don't understand it.
What does the || 0 do in the first line?
Why can't I change the key names?
frequency["key"+store[v]]=(frequency[store[v]] || 0)+1; returns {key1: 1, key2: 1, key3: 1, key4: 1} not {1: 1, 2: 2, 3: 1, 4: 1}, so the keys are playing an important role.
Is the if statement testing both the key and value?
Replacing any instance of frequency[store[v]]; with a variable (var freqTest = frequency[store[v]];, created inside or outside the loop) breaks something.
The whole thing is going over my head really.
The key in the entire logic is understanding this line
frequency[store[v]]=(frequency[store[v]] || 0)+1;
The left side is being used as a map for some number. When v is equal to 3 store[3] returns 2 and thus frequency[2] is accessed.
Now for the same iteration consider the right side. We already know that
frequency[store[3]]
resolves to
frequency[2]
but what will this return? As frequency[2] would have also been set in iteration 2 we would be accessing the number from iteration 2. Lets now look at the value derived from iteration 2 then:
frequency[store[2]] = (frequency[store[2]] || 0)+1
frequency[2] = (frequency[2] || 0)+1
frequency[2] = (null || 0)+1
frequency[2] = 1
Ahhh... so the value for iteration 3 is actually
frequency[2] = (frequency[2] || 0) + 1
frequency[2] = (1 || 0) + 1
frequency[2] = (1) + 1
frequency[2] = 2
As you can see, the loop is using frequency[n] as a map and increments the value each time it is found. Then the value is stored in max if it is higher. This is a very smart way to find the highest repeating value while only iterating over the list one time.
What does the || 0 do in the first line?
It takes 0 as a default value when the lookup fails (when there is not yet a frequency property with that name), so that the map is initialised with 1s on the first appearance of a value, not NaN (from undefined + 1).
The assignment can (and for beginners, should) be expanded to
if (frequency[key]) // the property already exists, does not give `0` or `undefined`
frequency[key] = frequency[key] + 1;
else // the property didn't exist and the access yielded `undefined`
frequency[key] = 1; // 0 + 1
Why can't I change the key names?
You can, you just have to do it everywhere.
The code should be written much cleaner like this:
var store = ['1','2','2','3','4'];
var frequency = {}; // object (key-value-map) of frequency
var max = 0; // holds the max frequency value
var result; // holds the max frequency element name
for (var v=0; v<store.length; v++) {
var key = "key" + store[v];
frequency[key] = (frequency[key] || 0)+1; // increment frequency
// ^^^ here as well
if (frequency[key] > max) { // is this frequency > max so far ?
max = frequency[key]; // update max.
result = store[v]; // update result.
// ^^^^^^^^ alternatively use `key` also here
}
}
Is the if statement testing both the key and value?
Testing? Hm, no. It does use the value from the store array as a key in the frequency object. It does then compare the property value with the max.
a || 0 means if a is not undefined, take 1 otherwise 0
You can change the key names.
var store = ['1','2','2','3', '1', '1','4'];
var frequency = {}; // array of frequency.
var max = 0; // holds the max frequency.
var result; // holds the max frequency element.
for(var v in store) {
frequency['key'+store[v]]=(frequency['key'+store[v]] || 0)+1; // increment frequency.
if(frequency['key' + store[v]] > max) { // is this frequency > max so far ?
max = frequency[store[v]]; // update max.
result = 'key' + store[v]; // update result.
}
}
The line you ask about frequency[store[v]]=(frequency[store[v]] || 0)+1 is sometimes referred to OR assignment; this stack overflow question has some good explanations and examples. For your code, consider this that I just typed into my browser's javascript console:
> var frequency = {};
<- undefined
> frequency[0];
<- undefined
> frequency[0] || 0
<- 0
As for you why you can't change the key names, you can, you just haven't changed them 'enough'. Changing the body to replace every key reference with "key"+store leaves the code in the same functioning state.
for(var v in store) {
// Increment the frequency of the value v
frequency["key"+store[v]]=(frequency["key"+store[v]] || 0)+1;
// is this frequency > max so far ?
if(frequency["key"+store[v]] > max) {
// If it is, we have a new, most frequently occurring number
// Update the max to this new highest frequency
max = frequency["key"+store[v]];
// Update the result to the new most frequent value
result = store[v];
}
}
I added some additional comments in the code to make it clearer what's going on.
To your first question:
In JavaScript you can test if variables are defined by using them as booleans.
var foo;
if(foo) //true
console.log('foo is false, 0, null or not defined');
So in this case you are testing if frequency already has an element for store[v]. If it does, use that, otherwise use 0 instead so that would be the same as
var valueInFrequency = frequency[store[v]] ? frequency[store[v]] : 0;
and then continue with valueInFrequency.
To your second question: As I explained just now, in
frequency[store[v]]=(frequency[store[v]] || 0)+1;
you either raise the current value by one or set it to 0 and then raise it by one. If you change the key you set the value to but then don't test for the new value, you end up simply overriding the existing value to 0 + 1.
Now to your last question: No it isn't. It uses store[v] as a key for frequency and then compares that value to max.
I hope I could answer your questions. If anything is still unclear, just ask!
I propose a better solution to this problem as the given solution.
Solution with emphasis to Array.prototype.forEach and the problem of getting more than one key if the max count is shared among more items.
What has changed:
result is now an array, because the maximal count of the distribution can affect more than one key/item.
The for () loop is replaced by Array.prototype.forEach and a callback which allows an iteration over all ements of an array in a more compact manner.
Only max is in the callback stored.
For the keys/item with the max count is another loop necessary.
First get the keys from the object with Object.keys
Then iterates over the keys and check for count === max and push the key
Display all found values.
To the question what x = x || y means:
If the value of x is falsy (like undefined, null, 0, -0, '') the the value of y is used, because of the Logical Or operator.
var store = ['1', '2', '2', '3', '4', '5', '5'],
distribution = {},
max = 0,
result = [];
store.forEach(function (a) {
distribution[a] = (distribution[a] || 0) + 1;
if (distribution[a] > max) {
max = distribution[a];
}
});
Object.keys(distribution).forEach(function (k) {
distribution[k] === max && result.push(k);
});
document.write('max: ' + max + '<br>');
document.write('key/s with max count: ' + JSON.stringify(result) + '<br>');
document.write('<pre>' + JSON.stringify(distribution, 0, 4) + '</pre>');

Can I create a variable that references a node in an object?

I have this piece of code. It increments a value in an array or sets it to 1 if undefined:
if(typeof sum[period][count] === "undefined"){
sum[period][count] = 1
}else{
sum[period][count]++;
}
I would like to make it shorter and less repetitive like this:
node = sum[period][count];
if(typeof node === "undefined"){
node = 1
}else{
node++;
}
or even
node = typeof node === "undefined"? 1 : node+1;
But it doesn't work, why not and what can I do about it?
since the value sum[period][count] is a primitive, you can't get a reference to it. You could shorten it by assigning the object containing this value to a variable: var node = sum[period] and test for node[count]. However, it is not much shorter:
var node = sum[period];
if(typeof node[count] === "undefined"){
node[count] = 1
}else{
node[count]++;
}
Because the node will have the value not the Reference.
I believe your solution will work in some cases, but in others it will not. This is a consequence of using an array of arrays (two-dimensional array/hash) and only checking if the 2nd order value is undefined.
When you define a list of lists (e.g. 'sum') you don't create a 2D matrix. Instead, you are creating a 1D list of 1D lists. If the first list index (period) is found, then it will get that list and look for the second index (count) within that 2nd list. But if the first list doesn't contain that first index, then you'll get an error immediately since you are referencing an index in an undefined list.
The solution is to check if the first order index value is undefined before checking the second. For reusability, I'm packaging the code up in a function. You will want to remove the JQuery output; it's just added for my testing.
var sum = {0: {0: 2}};
var period = 0;
var count = 1;
var helper = function(idxa, idxb) {
if (sum[idxa] === undefined) {
sum[idxa] = {idxb: 1}; // we can define the new list, knowing idxb must be 1 now.
} else {
// you were onto the right code here, but you weren't validating sum[idxa] separately.
sum[idxa][idxb] = (sum[idxa][idxb] === undefined) ? 1 : sum[idxa][idxb] + 1;
}
$('#output').append(" After: " + sum[idxa][idxb]);
};
helper(0, 3); // outputs " After: 1". [0] was defined, but [0][3] wasn't.
helper(2, 3); // outputs " After: 1". Neither [2] nor [2][3] were defined.
helper(0, 0); // outputs " After: 3", because [0][0] was 2
helper(0, 0); // outputs " After: 4", because we already incremented to 3.
You could nest a ternary operation inside a ternary, but I didn't because it's no faster and it wouldn't improve readability.

Read Elements from Stack without losing them in Javascript

Quick question. Let's say I've got a Stack, where I pop things.
var stack = [];
stack.push(2); // stack is now [2]
stack.push(5); // stack is now [5]
How do I read the last element, since, let's say I don't have the number of pops? For example a click function adds a variable to the stack and I need to use the last added variable, which command do I use?
And also, if I want to check all the Objects of the stack with another variable is it simply a for-loop with
stack[i] != x
Thanks in advance.
To assign the last element to a variable:
var x = stack[stack.length - 1]
If your array elements are objects you'll want to check a property of an object against a variable.
For example you might want to see if there's one or more objects with a key/value pair of 'number'/2 in the array. You might use some to check:
var x = 2;
var arr = [{ number: 1 }, { number: 2 }, { number: 3 }];
var some = arr.some(function (el) {
return el.number === x;
}); // true
Be sure to check out the various array methods on MDN.
Read last element in array
stack[stack.length-1]
stack[i] !== x is better comparison
!= won't catch that difference for instance null != undefined
These are connected with truthy and falsy values in javascript.

Categories

Resources