Say I have this default array in JavaScript:
const default = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet']
I have this array provided by the user:
const user_arr = ['red', 'green', 'violet']
I need to compare these two arrays and validate that the user array is in the correct order (i.e. I am allowed to skip levels, but reverse order is not allowed).
So the following arrays would be valid:
['red', 'orange', 'violet']
['yellow', 'blue']
['green', 'indigo', 'violet']
There arrays would be invalid:
['red', 'violet', 'orange']
['green', 'violet', 'yellow]
Is this possible without using complex tree data structures?
Here I map each element in the user array to its index in the proper array.
The array of indexes is checked to make sure it's in ascending order.
note that "default" is a javascript keyword and isn't a valid variable name
const proper = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet']
const isValid = (arr) => {
const isAscending = (arr) => arr.every((el, i, a) =>
i === a.length - 1
? true
: el < a[i + 1]
);
return isAscending(arr.map(el => proper.indexOf(el)));
}
const user_arr = ['red', 'green', 'blue']
console.log(isValid(user_arr));
console.log(isValid(['red', 'indigo', 'orange']));
I don't know if it is the most efficient way, but this should work:
/**
*
* #param {string[]} ref
* #param {string[]} array
* #return {boolean}
*/
function checkValidity(ref, array) {
let prev = 0;
for (let i = 0; i < array.length; i++) {
const current = ref.findIndex((elt) => elt === array[i]);
if (current >= prev) prev = current;
else return false;
}
return true;
}
This approach uses a Map which maps the value of an item to its index within the array.
Using that map we then we check for every element in a user supplied array whether we have that item in our original array. If we have not, the user array is invalid. If it is a known item we need to get its position in the original array and check if it greater than the previous position we have checked. If it is, everything is fine, if it is not, the user array is invalid.
Remarks on solution
This solution does not allow for duplicate values as it checks for <, not <=. If you have duplicate values in your original array the latest of all occurrences of that duplicate value will determine its position in the array. Currently this implementation does not accept empty arrays but one could easily change that so an empty array would be accepted.
Complexity
The advantage of using a Map is the speedup for the lookup of the index which can be done in O(1). Therefore, one needs O(n) time with n being the number of items in the original array once to initialize the Map. Then one can use that Map to check whether a user array is valid in worst case O(m) time (e.g. for a valid array) where m is the number of items in a user supplied array. Using just arrays and methods like indexOf() or find() will have a worst case time complexity of O(m * n). For small arrays such as yours those things won't really matter though.
const order = ["red", "orange", "yellow", "green", "blue", "indigo", "violet"];
const indexToValue = new Map();
order.forEach((item, idx) => indexToValue.set(item, idx));
const validArrays = [
["red"],
["red", "orange", "violet"],
["yellow", "blue"],
["green", "indigo", "violet"],
];
const invalidArrays = [
["red", "violet", "orange"],
["green", "violet", "yellow"],
];
/**
*
* #param {Array<string>} userArray
* #param {Map<string, number>} indexToValue
* #returns
*/
function checkCorrectness(userArray, indexToValue) {
if (!Array.isArray(userArray) || userArray.length === 0) return false;
let prev = -1;
return userArray.every((item) => {
// an unknown item is in the user array so it is invalid
if (!indexToValue.has(item)) return false;
// get index of the element in the original array
const idxInOrig = indexToValue.get(item);
// if the current index is smaller than the previous index -> invalid! stop immediately
if (idxInOrig < prev) return false;
// everything is fine current index is bigger than previous -> continue with next one
prev = idxInOrig;
return true;
});
}
console.log("Invalid arrays")
invalidArrays.forEach(invalid => console.log(checkCorrectness(invalid, indexToValue)));
console.log("Valid arrays")
validArrays.forEach(valid => console.log(checkCorrectness(valid, indexToValue)));
Related
I recently took part in an algorithm challenge to create a Fuzzy search with the following criteria:
Given a set array, create a function that receives one argument and returns a new array containing only the values that start with either:
A) The argument provided
B) The argument provided but with 1 difference (i.e. 1 incorrect letter)
The array was: fruits = [apple, apricot, banana, pear, mango, cherry, tomato]
so:
fuzzySearch('ap') = ['apple, apricot']
fuzzySearch('app') = ['apple', 'apricot']
fuzzySearch('appl') = ['apple']
fuzzySearch('pa') = ['banana', 'mango']
This is the solution I came up with:
const fruits = ['apple', 'apricot', 'banana', 'pear', 'mango', 'cherry', 'tomato']
function fuzzySearch(str) {
return fruits.filter(fruit =>
{
let letterCount = 0
const fruitLetArr = fruit.toLowerCase().split('')
const strArr = str.toLowerCase().split('')
for (var i = 0; i < strArr.length; i++) {
console.log(fruitLetArr[i], strArr[i], i, letterCount)
if (fruitLetArr[i] !== strArr[i]) letterCount++
if (letterCount === 2) break;
}
if (letterCount < 2) return true
});
}
fuzzySearch(str)
Can anyone think of a faster way that doesn't involve iterating over every value before a soltion can be found?
Here's something that should be slightly more efficient. Also easier to read. In this solution, I am assuming that by "difference" you mean a substitution of a letter for another letter, rather than the addition of another letter.
const fruits = ['apple', 'apricot', 'banana', 'pear', 'mango', 'cherry', 'tomato'];
const fuzzySearch = (str) => {
return fruits.filter((fruit) => {
// If our first case is met, immediately return
if (fruit.startsWith(str)) return true;
// Split the fruit based on the length of input string
const test = fruit.slice(0, str.length).split('');
let diffs = 0;
// Compare + keep track of differences between input + sliced fruit
test.forEach((letter, i) => letter !== str[i] && diffs++);
// If we have more than one difference, it doesn't meet case #2
if (diffs > 1) return false;
return true;
});
};
const testCases = ['ap', 'app', 'appl', 'pan', 'bp'];
for (const testCase of testCases) {
console.log(fuzzySearch(testCase));
}
One module in my app pulls records from a mongodb and prints out the fields for the user to see. The fields are displayed in a particular order. The order is stored in an array. The mongodb document (pers) may not have all of the fields possible filled out. Anyway, here is an example of my original code. It works. I want to move onto something better. I will explain as I go.
const keys = ['banana', 'orange', 'apple', 'grape']
const pers = {
banana: 'word',
orange: 'e t',
apple: 'scrabble',
}
let entries = []
let entry = ''
keys.forEach(key => {
if (pers[key]) entries.push(pers[key])
})
for (let i=0; i<entries.length; i++) {
if ((entries.length + entries[i].length < 10) entry += '\n' + entries[i]
else {
console.log(entry) //message.send in my app
entry = entries[i]
}
}
console.log(entry)
So in my app, the message that is sent cannot be longer than 2000 characters. I am using 10 characters for the example here in the post. And I do not want them split up in the middle either. So for instance 'word' + 'e t' is the maximum I can concatenate together. If I try to add 'scrabble' to it, the total length will be over 10 characters in length. So I want to send 'word e t' to the console and then start the variable entry over. I also do not want to send 'word' and then 'e' and then 't' and then 'scrabble'. Like I said before, my existing code works. I just want to try and be clear on my intent.
Moving forward... Below is my second iteration of the code. I'm trying to move away from imperative statements to declarative. So I replaced the forEach block. This also works great.
const keys = ['banana', 'orange', 'apple', 'grape']
const pers = {
banana: 'word',
orange: 'e t',
apple: 'scrabble',
}
let entry = ''
const entries = keys.filter(key => pers.hasOwnProperty(key)).map(key => pers[key])
for (let i=0; i<entries.length; i++) {
if ((entries.length + entries[i].length < 10) entry += '\n' + entries[i]
else {
console.log(entry)
entry = entries[i]
}
}
console.log(entry)
The console output for both of these is:
word
e t
scrabble
That is exactly what I wanted. Now I really want to move away from that for block if possible. Surely there is a way to add another chain to my declarative statement to take care of that portion? I could possibly see a way of doing it if I didn't have the character limitation.
Anyway, if I have been unclear in any of my description or intent, please let me know and I will do my best to clarify.
Thanks!!
Try using .reduce like this. Your original code had a syntax error, so I'm assuming that was just a typo and not something important.
While that's functionally identical to your current code, it's still pretty convoluted. If I were you, I'd create an array of entryStrs while iterating, and then console.log them all later, like this:
const keys = ['banana', 'orange', 'apple', 'grape']
const pers = {
banana: 'word',
orange: 'e t',
apple: 'scrabble',
}
const { currentMessage, messages } = keys
.filter(key => pers.hasOwnProperty(key))
.map(key => pers[key])
.reduce(({ currentMessage = '', messages = [] }, entry, i, entries) => {
if (entries.length + entry.length < 10) {
currentMessage += currentMessage
? ('\n' + entry)
: entry;
} else {
messages.push(currentMessage)
currentMessage = entry;
}
return { currentMessage, messages };
}, {});
const allMessages = [...messages, currentMessage];
console.log(allMessages.join('\n-----\n-----\n'));
since in the for block you have to iterate and change an external variable and not changing anything in the array itself, you can use Conditional (tenary) Operator and replace for with forEach()
const keys = ['banana', 'orange', 'apple', 'grape']
const pers = {
banana: 'word',
orange: 'e t',
apple: 'scrabble',
}
let entry = ''
const entries = keys.filter(key => pers.hasOwnProperty(key)).map(key => pers[key])
entries.forEach(e => {
entries.length + e.length < 10 ? entry += '\n' + e : (console.log(entry), entry = e)
});
console.log(entry)
Say I have an array that goes something like:
fruit_basket = ['apple', 'orange', 'banana', 'pear', 'banana']
I want to make an array fruits that consists of the fruits found in fruit basket, sorted in order of most frequently occurring fruit. (If there are ties I don't care about ordering.)
So one valid value for fruits is:
['banana', 'orange', 'apple', 'pear']
What's the most concise way to achieve this using LoDash? I don't care about run time performance.
First you'd count the occurences
var x = _.chain(fruit_basket).countBy(); // {apple: 1, orange: 1, banana: 2, pear: 1}
Then you'd pair them and sort by the number of occurences, using reverse to get the largest number first
var y = x.toPairs().sortBy(1).reverse(); //[["banana",2],["pear",1],["orange",1],["apple",1]]
Then you'd just map back the keys, and get the value as an array
var arr = y.map(0).value(); // ['banana', 'orange', 'apple', 'pear']
All chained together, it looks like
var arr = _.chain(fruit_basket).countBy().toPairs().sortBy(1).reverse().map(0).value();
Without loDash, something like this would do it
var fruit_basket = ['apple', 'orange', 'banana', 'pear', 'banana'];
var o = {};
fruit_basket.forEach(function(item) {
item in o ? o[item] += 1 : o[item] = 1;
});
var arr = Object.keys(o).sort(function(a, b) {
return o[a] < o[b];
});
Here's my take on this.
It doesn't use lodash as the question asks for. It's a vanilla alternative. I feel this could be valuable to people who land here from Google and don't want to use lodash (that's how I got here, at least).
const orderByCountAndDedupe = arr => {
const counts = new Map();
arr.forEach( item => {
if ( !counts.has(item) ) {
counts.set(item, 1);
} else {
counts.set(item, counts.get(item)+1);
}
});
return (
Array.from(counts)
.sort( (a, b) => b[1] - a[1])
.map( ([originalItem, count]) => originalItem)
);
};
An approach using for loop Array.prototype.splice()
var fruit_basket = ['apple', 'orange', 'banana', 'pear', 'banana'];
var res = [];
for (var i = 0; i < fruit_basket.length; i++) {
var index = res.indexOf(fruit_basket[i]);
// if current item does not exist in `res`
// push current item to `res`
if (index == -1) {
res.push(fruit_basket[i])
} else {
// else remove current item , set current item at index `0` of `res`
res.splice(index, 1);
res.splice(0, 0, fruit_basket[i])
}
}
console.log(res)
You can count occurences using _.countBy and then use it in _.sortBy:
var counter = _.countBy(fruit_basket)
var result = _(fruit_basket).uniq().sortBy(fruit => counter[fruit]).reverse().value()
Let's say I have an array:
var myArr = new Array('alpha','beta','gamma','delta');
And that I want a function to return an array of all items before a given item:
function getAllBefore(current) {
var myArr = new Array('alpha','beta','gamma','delta');
var newArr = ???
return newArr;
}
getAllBefore('beta'); // returns Array('alpha');
getAllBefore('delta'); // returns Array('alpha','beta','gamma');
What's the fastest way to get this? Can I split an array on a value? Do I have to loop each one and build a new array on the fly? What do you recommend?
What about if I wanted the opposite, i.e. getAllAfter()?
function getAllBefore(current) {
var myArr = new Array('alpha','beta','gamma','delta');
var i = myArr.indexOf(current);
return i > -1 ? myArr.slice(0, i) : [];
}
Get the index of the specified item. If found, .slice() from 0 to that index. If not found, return an empty array (or whatever other default value you like).
Note that .indexOf() is not supported (for arrays) in IE8 and older, but there is a shim you can use, or you could just use a simple for loop instead.
javascript slice array
// array.slice(start, end)
const FRUITS = ["Banana", "Orange", "Lemon", "Apple", "Mango"];
var citrus = FRUITS.slice(1, 3);
// citrus => [ 'Orange', 'Lemon' ]
// Negative values slice in the opposite direction
var fromTheEnd = FRUITS.slice(-3, -1);
// fromTheEnd => [ 'Lemon', 'Apple' ]
array cut only last 5 element
arr.slice(Math.max(arr.length - 5, 0))
Use indexOf and slice:
newArr = myArr.slice(0, myArr.indexOf(current));
Try something like this
var index = myArr.indexOf('beta');
var before = myArray.slice(0, index);
I recently had to do something like this for an array of objects. This is what I went with:
const myArr = [
{ controlId: 1, value: 'alpha'},
{ controlId: 2, value: 'beta' },
{ controlId: 3, value: 'gamma' },
{ controlId: 4, value: 'delta'}
];
function getAllBefore(id) {
const index = myArr.findIndex( ({ controlId }) => controlId === id);
return myArr.filter((_, i) => i < index);
}
I've an Array ['red', 'green', 'blue']
I want to create a new Hash from this Array, the result should be
{'red':true, 'green':true, 'blue':true}
What is the best way to achieve that goal using Prototype?
Just iterate over the array and then create the Hash:
var obj = {};
for(var i = 0, l = colors.length; i < l; i++) {
obj[colors[i]] = true;
}
var hash = new Hash(obj);
You can also create a new Hash object from the beginning:
var hash = new Hash();
for(var i = 0, l = colors.length; i < l; i++) {
hash.set(colors[i], true);
}
I suggest to have a look at the documentation.
This functional javascript solution uses Array.prototype.reduce():
['red', 'green', 'blue']
.reduce((hash, elem) => { hash[elem] = true; return hash }, {})
Parameter Details:
callback − Function to execute on each value in the array.
initialValue − Object to use as the first argument to the first call of the callback.
The third argument to the callback is the index of the current element being processed in the array. So if you wanted to create a lookup table of elements to their index:
['red', 'green', 'blue'].reduce(
(hash, elem, index) => {
hash[elem] = index++;
return hash
}, {});
Returns:
Object {red: 0, green: 1, blue: 2}
Thanks all
here is my solution using prototypejs and inspired by Felix's answer
var hash = new Hash();
colors.each(function(color) {
hash.set(color, true);
});