How avoid having double loop? - ios

From healthkit, I will receive some datas, for example steps data.
I save in my server this datas. Il have then an array of datas:
1.- startDate, endDate, value
2.- startDate, endDate, value
etc
It can be very lot of values in my server.
Then I get the values in the Healthkit. I have lot of values. Values who are in the server and new values.
I want to upload to the server only the new values.
I do so:
for each value in my server {
for each value in healthkit{
if(startDate, endDate and value are not equal to the value in the server){
then save the value in the server
}
}
}
The algo will work, but it's very very slow. I can have lot of values in the two systems. Most of them the same in the two places.
Have you an idea how to do better?
I cannot save a flag in the healthKit.
I'm using ionic with angular 4 and typescript.

This answer assumes that the data on your server and the data from healthKit are sorted in the same way, or that you can sort them without affecting anything else.
For example you can sort your data on the server and the data from healthKit by StartDate, break ties by EndDate, then break ties by value.
Now you have two sorted arrays that you want to merge. The idea is to use the merge function of merge sort explained here.
You will end up with an array containing all the data without repetitions, which you can save on your server.
Edit:
void mergeArrays(int arr1[], int arr2[], int n1,
int n2, int arr3[])
{
int i = 0, j = 0, k = 0;
// Traverse both array
while (i<n1 && j <n2)
{
// Check if current element of first
// array is smaller than current element
// of second array. If yes, store first
// array element and increment first array
// index. Otherwise do same with second array
if (arr1[i] < arr2[j])
arr3[k++] = arr1[i++];
else if (arr2[j] < arr1[i])
j++;
else
j++,i++;
}
// Store remaining elements of first array (healthKit Array)
while (i < n1)
arr3[k++] = arr1[i++];
}

I think I need a bit more context to understand the problem. By "new" values, are you including entries that have been modified, or just new entries that have been added?
If you only need the new values, and they are being added to the end of the array on the client side, then you can just take them from the end of the array.
const new_count = healthkit.length - myserver.length, // number of new entries
index_start = healthkit.length - new_count, // index in healthkit array where new entries start
new_values = healthkit.slice(index_start); // new array containing only new entries
addNewValues(new_values);
Now you have the new values in a separate array, and you can update them to the server.
If it is the case that you do need to update values that have changed, you can iterate over both arrays (only once, and at the same time) and find the differences. I am going to assume that the entries are in the same index for both arrays. I'm also assuming the "value" key is the only one you want to compare. You can do the following to find the values that have been modified.
const modified_values = [];
myserver.forEach(function(entry, i) { // iterate over server array
let healthkit_entry = healthkit[i]; // get healthkit entry with same index
if(entry.value !== healthkit_entry.value) { // see if value has changed
modified_values.push(healthkit_entry); // if changed, add to modified array
}
});
updateModifiedValues(modified_values);
Now the modified_values array has all of the modified entries from the healthkit array.

Related

Airtable Scripting block - Batch copy a field to another field in same table (10,000 records)

I'm trying to copy one field to another field in the same table with 10,000 + records, in batches of 50 using the Scripting App.
What am I doing wrong in this code block? It only copies the first record. If I remove the await, it'll copy 15 records then stop.
let table = base.getTable('Merchants');
let view = table.getView('Grid view');
let query = await view.selectRecordsAsync();
let records = query.records;
updateLotsOfRecords(records);
async function updateLotsOfRecords(records) {
let i = 0;
while (i < records.length) {
const recordBatch = records.slice(i, i + 50);
for (let record of recordBatch) {
let sourceValue = record.getCellValue('Merchant');
await table.updateRecordAsync(record, { 'LogoBase64': sourceValue });
}
i += 50;
}
}
you should use updateRecordsAsync function, not updateRecordAsync
When using single update function in loop, there is no sense to divide it into batches.
You exceed some limit of calls per second, that's why it stops.
For multiple updates, you need to use updateRecordsAsync, like this
while (recordsToWrite.length > 0) {
await updates.updateRecordsAsync(recordsToWrite.slice(0, 50));
recordsToWrite = recordsToWrite.slice(50);
}
Data that you should pass to it, more complex. I learned JS for 3 months and still have difficulties understandins all these "arrays of arrays of objects, passed via object's property". But that's the key to unerstand JS.
It's quite hard to leave basic/pascal habits, with plenty of inserted FOR loops, and GOTO sometimes))
I think, you already found the answer for 2 months, so my answer may be useless, but when i write it here, maybe i understand it better for myself. And help to some beginners also.
For single write, you pass (record, Object), where object is {field:'Value}
For multiple, you should pass
Array of Objects, where
Object is {id:recordID, fields:{object2}} , where
object2 is array of obj3 [ {obj3},{obj3}, {obj3} ], where
obj3 is a { 'Name or ID of field': fieldvalue }
you script might be:
let query = await view.selectRecordsAsync();
let updates=query.records.map(rec=>{
Map method can be applied for arrays, and 'query.records' is array of records. Here
'rec' is loop variable inside this "arrowfunction"
now let's create obj3 , in our case { 'Name or ID of field': fieldvalue }
{'LogoBase64':rec.getCellValue('Merchant')}
wrap it into fields property
fields:{'LogoBase64':rec.getCellValue('Merchant')}
and add record id
wrapping as Object.
To avoid complex string with linebreaks, and to make object creation easier, we can do it with function:
{rec.id, fields:{'LogoBase64':rec.getCellValue('Merchant')}}
fuction myObj(rec){return {rec.id, fields:{'LogoBase64':rec.getCellValue('Merchant')}}
map(rec=>myObj(rec)) - can be written as map(myObj)
we need array of objects, and map method gets first array, doing something with each element and return other array, of results. like we need.
and now finally we get
let table = base.getTable('Merchants');
let view = table.getView('Grid view');
let query = await view.selectRecordsAsync();
function myObj(rec){return {'id':rec.id,'fields':{'Logobase64':rec.getCellValue('Merchant')}}};
let updates=query.records.map(myObj);
while (updates.length > 0) {
await table.updateRecordsAsync(updates.slice(0, 50));
updates = updates.slice(50); }

Assign matching column header from table with match criteria to a string list in an arrayformula in Google Sheets

In a Google Sheet I have a first tab with data entries in column A. I would like to assign a category to each entry in column B using an arrayformula. The category is supposed to be determined in the following way: If in the second tab one of the strings of a column matches the entry, the header of the column is supposed to be assigned as the category.
I have the feeling that there should be a way to do this with the query function, but can't figure it out.
This is an example sheet. I am happy to slightly change the setup of the sheet if the solution requires it but would like to avoid blowing up the sheet. Also I am interested in the simplest possible solution.
Approach
I would use a custom function in this case so that you can gain more control on the logic and structure of your sheet.
First you should change move the categories sheet into a rows structure since Google Sheets gets the values in that way.
Now let's build the function that extracts the data from the categories and assign every name to the corresponding one.
This is a custom function so I build the proper docs header so that it will be visible in your Spreadsheet context.
/**
* Assign the name the corresponding category.
*
* #param {input} the name to assign to the category
* #return The matching category
* #customfunction
*/
function MATCHCATEGORY(input) {
// if the input is from ARRAYFORMULA I will reiterate along the array with a map
if (input.map) {
return input.map(MATCHCATEGORY)
} else {
// Recursion base case
// I will get the categories rows
var rows = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("CATEGORIES").getDataRange().getValues();
var category = "";
// Now I will map a string matching function along the rows
rows.map(row => {
let no_blanks = row.filter(value => value != "");
for (var i=1; i<no_blanks.length; i++) {
if (input.includes(row[i])) {
category = row[0];
break;
}
}
});
// Finally I return the category
return category
}
}
Now we can use our custom function inside an ARRAYFORMULA:
=ARRAYFORMULA(MATCHCATEGORY("ENTRY COLUMN RANGE"))

Returning Array of Objects shows only one object in next step

Ive been working on a code zap step to make a get call to an api endpoint, and then transform the response into key pair objects to pass to further steps in zapier.
var fileIds = [],
tempData = [],
newData = [],
obj = [];
fetch('zoho getClientById endpoint'+inputData.id)
.then(function(res) {
return res.json();
})
.then(function(json) {
tempData = json.response.result.Leads.row.FL;
for(var i = 0; i < tempData.length; i++ ){
tempVal = tempData[i].val;
newData = tempData[i].content;
let allData = {};
allData[tempVal] = newData;
obj.push(allData)
}
callback(null, obj);
}).catch(callback);
Above is more or less the code Im using. It works, except that when the array of objects comes out of the step, only the first object is available for steps after. Im not certain if this is because of the way Im handling it, or if it's something with how zapier works.
Edit: What's interesting is I can use the log statement to see results in the meta data, and it shows the full array of objects.
So, after a little extra reading, it seems that zapier will take each object in an array and try trigger another step with it. I updated my set up to just take the empty object I initially created and set a key with a value based off the value and content responses from my other response.

Printing Values of dictionary using loop

I trying to print values of dictionary in serial order, like One Two Three but using below code is displaying random value from dictionary. How can i rectify it! Thank you !!
var someDict:[Int:String] = [1:"One", 2:"Two", 3:"Three"]
for keys in someDict.keys
{
//print (keys)
print(someDict[keys]!)
}
Output:
Two
Three
One
And when i print keys. it shows 2 3 1 :(
The keys of the dictionary is no order.
So you can use other Array to save key to ensure order.
Or..
for key in someDict.keys.sort ({
$0 < $1
}) {
print(someDict[key])
}
Chose height performance way according to your demand.

Arrange messed up array

In my app there's an array called newArray that contains a lot of numbers. The app sends the numbers to a server and puts them into a new array called stalkers. My problem is that some names are returned faster than others and the names in the stalkers array could be in any order. How can I make the order of the returned data be the same as the corresponding numbers in newArray?
So let's say newArray = ["10002938948","46789890","2984829389"]. My code sends it to a server and downloads the names. For "10002938948", the server returns "Tim". For "46789890" the server returns "Louis". For "2984829389", the server returns "Rob". Then it should be stalkers = ["Tim","Louis","Rob"], but because they are not downloaded in order (one page loads faster than another), stalkers = ["Louis","Rob","Tim"].
It should be something like I could give each number an id, so I can just sort the id's in the right order in the stalkers array.
Create a dictionary using the elements of newArray as the keys and the arriving name values as the values. When all the results have arrived, create the stalkers array by cycling through newArray, thus keeping the order.
let newArray=["10002938948","46789890","2984829389"]
// For 10002938948, the server returns Tim.
// For 46789890 the server returns Louis.
// For 2984829389, the server returns Rob.
// Then it should be stalkers=["Tim","Louis","Rob"]
// As they arrive, store each result in a dictionary:
var d = [String:String]()
// note that the order of the next three statements doesn't matter!
// Tim arrives for 10002938948
d["10002938948"] = "Tim"
// Louis arrives for 46789890
d["46789890"] = "Louis"
// Rob arrives 2984829389
d["2984829389"] = "Rob"
// now assemble stalkers by cycling through newArray
let stalkers = newArray.map{d[$0]!} // ["Tim","Louis","Rob"]

Resources