Create MultiDimensional javascript array from C++ - javascript

Edit: I have figured out the problem. There were several bugs in my originally posted code that came from my (very) poor understanding of COM, most notably not understanding what the calls to GetDispID were doing. I'll update the post with the correctly working code, in case anyone else runs into this issue.
I have a C++ application (MFC) with an embedded web browser. We are calling C++ functions from javascript using:
var arrResult = window.external.MyFunction (myArgs);
It works well. We send data back as an array using the code from CodeProject article http://www.codeproject.com/Articles/88753/Creating-JavaScript-arrays-and-other-objects-from
Now we need to send back a 2-dimensional array. This code works for me, now. Note that, in javascript, my 2 dimensional array really comes as an Object, not an array. So, isArray returns false:
oMyResult = window.external.VPQuery (strQuery);
if (Array.isArray (oMyResult))
alert ("This is an array!");
But this will be successful:
if( (typeof arrVPResult === "object") && (arrVPResult !== null) )
alert ("This is an object!");
So I can iterate through it like this:
for (var strProp in arrVPResult) {
if (arrVPResult.hasOwnProperty (strProp)) {
// do something...
}
}
Edit #2: If the call to theObject->GetDispID uses a numeric value rather than a text value (where CComBSTR(rarrData[n]) is way down below) then the created object will all act like an array in javascript (and have a .length value). If you use a text value, it will act like an object (and will not have a .length value).
Anyway, here's my C++ code. I hope it's useful for someone.
bool CMyHtmlDispatch::BuildJsArrayTest (CStringArray& rarrData, VARIANT& vtResult)
{
CComQIPtr<IDispatchEx> theObject;
DISPID didMainArray = 0;
DISPID didCurSubArray = 0;
DISPID didTemp = 0;
//
// First create our main array. The elements in this array will be
// arrays, too, giving us our 2 dimensional array. We'll operate on
// this array using theObject. As far as I can tell, we don't need didMainArray (the dispatch ID)
if (! BuildJsArray_CreateObject (vtResult, theObject, didMainArray))
return false;
if (NULL == theObject)
return false;
//
// Now create our sub-arrays. Each of them will contain an array of strings.
CString strIndex;
for(size_t n = 0; n < rarrData.GetCount(); n++)
{
CComQIPtr<IDispatchEx> theObjectSubArray;
_variant_t vtSubArray;
//
// Just like above, this creates our javascript array object. We'll refer to it
// using theObjectSubArray. We won't need to use didCurSubArray (the dispatch ID)
if (! BuildJsArray_CreateObject (vtSubArray, theObjectSubArray, didCurSubArray))
return false;
//
// Add our data to the sub-array, then we'll add the sub-array to the main array
// In our case, we'll just create some test data. We'll use the value in rarrData
// as the Key name and fill the sub-array values with variations on the key name.
for (int x = 0; x < 3; x ++) {
//
// First create the key for our index in our sub array. We'll just use the index
// number as the key, though we have to pass it to GetDispID as a string.
// fdexNameEnsure makes theObjectSubArray create it for us. It sends the dispatch ID
// back that we'll use below to set the value for our key in InvokeEx
strIndex.Format (_T("%d"), x);
if (FAILED (theObjectSubArray->GetDispID (CComBSTR(strIndex), fdexNameEnsure, &didTemp)))
break;
//
// Now add our sub-item value string to the sub-array object. We'll call
// InvokeEx on theSubObjectArray and need to pass in didTemp so it knows which
// key we're setting the value for.
CString s; s.Format (_T("%s - %d"), rarrData[n], x);
CComVariant vArg (s);
DISPID namedArgs[] = {DISPID_PROPERTYPUT};
DISPPARAMS p = {&vArg, namedArgs, 1, 1};
if (FAILED (theObjectSubArray->InvokeEx(didTemp, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYPUT, &p, NULL, NULL, NULL)))
return false;
}
//
// OK! Now, in theory, all the data for rarrData[n] is in didCurSubArray. Let's
// add that to the main array. Remember that GetDispID returns a dispatch ID that
// we'll use in our call to InvokeEx.
if (FAILED (theObject->GetDispID (CComBSTR(rarrData[n]), fdexNameEnsure, &didTemp)))
break;
CComVariant vArg (vtSubArray);
DISPID namedArgs[] = {DISPID_PROPERTYPUT};
DISPPARAMS p = {&vArg, namedArgs, 1, 1};
//
// Because our vArg really contains an IDispatch, we have to use DISPATCH_PROPERTYPUTREF
// rather than DISPATCH_PROPERTYPUT. So here we're associating that sub array with the
// key whose dispatch ID is didTemp.
if (FAILED (theObject->InvokeEx (didTemp, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYPUTREF, &p, NULL, NULL, NULL))) // DISPATCH_PROPERTYPUT
return false;
} // end loop through main data
return true;
} // end build js array int
//////////////////////////////////////////////////////////////////////////////
//
// This does the grunt work
//
// This function turns our MFC array into a native Javascript array.
// Javascript can't handle SAFEARRAYS directly. It _can_ handle them
// by using the javascript object VBArray, like this:
// var arrResult = new VBArray(window.external.CB_CustomFunctionWithParams (oRed, nPi)).toArray()
//
// But by doing the BuildJsArray stuff it lets the javascript side
// treat it just like a native javascript array.
//
// I got this code from:
// http://www.codeproject.com/Articles/88753/Creating-JavaScript-arrays-and-other-objects-from
//
bool CMyHtmlDispatch::BuildJsArray_CreateObject (VARIANT& vtResult, CComQIPtr<IDispatchEx>& rpObject, DISPID& did)
{
if (NULL == m_pHtmlView)
return false;
CWebDocument2Ptr pWebDocument2 = m_pHtmlView->GetWebDocument2 ();
if (NULL == pWebDocument2)
return false;
CComQIPtr<IDispatch> scriptDispatch;
pWebDocument2->get_Script (&scriptDispatch);
if (!scriptDispatch)
return false;
//
// get DISPID for "Array"
did = 0;
LPOLESTR lpNames[] = {L"Array"};
HRESULT hr = scriptDispatch->GetIDsOfNames(IID_NULL, lpNames, 1, LOCALE_USER_DEFAULT, &did);
if (FAILED(hr))
return false;
//
// invoke scriptdispatch with DISPATCH_PROPERTYGET for "Array"
CComVariant vtRet;
DISPPARAMS params = {0};
hr = scriptDispatch->Invoke(did, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET, &params, &vtResult, NULL, NULL);
if (FAILED(hr))
return false;
//
// check result: should be a VT_DISPATCH
if ((VT_DISPATCH != vtResult.vt) || (NULL == vtResult.pdispVal))
return false;
//
// get IDispatchEx on returned IDispatch
CComQIPtr<IDispatchEx> prototype(vtResult.pdispVal);
if (!prototype)
return false;
//
// call InvokeEx with DISPID_VALUE
// and DISPATCH_CONSTRUCT to construct new array
VariantClear (&vtResult);
hr = prototype->InvokeEx(DISPID_VALUE, LOCALE_USER_DEFAULT, DISPATCH_CONSTRUCT, &params, &vtResult, NULL, NULL);
if (FAILED(hr))
return false;
//
// vtresult should contain the new array now.
if ((VT_DISPATCH != vtResult.vt) || (NULL == vtResult.pdispVal))
return false;
//
// get IDispatchEx on returned IDispatch
CComQIPtr<IDispatchEx> theObject(vtResult.pdispVal);
if (!theObject)
return false;
rpObject = theObject;
return true;
}

Related

Creating a udf in bigquery to match array inputs

I'm trying to create a udf to match array's in biqquery, essentially if there are values of x array in y array then I want the result to be true.
e.g. match_rows([4,5,6], [5,6,7] should return true.
I've written this out but I keep getting syntax error's and i'm not familiar enough with what i'm doing to be able to debug so was hoping someone might be able to shed some light on what is going on.
Specific error = No matching signature for function match_rows for argument types: ARRAY, ARRAY. Supported signature: match_rows(ARRAY, ARRAY) at [44:1]
CREATE TEMP FUNCTION match_rows(arr1 ARRAY<FLOAT64>, arr2 ARRAY<FLOAT64>)
RETURNS BOOL
LANGUAGE js AS
"""
function findCommonElements2(arr1, arr2) {
// Create an empty object
let obj = {};
// Loop through the first array
for (let i = 0; i < arr1.length; i++) {
// Check if element from first array
// already exist in object or not
if(!obj[arr1[i]]) {
// If it doesn't exist assign the
// properties equals to the
// elements in the array
const element = arr1[i];
obj[element] = true;
}
}
// Loop through the second array
for (let j = 0; j < arr2.length ; j++) {
// Check elements from second array exist
// in the created object or not
if(obj[arr2[j]]) {
return true;
}
}
return false;
}
""";
WITH input AS (
SELECT STRUCT([5,6,7] as row, 'column2' as value) AS test
)
SELECT
match_rows([4,4,6],[4,7,8]),
match_rows(test.row, test.row)
FROM input ```
Instead of using ARRAY<FLOAT64> you are passing ARRAY<INT64> to your function. Therefore, the No matching signature error. In order to solve this error, you can just assign one of the values in your array as float, the syntax is below:
WITH input AS (
#notice that the first element is a float and so the whole array is ARRAY<FLOAT64>
SELECT STRUCT([5.0,6,7] as row, 'column2' as value) AS test
)
SELECT
#the same as it was done above, first element explicitly as float
match_rows([4.0,4,6],[4,7,8]),
match_rows(test.row, test.row)
FROM input
However, I have tested your function and I have realised that the syntax of your JavaScript UDF is not according to the documentation. The syntax should be as follows:
CREATE TEMP FUNCTION multiplyInputs(x FLOAT64, y FLOAT64)
RETURNS FLOAT64
LANGUAGE js AS """
//write directly your transformations here
return x*y;
""";
Notice that, it is not needed to especify function findCommonElements2(arr1, arr2), you can go directly to the body of your function because the function's name is defined after the statement CREATE TEMP FUNCTION.
In addition, I have also figured out that your function was not returning the desired output. For this reason, I have written a simpler JavaScript UDF which returns what you expect. Below is the syntax and test:
CREATE TEMP FUNCTION match_rows(arr1 ARRAY<FLOAT64>, arr2 ARRAY<FLOAT64>)
RETURNS BOOL
LANGUAGE js AS
"""
//array of common elements betweent the two arrays
var common_el= [];
for(i=0;i < arr1.length;i++){
for(j=0; j< arr2.length;j++){
if(arr1[i] = arr2[j]){
//add to the common_el array when the element is present in both arrays
common_el.push(arr1[i]);
}
}
}
//if the array of common elements has at least one element return true othersie false
if(common_el.length > 0){return true;}else{return false;}
""";
WITH input AS (
SELECT STRUCT([5.0,6,7] as row, 'column2' as value) AS test
)
SELECT
match_rows([4.0,4,6],[4.0,5,7]) as check_1,
match_rows(test.row, test.row) as check_2
FROM input#, unnest(test) as test
And the output,
Row check_1 check_2
1 true true
Below is for BigQuery Standard SQL
#standardSQL
CREATE TEMP FUNCTION match_rows(arr1 ANY TYPE, arr2 ANY TYPE) AS (
(SELECT COUNT(1) FROM UNNEST(arr1) el JOIN UNNEST(arr2) el USING(el)) > 0
);
SELECT match_rows([4,5,6], [5,6,7])

Hackerrank: Frequency Queries Timeout

I know that there are plenty of similar questions on SO on this specific thing, but I have a solution that works for all test cases EXCEPT for one (it gets timed out). Is there anyway I can make my code run faster or more efficiently... or do I need to start all over?
My logic:
I create three arrays.
Whenever there is a new value, I add it to my data array. At the same time, I add a "1" to my frequency array. The positions should be the same.
Whenever it is the same value, I simply increase the frequency value for the corresponding value by 1.
Whenever I need to return a value to say whether or not my array has a value with frequency "_", I just indexOf my frequency and tada if it's there I return 0, else I return 1.
function freqQuery(queries) {
var answer = new Array(),
data = new Array(),
frequency = new Array();
for (var i = 0; i < queries.length; i++){
var test = queries[i][0];
if (test == 1) { // first test
var place = data.indexOf(queries[i][1]);
if (place == -1){
data.push(queries[i][1]);
frequency.push(1);
} else {
frequency[place]++;
}
} else if (test == 2) { // second test
var place = data.indexOf(queries[i][1]);
if ((place != -1) && (frequency[place] > 0)) {
frequency[place]--;
}
} else if (test == 3) { // third test
if (frequency.indexOf(queries[i][1]) == -1) {
answer.push(0);
} else {
answer.push(1);
}
}
}
return answer;
}
Link: Hackerrank
Task is in category "Dictionaries and Hashmaps". Instead of data and frequency arrays create object with keys being data values and keys being frequencies. Instead of using indexOf which is O(n) you would be doing frequenciesMap[queries[i][1]] which is O(1).

change array passed to function

I pass 2 arrays to a function and want to move a specific entry from one array to another. The moveDatum function itself uses underscorejs' methods reject and filter. My Problem is, the original arrays are not changed, as if I was passing the arrays as value and not as reference. The specific entry is correctly moved, but as I said, the effect is only local. What do I have to change, to have the original arrays change as well?
Call the function:
this.moveDatum(sourceArr, targetArr, id)
Function itself:
function moveDatum(srcDS, trgDS, id) {
var ds = _(srcDS).filter(function(el) {
return el.uid === uid;
});
srcDS = _(srcDS).reject(function(el) {
return el.uid === uid;
});
trgDS.push(ds[0]);
return this;
}
Thanks for the help
As mentioned in the comments, you're assigning srcDS to reference a new array returned by .reject(), and thus losing the reference to the array originally passed in from outside the function.
You need to perform your array operations directly on the original array, perhaps something like this:
function moveDatum(srcDS, trgDS, id) {
var ds;
for (var i = srcDS.length - 1; i >= 0; i--) {
if (srcDS[i].uid === id) {
ds = srcDS[i];
srcDS.splice(i,1);
}
}
trgDS.push(ds);
return this;
}
I've set up the loop to go backwards so that you don't have to worry about the loop index i getting out of sync when .splice() removes items from the array. The backwards loop also means ds ends up referencing the first element in srcDS that matches, which is what I assume you intend since your original code had trgDS.push(ds[0]).
If you happen to know that the array will only ever contain exactly one match then of course it doesn't matter if you go forwards or backwards, and you can add a break inside the if since there's no point continuing the loop once you have a match.
(Also I think you had a typo, you were testing === uid instead of === id.)
Copy over every match before deleting it using methods which modify Arrays, e.g. splice.
function moveDatum(srcDS, trgDS, id) { // you pass an `id`, not `uid`?
var i;
for (i = 0; i < srcDS.length; ++i) {
if (srcDS[i].uid === uid) {
trgDS.push(srcDS[i]);
srcDS.splice(i, 1);
// optionally break here for just the first
i--; // remember; decrement `i` because we need to re-check the same
// index now that the length has changed
}
}
return this;
}

How to find index of object in a JavaScript array with jQuery

I am trying to find the index of an object in an array in jquery.
I cannot use jQuery.inArray because i want to match objects on a certain property.
I am using:
jQuery.inObjectArray = function(arr, func)
{
for(var i=0;i<arr.length;i++)
if(func(arr[i]))
return i;
return -1;
}
and then calling:
jQuery.inObjectArray([{Foo:"Bar"}], function(item){return item.Foo == "Bar"})
Is there a built in way?
Not sure why each() doesn't work for you:
BROKEN -- SEE FIX BELOW
function check(arr, closure)
{
$.each(arr,function(idx, val){
// Note, two options are presented below. You only need one.
// Return idx instead of val (in either case) if you want the index
// instead of the value.
// option 1. Just check it inline.
if (val['Foo'] == 'Bar') return val;
// option 2. Run the closure:
if (closure(val)) return val;
});
return -1;
}
Additional example for Op comments.
Array.prototype.UContains = function(closure)
{
var i, pLen = this.length;
for (i = 0; i < pLen; i++)
{
if (closure(this[i])) { return i; }
}
return -1;
}
// usage:
// var closure = function(itm) { return itm.Foo == 'bar'; };
// var index = [{'Foo':'Bar'}].UContains(closure);
Ok, my first example IS HORKED. Pointed out to me after some 6 months and multiple upvotes. : )
Properly, check() should look like this:
function check(arr, closure)
{
var retVal = false; // Set up return value.
$.each(arr,function(idx, val){
// Note, two options are presented below. You only need one.
// Return idx instead of val (in either case) if you want the index
// instead of the value.
// option 1. Just check it inline.
if (val['Foo'] == 'Bar') retVal = true; // Override parent scoped return value.
// option 2. Run the closure:
if (closure(val)) retVal = true;
});
return retVal;
}
The reason here is pretty simple... the scoping of the return was just wrong.
At least the prototype object version (the one I actually checked) worked.
Thanks Crashalot. My bad.

strange behaviour with javascript map/object

I am using javascript map to store keys and values. Later on I check if specified key is present in map or not, but it sometimes gives correct result but sometimes it don't. I tried to print the map using console.log(mapname), it shows all keys, but if I try to check if some specified key is present - sometimes it gives wrong answer.
Am using following code:
// following code is called n times in loop with different/same vales of x
myMap : new Object();
var key = x ; // populated dynamically actually
myMap[key] = "dummyval";
if(myIdMap[document.getElementById("abc").value.trim()] != null)
alert('present');
else
alert('not present');
What can be the possible problem? Can alphanumericstring/integers values can be used as keys?
var myMap = {};
// ...
//simulating your inner loop; is this close enough?
//Note: A MacGuffin is a plot device to move the story forward.
// Like a time machine, or a space ship.
for (var key in macGuffin) {
myMap[key] = macGuffin.processItem(key);
}
// ...
var myKey = document.getElementById("abc").value.trim();
//Note the use of !==; false == null == 0 == '', but null only === null
if (myMap[myKey]!==null)
//Better? (typeof myMap[myKey] != 'undefined')
console.log('present');
else
console.log('not present');
Be sure you're aware that the value stored with the key could be null, as the following code shows:
a = {
"mykey": null
}
for (x in a) {
if (a[x] == null) {
alert(x + " is null!");
}
} // produces a single alert: "mykey is null!"
I think you have to store keys in some temporary table or objects like session or cookies for future retrieve.
you have dynamically store x position value in mymap[key] but every time it loads and overwrite your new values. you something doing wrong there..

Categories