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.
Related
I'm a beginner trying to learn JS, I've got some basic knowledge.
I wrote a function to realize insertion sort on a given array (the array is passed on to the function as a parameter).
When I initialize the array and give it value, e.g,
sampleArray = [1,35,73,234,1,1,356];
and pass that to my function, it works perfectly.
however, if I try to pass on an array filled by user input - or an array that was merged out of two given arrays (my original assignment),
it doesn't work - no exceptions or errors, it just... doesn't sort as expected.
I've been racking my mind over this, maybe I don't know where to look?
function sortArray(arrT) {
for (let i = 1; i < arrT.length; i++){
var tempMax = arrT[i];
var j = i - 1;
while ((j >= 0) && (arrT[j] > tempMax)) {
console.log(arr1 + "\nj=" + j + " i=" + i);
arrT[j+1] = arrT[j];
j--;
}
arrT[j+1] = tempMax;
}
console.log("sorted array is (inside loop) :\n" +arrT);
return arrT;
}
for an array that was filled by a while loop of prompts such as
it's equal to the above sample array, the result is
1,1,1,234,35,356,73
for reference, though it's far from elegant, I'm using this to fill the array:
for (let i = 0, x = ""; x !== "x"; i++) {
x = prompt("press x to finish, enter to continue");
if (x == "x") { break }
arr1[i]=prompt("enter");
}
As per my understanding.
The mistake is here
Original Code:
for (let i = 0, x = ""; x !== "x"; i++) {
x = prompt("press x to finish, enter to continue");
if (x == "x") { break }
arr1[i]=prompt("enter");//do not use prompts while unnecessary. Just replace it with x;
}
Corrected One:
for (let i = 0, x = ""; x !== "x"; i++) {
x = prompt("press x to finish, enter to continue");
if (x == "x") { break }
/*
you can also improve your code by applying few checks
if(!isNaN(x)) continue; // --- to skip when input value isn't a number
*/
arr1[i]=x;
}
for (let i = 0, x = ""; x !== "x"; i++) {
x = prompt("press x to finish, enter to continue");
if (x == "x") { break }
arr1[i]=prompt("enter");
}
prompt actually returns a string, hence your input is an array of strings instead. You should use Number to ensure the provided value is numeric.
I would rewrite the above in this way:
// stores all the values.
var arr1 = [];
// Stores the current value.
var input;
do {
var _ = prompt("press x to finish, enter to continue"); // <-- not sure why you're doing that every time, I would suggest you to move it outside of the loop.
input = prompt("enter");
var n = Number(input);
if (!isNaN(n)) arr1.push(n); // <-- checks whether the provided value is actually numeric and a valid number. If it is, the value is added to the collection.
}
while (input !== 'x');
console.log(arr1);
I would suggest you to move the first prompt outside of the loop, but since you did it in your code, I suspect there is a reason for that, though I don't get it.
In any case, the above sample will check whether the value passed is valid; if it is, it push the item to the collection, otherwise it continues until 'x' is met.
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) {
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)
})
This question already has an answer here:
Find the highest subset of an integer array whose sums add up to a given target
(1 answer)
Closed 6 years ago.
I'm stumbling into a problem that I've to treat a given value and check if this value is bigger than my array of values, if it is, combine the output using my array.
For example.
My array always will be:
const ArrayPrimitive = [100,50,20,10];
And for example the given value in the input:
Entry: 30.00 Result: [20.00, 10.00]
Entry: 80.00 Result: [50.00, 20.00, 10.00]
Entry: 125.00 Result: throw NoteUnavailableException
Entry: -130.00 Result: throw InvalidArgumentException
Entry: NULL Result: [Empty Set]
I've started to develop but I'm stuck in the in how to deal with the rest, after check if the value is valid.
const ArrayPrimitive = [100,50,20,10];
var combination = [];
$(document).ready(function(){
$(".btn-submit").on("click",function(){
var amountOfMoney = document.getElementById('amountOfMoney').value;
for(let i=0; i=ArrayPrimitive.length;i++){
if(amountOfMoney=ArrayPrimitive[i]){
combination.push(amountOfMoney);
}
while(amountOfMoney > ArrayPrimitive[i]){
var k = amountOfMoney/ArrayPrimitive[i];
for(let j=0; j = k; j++){
combination.push(ArrayPrimitive[i]);
amountOfMoney = amountOfMoney - ArrayPrimitive[i];
}
var rest = n % ArrayPrimitive[i];
}
}
});
});
My major questions are:
Can you guys help me to solve this in the old way?
I'm about to learn ES6 and probably the .map or other function could save my life, could someone enlighten me in this point?
I hope I made myself clear.
https://jsfiddle.net/v748ha08/1/
UPDATE: Thanks #Malk for your answer but your solution just provides the right answer only when you don't have to combine multiple values of the subset.
e.g.
Entry: 200.00
Result: [100.00, 100.00]
In this case I need 2 values of 100 in my subset, and when I test this they functions throws me an error.
If you want to iterate through an array and build up an object (or anything else) along the way then Array.reduce() is the way to go.
const ArrayPrimitive = [100, 95, 20, 10]; // Assuming presorted array(descending)
function findChange(m) {
return ArrayPrimitive.reduce((mm, c) => {
if (mm.rest >= c) {
mm.change.push(c);
mm.rest -= c
}
return mm
}, {
change: [],
rest: m
});
}
function findChangeOld(m) {
var retval = {
change: [],
rest: m
},
i = ArrayPrimitive.length;
for (var x = 0; x < i; x++) {
if (retval.rest >= ArrayPrimitive[x]) {
retval.change.push(ArrayPrimitive[x])
retval.rest -= ArrayPrimitive[x];
}
}
return retval;
}
function calcChange(v) {
var c = findChangeOld(v);
if (v < 0 || isNaN(v)) {
console.log(`${v}: throw InvalidArgumentException`);
return;
}
if (c.rest > 0)
console.log(`${v}: throw NoteUnavailableException`);
else
console.log(`${v}: ${c.change}`);
}
calcChange(30);
calcChange(80);
calcChange(105);
calcChange(125);
calcChange(-130);
calcChange(null);