Currently I am getting the coordinates (lat, long) from the device and storing pushing them in an array to be something like the following:
[35.23223,-5.293222]
But, in several times those coordinates get duplicated (maybe the device sent the same coords, etc...)
For that, I've implemented the below:
var uniqueCoords = [Array.from(new Set(coords))];
Which on each call, digs into the existing array and remove whatever duplicated coordinate.
However, that causes serious issues, especially when having (for example) a new latitude and an old longitude (i.e [35.23223,-5.319399]) or vice-versa.
On this particular example uniqueCoords will dig into the array find that 35.23223 is duplicated and remove and it will leave -5.319399 alone, by the end of the journey It may end having :
[35.23223,-5.293222,-5.319399]
What I want here is to remove (lat/long) only when both, the pair lat & long, is exactly the same as a pair that existing already on the array.
Current code:
this.storage.get('route').then((route) => {
let uniqueCoords: any = [Array.from(new Set(route))];
uniqueCoords.push(latLng.lat, latLng.lng);
this.storage.set('routeTaken', uniqueCoords);
}).catch((error) => {
this.presentAlert(error)
})
Array of raw data:
[35.7790733,-5.8453983,35.779335,-5.8465283,35.779705,-5.84782,35.7787533,-5.8482083,35.7780167,-5.8491983,35.77782,-5.8504883,35.7774783,-5.8518267,35.776955,-5.852945,35.7765,-5.8541383,35.7761667,-5.855425,-5.8566467,35.77628,-5.8579367,35.7763233,-5.8588633,35.776435,-5.8591367,35.7767667,-5.8594817,35.7776267,-5.8586933,35.7785467,-5.8577233,-5.8585467,35.77949,-5.8597567,35.7797183,-5.86081,35.7805917,-5.8606533,35.7817533,-5.8606867,35.7826217,-5.8618667,35.78295,-5.8636367,35.7834217,-5.8643667]
You could join the coordinates and split them after uniqueifying.
var coordinates = [[35.23223, -5.293222], [35.23223, -5.319399], [35.23223, -5.319399]],
unique = Array.from(new Set(coordinates.map(a => a.join('|'))), s => s.split('|').map(Number));
console.log(unique);
.as-console-wrapper { max-height: 100% !important; top: 0; }
If you take the array of a point, like [35.23223, -5.293222], you insert into the point an object. Another pont with the same coordinates generates a new object, which is not equal to the former array. For making both equal, you need some stringifing the array. This could be a JSON string, or some more simple like joining with a separator.
The same with continuous coordinates in a single array.
var coordinates = [35.23223, -5.293222, 35.23223, -5.319399, 35.23223, -5.319399],
unique = Array.from(new Set(coordinates
.reduce((r, a, i) => (i % 2 ? r[r.length - 1].push(a) : r.push([a]), r), [])
.map(a => a.join('|'))), s => s.split('|').map(Number));
console.log(unique);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Here's a very logical, step by step, no fancy shit way of doing it
var coords = [
35.7790733, -5.8453983,
35.779335, -5.8465283,
35.7790733, -5.8453983,
35.779705, -5.84782
];
var temp = [];
var unique = [];
var uniqueCoords = [];
for (var i = 0; i < coords.length; i += 2) {
temp.push(coords[i] + '---' + coords[i + 1]); // create some strings
}
for (var i = 0; i < temp.length; i++) {
if (unique.indexOf(temp[i]) === -1) // remove duplicates
unique.push(temp[i]);
}
for (var i = 0; i < unique.length; i++) { // split the strings back into array
uniqueCoords = uniqueCoords.concat(unique[i].split('---'));
}
console.log(uniqueCoords)
if you just switch to format of ['lat,lng', ...] (as a string) your code will work fine.
But you should check for duplicates after adding new coords. Currently you do it before.
Assuming uniqueCoords is formatted as [lat,long,lat,long,...] and new Set generates something similar to [lat, long]
let coordSet = Array.from(new Set(coords));
// search the uniqueCoords list
let exists = uniqueCoords.find(function (itemCoord, idx, arr) {
// only check on every other coord
// if the previous coord being looped over matches the first coord in the coord set
// if the current coord being looped over matches the second coord in the coord set
// return true indicating the coord-set exists
if ((idx % 2) === 1) && (arr[idx -1] === coordSet[0]) && (itemCoord === coordSet[1]) {
return true;
}
});
// if a matching coord-set wasn't found, push the new set
if (!exists) {
// called with apply so each item in coordSet
// is appended to the uniqueCoords Array
uniqueCoords.push.apply(uniqueCoords, coordSet);
}
this.storage.get('route').then((route) => {
if (route.length < 2 || route[route.length - 2] != latLng.lat || route[route.length - 1] != latLng.lng) {
uniqueCoords.push(latLng.lat, latLng.lng);
this.storage.set('routeTaken', uniqueCoords);
}
}).catch((error) => {
this.presentAlert(error)
})
Related
let x=[1,2,6,3,5,5,5,4,4];
let y=[3,4,3,5,2,4,4,2,6];
expected_x=[1,2,6,3,5,5,4]
expected_y=[3,4,3,5,2,4,6]
Think of x and y as coordinates.[1,3] will be first point and [4,6] will be last point.
If a [X,Y] has duplicates, only one of the [X,Y] will be displayed in the expected output (no duplicate). And if, there is a mirror like [X,Y] which is a mirror of [Y,X] with both at the same index.
This is the code I have written for just one array to make the array unique. However, I am unsure on how to use it with 2 seperate arrays representing x and y coordinates. Any help will be appreciated :)
let chars = ['A', 'B', 'A', 'C', 'B'];
let uniqueChars = [...new Set(chars)];
console.log(uniqueChars);
Use this:
let x=[1,2,6,3,5,5,5,4,4];
let y=[3,4,3,5,2,4,4,2,6];
const coordinates = [];
let i = -1;
while ( x[++i] ) {
const c = {
index: i,
value: [x[i], y[i]]
}
coordinates.push(c);
}
const coordArray = coordinates.reduce((p, next) => {
if (!p.values.includes(JSON.stringify(next.value)) && !p.values.includes(JSON.stringify([...next.value].reverse()))) {
p.values.push(JSON.stringify(next.value));
p.indexes.push(next.index);
}
return p;
},{
indexes: [],
values: []
})
coordArray.values = coordArray.values.map(JSON.parse)
console.log(coordArray)
You can use a for loop and iterate both arrays together, since they have the same length (being an x,y pair) to each other.
You can also keep a "history" of duplicates and mirrors. Then all you need to do while iterating is check the history. If there is no match, append the current to the result arrays, then update the history.
let x=[1,2,6,3,5,5,5,4,4];
let y=[3,4,3,5,2,4,4,2,6];
let h=[]; // history
let rx = []; // result x
let ry = []; // result y
for (let i = 0; i < x.length && i < y.length; i++) {
// The if line (with include()) would be nice if it worked, but it didn't because of
// always returning false.
// Instead I will have to manually search.
// if (h.includes([x[i], y[i]]) || h.includes([y[i], x[i]])) {
let found = false;
for (let s = 0; s < h.length; s++) {
// check for duplicate
if (h[s][0] == x[i] && h[s][1] == y[i]) {
found = true;
break;
}
// check for mirror
if (h[s][0] == y[i] && h[s][1] == x[i]) {
found = true;
break;
}
}
if (found) {
// do nothing, its a duplicate or mirror
console.log("duplicate or mirror detected on index " + i);
}
else {
// update results
rx.push(x[i]);
ry.push(y[i]);
// update history
h.push([ x[i], y[i] ]);
}
}
console.log("rx: " + rx);
console.log("ry: " + ry);
In short, .include() would have been nice, but apparantly the array by reference broke my intended logic. I don't know. But the above separated those concerns out by a literal search of "history", which would alter the "found" boolean to know whether a duplicate or mirror existed.
Obviously this code could like be shortened into less than 10 or 7 lines, but I wanted to work on it because it was interesting and the approach used demonstrates how regular for loops could be used to solve such "iteration" problems.
Hopes it helps.
I've two strings in JavaScript like
var description = "<DP_A>.<Del.Dce Lks.{Link}>.<Pl.Rrs Bk 0.310-PT-304_({strm})>"
var Title = "<DP_A>.<Del.Dce Lks.1>.<Pl.Rrs Bk 0.310-PT-304_(1)>"
here {Link} and {strm} are placeholders or more likely whatever comes between { } is placeholder
I need to compare both string like description and Title to find placeholder values, Output needs to be like
{"Link" : 1, "strm" : 1 }
or array
[{Link" : 1, "strm" : 1}]
I've tried some RegEx but not working, any help??
if (description.includes("{")) {
var found = [], // an array to collect the strings that are found
rxp = /{([^}]+)}/g,
curMatch;
while (curMatch = rxp.exec(description)) {
found.push(curMatch[1]);
}
}
I'm able to get array of Placeholders but not able to find values into title string.
You could get all parts and then splice the values out of the title string.
"<DP_A>.<Del.Dce Lks.{Link}>.<Pl.Rrs Bk 0.310-PT-304_({strm})>",
"<DP_A>.<Del.Dce Lks. 1 >.<Pl.Rrs Bk 0.310-PT-304_( 1 )>";
function getParts(pattern, values) {
var result = {}, value, p1, p2 = 0;
(pattern.match(/[^{}]+/g) || []).forEach((s, i, a) => {
if (i % 2) return Object.assign(result, { [s]: value });
p1 = values.indexOf(s, p2),
p2 = values.indexOf(a[i + 2], p1);
value = values.slice(p1 + s.length, p2 === -1 ? undefined : p2);
});
return result;
}
var description = "<DP_A>.<Del.Dce Lks.{Link}>.<Pl.Rrs Bk 0.310-PT-304_({strm})>{last}",
title = "<DP_A>.<Del.Dce Lks.abcdef>.<Pl.Rrs Bk 0.310-PT-304_(ghijklöööö)>fubar";
console.log(getParts(description, title));
With a for statement and reusing known positions.
function getParts(pattern, values) {
var parts = pattern.match(/[^{}]+/g),
result = {}, p1, p2, i;
if (!parts || parts.length < 2) return {};
p1 = values.indexOf(parts[0]);
for (i = 1; i < parts.length; i += 2) {
p2 = values.indexOf(parts[i + 1], p1);
Object.assign(result, { [parts[i]]: values.slice(p1 + parts[i - 1].length, p2 === -1 ? undefined : p2) });
p1 = p2;
}
return result;
}
var description = "<DP_A>.<Del.Dce Lks.{Link}>.<Pl.Rrs Bk 0.310-PT-304_({strm})>{last}",
title = "<DP_A>.<Del.Dce Lks.abcdef>.<Pl.Rrs Bk 0.310-PT-304_(ghijklöööö)>fubar";
console.log(getParts(description, title));
Use replace:
var description = "<DP_A>.<Del.Dce Lks.{Link}>.<Pl.Rrs Bk 0.310-PT-304_({strm})>"
const obj = {
Link: 1,
strm: 2
};
const res = description.replace(/{(.*?)}/g, m => obj[m.slice(1, -1)]);
document.write(res);
Okay, this is far more complex than I actually expected.
I'm not actually that good at this kind of operations, but here is a "working" solution: you may want to rewrite it a bit, but still, the concept is actually fair to me.
The steps followed to achieve the results are:
Acquire all the indexes of "{". I've used a function generator below, but you may use whathever other criteria you want. The goal is to acquire the starting bracket of each match.
loop each matched bracket, look for the closing bracket and acquire the character just after it in the description string.
perform the value match upon the Title string.
Continue by applying currently matched values to update the offsets.
Map the result to collect the desired output: I've intentionally returned an array of items because a placeholder may exist twice.
Some side notes:
The below script, as mentioned above, won't take care of limit cases like "{hello{world}".
The below script can be improved by matching both the previous character and the next character.
The below script might fail in some situations, it just happens to work in this case, but I didn't test it with limit cases.
var description = "<DP_A>.<Del.Dce Lks.{Link}>.<Pl.Rrs Bk 0.310-PT-304_({strm})>";
var Title = "<DP_A>.<Del.Dce Lks.1>.<Pl.Rrs Bk 0.310-PT-304_(1)>";
// Acquire all the indexes of every "{".
// BEWARE: This will actually fail if the description is "<{LLT{hello}", but you may change this.
const descriptionLookupIndexes = [].concat(...(function*(){
for (var i = 0; i < description.length; i++) {
if (description[i] === "{") yield [i];
}
})());
let matches = [];
descriptionLookupIndexes.forEach((i, index) => {
// acquire the description by replacing the currently known values.
let _replacedDescription = description;
let _replacedDescriptionIndex = i - matches.reduce((a,b) => a + b.amount, 0);
// This foreach will replace the placeholders already found with their respective values.
matches.forEach(k => {
let splitted = _replacedDescription.split('');
splitted.splice(k.from, k.amount, [k.value.split('')]);
_replacedDescription = splitted.join('');
});
// Acquire the relevant portion of the string.
const needle = _replacedDescription.substring(_replacedDescriptionIndex, _replacedDescription.length);
// Look for the next character after the first } occurrence in the current substring.
const nextChar = needle[needle.indexOf("}") + 1];
// Acquire the relevant substring for the title.
const titleNeedle = Title.substring(_replacedDescriptionIndex, Title.length);
matches.push({
from: _replacedDescriptionIndex,
amount: needle.match(/[^{\}]+(?=})/g)[0].length + 1,
needle: needle.match(/[^{\}]+(?=})/g)[0],
value: titleNeedle.substring(0, titleNeedle.indexOf(nextChar))
});
});
// Matches is now the array with all the occurrences, let's just map it to acquire a new array of objects with the desired format.
// BEWARE: If multiple keys exists, they will be mapped to an array.
const res = matches.reduce((acc, next) => {
acc[next.needle] = acc[next.needle] || [];
acc[next.needle].push({
[next.needle]: next.value
});
return acc;
}, {});
console.log(res);
This question already has answers here:
Get the element with the highest occurrence in an array
(42 answers)
Closed 4 years ago.
I am a beginner in JavaScript and I was trying to write code for finding the mode. My code is running but it can find the mode only when it is written consecutively. But when there is an array like this a = [1,2,3,4,5,2], it can not find the mode.
As I am a beginner I do not want to write anything complex but want to learn it in the simplest way. Can anyone please help me in this purpose?
list = [1,2,3,4,5,6,7,7]
var empty = []
i = 0
max = 0
while (i<list.length){
if (list[i]==list[i+1]){
empty = list[i]
i += 1
}else{
i +=1
}
}
document.write(empty)
Your code assumes that the parameter array is pre-sorted which is a risky and limiting assumption, and only appears to work on sorted arrays (counterexample: [1,1,1,7,7] incorrectly reports 7 as the mode).
If you wish you persist with this approach, you're on the right track, but you'll need to keep track of the current/best streaks, current/best elements and perform a final check for longest streak before returning the result:
var mode = a => {
a = a.slice().sort((x, y) => x - y);
var bestStreak = 1;
var bestElem = a[0];
var currentStreak = 1;
var currentElem = a[0];
for (let i = 1; i < a.length; i++) {
if (a[i-1] !== a[i]) {
if (currentStreak > bestStreak) {
bestStreak = currentStreak;
bestElem = currentElem;
}
currentStreak = 0;
currentElem = a[i];
}
currentStreak++;
}
return currentStreak > bestStreak ? currentElem : bestElem;
};
console.log(mode([1,2,3,4,5,6,7,7]));
console.log(mode([1,1,1,4,5,6,7,7]));
console.log(mode([1,2,3,3,3,6,3,7]));
console.log(mode([1,3,3,4,5,2,2,1]));
console.log(mode([]));
Having said that, sorting is a non-linear operation, so I recommend trying another approach.
The idea is to keep a count of occurrences for each item in the array using an object, then take the element with the highest count. I used reduce to perform these two operations:
const mode = a =>
Object.values(
a.reduce((count, e) => {
if (!(e in count)) {
count[e] = [0, e];
}
count[e][0]++;
return count;
}, {})
).reduce((a, v) => v[0] < a[0] ? a : v, [0, null])[1];
;
console.log(mode([1,2,3,4,5,6,7,7]));
console.log(mode([1,1,1,4,5,6,7,7]));
console.log(mode([1,2,3,3,3,6,3,7]));
console.log(mode([1,3,3,4,5,2,2,1]));
console.log(mode([]));
Or, the same thing, written without reduce for readability:
const mode = a => {
const count = {};
a.forEach(e => {
if (!(e in count)) {
count[e] = 0;
}
count[e]++;
});
let bestElement;
let bestCount = 0;
Object.entries(count).forEach(([k, v]) => {
if (v > bestCount) {
bestElement = k;
bestCount = v;
}
});
return bestElement;
};
console.log(mode([1,2,3,4,5,6,7,7]));
console.log(mode([1,1,1,4,5,6,7,7]));
console.log(mode([1,2,3,3,3,6,3,7]));
console.log(mode([1,3,3,4,5,2,2,1]));
console.log(mode([]));
Note that these approaches don't choose the same mode in case of ties. You may wish to add an array to keep track of all modes, or change your algorithm to pick the first or last occurring mode to suit your needs.
use a hash
list = [1,2,3,4,5,6,7,7]
counts = {}
list.forEach(function(e) {
if(counts[e] === undefined) {
counts[e] = 0
}
counts[e] += 1
})
which results in this:
{1:1,2:1,3:1,4:1,5:1,6:1,7:2}
This related question deals with finding the max and min in a hash, which is essentially what you do at the end of this.
Fast way to get the min/max values among properties of object
I am working on dictionary application written with react-native.
When I want to filter the array from the search box, I wrote below function. This is working quite good when I test with 2000 word list. But when the word list goes to thousands the search speed is really slow.
So, how can I improve this search function?
//Filter array when input text (Search)
let filteredWords = []
if(this.state.searchField != null)
{
filteredWords = this.state.glossaries.filter(glossary => {
return glossary.word.toLowerCase().includes(this.state.searchField.toLowerCase());
})
}
There are multiple factors that are making this code slow:
You're using filter() with a lambda. This adds a function call overhead for each item being searched.
You're calling toLowercase() on both strings before calling includes(). This will allocate two new string objects for every comparison.
You're calling includes. For some reason the includes() method is not as well optimized in some browsers as indexOf().
for loop (-11%)
Instead of using the filter() method, I recommend creating a new Array and using a for loop to fill it.
const glossaries = this.state.glossaries;
const searchField = this.state.searchField;
const filteredWords = [];
for (let i = 0; i < glossaries.length; i++) {
if (glossaries[i].toLowerCase().includes(searchField.toLowerCase())) {
filteredWords.push(glossaries[i]);
}
}
toLowerCase allocations (-45%)
Memory allocation is expensive due to the fact that JavaScript uses garbage collection mechanism for freeing used memory. When a garbage collection is performed the whole program is paused while it tries to finds memory which is not used anymore.
You can get rid of the toLowerCase() (inside the search loop) completely by making a copy of the glossary everytime the glossary is updated, which I assume is not often.
// When you build the glossary
this.state.glossaries = ...;
this.state.searchGlossaries = this.state.glossaries.map(g => g.toLowerCase());
You can also remove the toLowerCase() on the searchText by calling it once before the loop. After these changes, the code will look like:
const glossaries = this.state.glossaries;
const searchGlassaries = this.state.searchGlossaries;
const searchField = this.state.searchField.toLowerCase();
const filteredWords = [];
for (let i = 0; i < glossaries.length; i++) {
if (searchGlassaries[i].includes(searchField)) {
filteredWords.push(glossaries[i]);
}
}
indexOf() instead of includes() (-13%)
I am not really sure why this is the case, but tests show that indexOf is a lot faster than includes.
const glossaries = this.state.glossaries;
const searchGlassaries = this.state.searchGlossaries;
const searchField = this.state.searchField.toLowerCase();
const filteredWords = [];
for (let i = 0; i < glossaries.length; i++) {
if (searchGlassaries[i].indexOf(searchField) !== -1) {
filteredWords.push(glossaries[i]);
}
}
Overall the performance has improved by 70%.
I got the performance percentages from https://jsperf.com/so-question-perf
Optimize the algorithm
In the comments you said you would like an example of optimizations that can be done when the requirements are loosened to only match words that start with the search text. One way to do this is a binary search.
Let's take the code from above as starting point. We sort the glossaries before we store it in the state. For sorting case insensitively, JavaScript exposes the Intl.Collator constructor. It provides the compare(x, y) method that returns:
negative value | X is less than Y
zero | X is equal to Y
positive value | X is greater than Y
And the resulting code:
// Static in the file
const collator = new Intl.Collator(undefined, {
sensitivity: 'base'
});
function binarySearch(glossaries, searchText) {
let lo = 0;
let hi = glossaries.length - 1;
while (lo <= hi) {
let mid = (lo + hi) / 2 | 0;
let comparison = collator.compare(glossaries[mid].word, searchText);
if (comparison < 0) {
lo = mid + 1;
}
else if (comparison > 0) {
hi = mid - 1;
}
else {
return mid;
}
}
return -1;
}
// When you build the glossary
this.state.glossaries = ...;
this.state.glossaries.sort(function(x, y) {
return collator.compare(x.word, y.word);
});
// When you search
const glossaries = this.state.glossaries;
const searchField = this.state.searchField.toLowerCase();
const filteredWords = [];
const idx = binarySearch(glossaries, searchField);
if (idx != -1) {
// Find the index of the first matching word, seeing as the binary search
// will end up somewhere in the middle
while (idx >= 0 && collator.compare(glossaries[idx].word, searchField) < 0) {
idx--;
}
// Add each matching word to the filteredWords
while (idx < glossaries.length && collator.compare(glossaries[idx].word, searchField) == 0) {
filteredWords.push(glossaries[idx]);
}
}
As the question doesn't seem to belong on CodeReview, I think there are a few things that you can do to make your code drastically faster [citation needed]:
Cache that call to this.state.searchField.toLowerCase() as you don't need to call it on every iteration.
Use regular old for loops instead of flashy-but-slow Array functions.
And here is the final result:
let filteredWords = []
if(this.state.searchField != null) {
let searchField = this.state.searchField.toLowerCase(),
theArray = this.state.glossaries; // cache this too
for(let i = 0, l = theArray.length; i < l; ++i) {
if(theArray[i].word.toLowerCase().includes(searchField)) {
filteredWords.push(theArray[i]);
}
}
}
Edit:
If you want to search for glossaries whose word start with searchField, then use indexOf === 0 instead of includes as the condition like this:
if(theArray[i].word.toLowerCase().indexOf(searchField) === 0) {
var availableMarketGroups = {};
angular.forEach(function (market) {
if (availableMarketGroups[market.group_id]) { // market.group_id is not sorted id
availableMarketGroups[market.group_id].count++;
}
});
market.group_id - number ,not sorted, and sometimes its duplicates
availableMarketGroups[market.group_id].count - its length
Lets see the image for more info.
The numbers of the market groups don't represent real amount of markets.
availableMarketGroups[market.group_id].count show - 15 ,but in real it should be 5 (5 groups) ,because market.group_id is duplicates.
How can i ignore duplicated market.group_id values in if statement ?
var availableMarketGroups = {};
var groupsProcessed = [];
angular.forEach(availableMarketGroups, function(marketGroup) {
if (groupsProcessed.indexOf(marketGroup.group_id) < 0) {
groupsProcessed.push(marketGroup.group_id);
availableMarketGroups[market.group_id].count++;
}
});
Answer for counting unique array elements is to make a function as given below.
var counts = {};
for (var i = 0; i < arr.length; i++) {
counts[arr[i]] = 1 + (counts[arr[i]] || 0);
}
It will return the unique counts of elements in the array.
Reference : Count unique elements in array without sorting
Hard to say without your data. Generally speaking, you should be able to reduce this down to the unique Set of group_ids:
const uniqueGroups = markets.reduce(function(set, market) {
if (!set.has(market.group_id)) {
set.add(market.group_id);
}
return set;
}, new Set());
console.log('Unique group count: ', uniqueGroups.size);
You can use underscore:
var newObj = _.uniq(market, function(p){ return p.group_id; });