Lodash swap adjacent items if condition is met - javascript

Say I have an array, [1, 2, 3, 4, 5, 3, 4, 6, 3, 7, 4]. I want to swap the values of 3 and 4 iff they are adjacent an in the order [3, 4] (i.e. [4, 3] remain intact). The result of the example would be [1, 2, 4, 3, 5, 4, 3, 6, 3, 7, 4].
Is there an elegant way to do this in Lodash without the use of for loops?
Edit:
How about
_.sortBy(arr, function(value, index) {
if (value === 3) {
return index + 0.75;
} else if (value === 4) {
return index - 0.75;
} else {
return index
}
});
Edit 2:
Went with the following (it's not actually 3 and 4).
return _.reduce(tags, function(out, tag) {
var word = tag[0];
if (out[0] && unitPowerSuffixes.hasOwnProperty(word)) {
out.splice(-1, 0, {
type: 'unit-power',
value: unitPowerSuffixes[word]
}); // Insert one from end
} else {
out.push(tag);
}
return out;
}, []);

Is there an elegant way to do this in Lodash without the use of for loops?
Yes, you can use reduce as follows:
var array = [1, 2, 3, 4, 5, 3, 4, 6, 3, 7, 4];
var result = _.reduce(array, swapAdjacentInOrder(3, 4), []);
alert(JSON.stringify(result));
function swapAdjacentInOrder(a, b) {
return function (result, element) {
var length = result.length;
if (length > 0 && element === b) {
var last = length - 1;
if (result[last] === a) {
result[last] = b;
element = a;
}
}
result[length] = element;
return result;
};
}
<script src="https://rawgit.com/lodash/lodash/master/lodash.min.js"></script>
However, the swapAdjacentInOrder function also has the following property:
var array = [3, 4, 4, 4];
var result = _.reduce(array, swapAdjacentInOrder(3, 4), []);
alert(JSON.stringify(result)); // [4, 4, 4, 3]
function swapAdjacentInOrder(a, b) {
return function (result, element) {
var length = result.length;
if (length > 0 && element === b) {
var last = length - 1;
if (result[last] === a) {
result[last] = b;
element = a;
}
}
result[length] = element;
return result;
};
}
<script src="https://rawgit.com/lodash/lodash/master/lodash.min.js"></script>
If you don't want that then you can do the following updated swapAdjacentInOrder function:
var array = [3, 4, 4, 4];
var result = _.reduce(array, swapAdjacentInOrder(3, 4), []);
alert(JSON.stringify(result)); // [4, 3, 4, 4]
function swapAdjacentInOrder(a, b) {
return function (result, element) {
var length = result.length;
if (length > 0 && element === b) {
var last = length - 1;
if (result[last] !== a || last > 0 && result[last - 1] === b);
else {
result[last] = b;
element = a;
}
}
result[length] = element;
return result;
};
}
<script src="https://rawgit.com/lodash/lodash/master/lodash.min.js"></script>
Hope that helps.

A really functional way would be to use something like splitOn. But we can do that using strings (not even needing lodash):
arr.join().split("3,4").join("4,3").split(",").map(Number)

Related

How to remove *some* items from array when there is repetition? (Lodash/Underscore preferred)

I'm trying to write a function that filters out triplets from an array of 6 dice. Is there an easy way to do it using Lodash or Underscore?
noTriplets([1,1,1,3,3,5]) // = [3,3,5]
noTriplets([1,1,1,1,3,5]) // = [1,3,5]
noTriplets([1,1,1,1,1,5]) // = [1,1,5]
noTriplets([1,1,1,5,5,5]) // = []
noTriplets([1,1,1,1,1,1]) // = []
It's a little rough and dirty, but it doesn't require you to know your triplets ahead of time. Within the noTriplets() - I create a quick hashMap and then loop through that object. The loop logic handles the triplet piece.
const arrayTestOne = [1,1,1,3,3,5];
const arrayTestTwo = [1,1,1,1,3,3,5];
const arrayTestThree = [1,1,1,3,3,3];
const arrayTestFour = [1,1,1,1,3,3,3,5,5,5,5,5,5,5,5,5,5,5,5,7,7];
const hashMap = (array) => array.reduce((allNums, num) => {
if (num in allNums) {
allNums[num]++
}
else {
allNums[num] = 1
}
return allNums
}, {})
function noTriplets(arr) {
let newArr = [];
let obj = hashMap(arr);
for (var key in obj) {
for (let i=0; i < obj[key] % 3; i++) {
newArr.push(key)
}
}
console.log(newArr)
}
noTriplets(arrayTestOne)
noTriplets(arrayTestTwo)
noTriplets(arrayTestThree)
noTriplets(arrayTestFour)
You could use a count for every item and calculate how many items to ignore.
function noTriplets(array) {
var hash = {};
array.forEach(function (a) {
hash[a] = hash[a] || { count: 0 };
hash[a].ignore = Math.floor(++hash[a].count / 3) * 3;
});
return array.filter(function (a, i) {
return --hash[a].ignore < 0;
});
}
console.log(noTriplets([1, 1, 1, 3, 3, 5])); // [3, 3, 5]
console.log(noTriplets([1, 1, 1, 1, 3, 5])); // [1, 3, 5]
console.log(noTriplets([1, 1, 1, 1, 1, 5])); // [1, 1, 5]
console.log(noTriplets([1, 1, 1, 5, 5, 5])); // []
console.log(noTriplets([1, 1, 1, 1, 1, 1])); // []
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can use an object to record the values and then generate a new array using the previous object.
function noTriplets(arr){
var tripletCount = arr.reduce((dice,value) => {
dice[value] = dice[value] || { count : 0 };
dice[value].count = (dice[value].count + 1) % 3;
return dice;
},{});
return Object.keys(tripletCount).reduce((arr,key) => {
return arr.concat(new Array(tripletCount[key].count).fill(key));
},[]);
}
console.log(noTriplets([1, 1, 1, 3, 3, 5])); // [3, 3, 5]
console.log(noTriplets([1, 1, 1, 1, 3, 5])); // [1, 3, 5]
console.log(noTriplets([1, 1, 1, 1, 1, 5])); // [1, 1, 5]
console.log(noTriplets([1, 1, 1, 5, 5, 5])); // []
console.log(noTriplets([1, 1, 1, 1, 1, 1])); // []
My universal solution with pure JS. You can specify how many repetition items should be removed. For example, here created noDoubles, noTriplets and noQuadruples methods.
function isArrayWithIdenticalElements(array) {
return array.length > 1 && !!array.reduce(function(a, b){ return (a === b) ? a : NaN; });
}
function noRepetition(numberOfRepetition, array) {
var sliceLength = numberOfRepetition - 1;
var pointer = sliceLength;
var element = array[pointer];
while (element) {
if (isArrayWithIdenticalElements(array.slice(pointer - sliceLength, pointer + 1))) {
array.splice(pointer - sliceLength, numberOfRepetition);
pointer = pointer - sliceLength;
element = array[pointer];
} else {
pointer = pointer + 1;
element = array[pointer];
}
}
return array;
}
var noDoubles = noRepetition.bind(null, 2);
var noTriplets = noRepetition.bind(null, 3);
var noQuadruples = noRepetition.bind(null, 4);
console.log('noTriplets([1,1,1,3,3,5] ==> ', noTriplets([1,1,1,3,3,5])); // = [3,3,5]
console.log('noTriplets([1,1,1,1,3,5] ==> ', noTriplets([1,1,1,1,3,5])); // = [1,3,5]
console.log('noTriplets([1,1,1,1,1,5] ==> ', noTriplets([1,1,1,1,1,5])); // = [1,1,5]
console.log('noTriplets([1,1,1,5,5,5] ==> ', noTriplets([1,1,1,5,5,5])); // = []
console.log('noTriplets([1,1,1,1,1,1] ==> ', noTriplets([1,1,1,1,1,1])); // = []
console.log('noQuadruples([1,1,1,3,3,5] ==> ', noQuadruples([1,1,1,3,3,5])); // = [1,1,1,3,3,5]
console.log('noQuadruples([1,1,1,1,3,5] ==> ', noQuadruples([1,1,1,1,3,5])); // = [3,5]
console.log('noDoubles([1,1,1,5,5,5] ==> ', noDoubles([1,1,1,5,5,5])); // = [1,5]
Great answers! After reading everyone's answers, especially Christopher Messer's, I came up with a lodash-based version:
function noTriplets(arr) {
var hashMap = _.countBy(arr)
var filler = n => Array(hashMap[n] % 3).fill(n)
return _.flatten(_.keys(hashMap).map(filler))
}

Difference between same value in array using only one loop javascript

How to find the difference between the min and max indexes of the same value in an array with one loop with complexity exactly O(N)?
For example, given array A:
[4, 6, 2, 2, 6, 6, 1];
the function returns 4.
I'd use reduce to remember the first index of each value, then update the last value and maximum spread as I went along, e.g.
var data = [4, 6, 2, 2, 6, 6, 1];
function getMaxIndexSpread(data) {
return data.reduce(function(acc, value, index) {
if (value in acc) {
acc[value].lastIndex = index
} else {
acc[value] = {firstIndex: index, lastIndex: index};
}
var spread = acc[value].lastIndex - acc[value].firstIndex;
if (acc.maxSpread < spread) acc.maxSpread = spread;
return acc;
}, {maxSpread: 0}).maxSpread;
}
console.log(getMaxIndexSpread(data));
There's likely a funkier way, but this makes sense to me.
var data = [4, 6, 2, 2, 6, 6, 1];
console.log(Math.max(...data.map((v,i) => i - data.indexOf(v))));
var arr = [4, 6, 2, 2, 6, 6, 1];
function test(arr) {
var resultArr = [];
arr.map(function (v, i, a) {
for (var j = arr.length - 1; j >= 0; j--) {
if (v == arr[j]) {
resultArr.push({value: v, result: j - i});
// console.log(v+'的折扣值'+(j-i));
break;
}
}
})
resultArr.sort(function (a, b) {
return b.result - a.result;
})
console.log(resultArr[0])
}
test(arr);
Try with Array#filter .filter the array without max and min value .Then find max value in filtered array .... its spread syntax
var data = [4, 6, 2, 2, 6, 6, 1];
function bet(data) {
return Math.max(...data.filter(a => a != Math.max(...data) && a != Math.min(...data)))
}
console.log(bet(data))

Find the least duplicate items in an array

Okay so as the title says my goal is to find the least duplicate elements, given that the elements are only integers.
ex1: array = [1,1,2,2,3,3,3] result should be 1,2
ex2: array = [1,2,2,3,3,4] result should be 1,4
I could use the xor operator to find the elements that appear only once but since there might be only duplicates I cant.
I was thinking of first checking with XOR if the're any non-duplicate elements. If no proceed with fors to check for only two occurrences of the same element and so on, but that is not a good approach as its kinda slow,
any suggestions?
Another approach, using new Set() and few Array.prototype functions. If you have any questions, let me know.
var array1 = [1, 1, 2, 2, 3, 3, 3],
array2 = [1, 2, 2, 3, 3, 4];
function sort(arr) {
var filtered = [...new Set(arr)],
solution = [],
res = filtered.reduce(function(s, a) {
s.push(arr.filter(c => c == a).length);
return s;
}, []);
var minDupe = Math.min.apply([], res);
res.forEach((v, i) => v == minDupe ? solution.push(filtered[i]) : null)
console.log(solution)
}
sort(array1);
sort(array2);
Using Array#forEach instead of Array#reduce.
var array1 = [1, 1, 2, 2, 3, 3, 3],
array2 = [1, 2, 2, 3, 3, 4];
function sort(arr) {
var filtered = [...new Set(arr)],
solution = [],
res = [];
filtered.forEach(v => res.push(arr.filter(c => c == v).length));
var minDupe = Math.min.apply([], res);
res.forEach((v, i) => v == minDupe ? solution.push(filtered[i]) : null)
console.log(solution)
}
sort(array1);
sort(array2);
There may be a better or faster solution, but I would suggest to create a hash (object) with the integer as keys and the counts as values. You can create this in one loop of the array. Then, loop over the object keys keeping track of the minimum value found and add the key to the result array if it satisfies the minimum duplicate value.
Example implementation:
const counts = input.reduce((counts, num) => {
if (!counts.hasOwnProperty(num)) {
counts[num] = 1;
}
else {
counts[num]++;
}
return counts;
}, {});
let minimums = [];
let minCount = null;
for (const key in counts) {
if (!minimums.length || counts[key] < minCount) {
minimums = [+key];
minCount = counts[key];
}
else if (counts[key] === minCount) {
minimums.push(+key);
}
}
return minimums;
You can also simplify this a little bit using lodash: one operation to get the counts and another to get the minimum count and get the list of values that match that minimum count as a key:
import { countBy, invertBy, min, values } from "lodash";
const counts = countBy(input);
const minCount = min(values(counts));
return invertBy(counts)[minCount];
You could count the appearance, sort by count and delete all same max count keys. Then return the original values.
Steps:
declarate all variables, especial the hash object without any prototypes,
use the items as key got the hash table and if not set use an object with the original value and a count property,
increment count of actual hash,
get all keys from the hash table,
sort the keys in descending order of count,
get the count of the first element and store it in min,
filter all keys with min count,
get the original value for all remaining keys.
function getLeastDuplicateItems(array) {
var hash = Object.create(null), keys, min;
array.forEach(function (a) {
hash[a] = hash[a] || { value: a, count: 0 };
hash[a].count++;
});
keys = Object.keys(hash);
keys.sort(function (a, b) { return hash[a].count - hash[b].count; });
min = hash[keys[0]].count;
return keys.
filter(function (k) {
return hash[k].count === min;
}).
map(function (k) {
return hash[k].value;
});
}
var data = [
[1, 1, 2, 2, 3, 3, 3],
[1, 2, 2, 3, 3, 4],
[4, 4, 4, 6, 6, 4, 7, 8, 5, 5, 6, 3, 4, 6, 6, 7, 7, 8, 3, 3]
];
console.log(data.map(getLeastDuplicateItems));
.as-console-wrapper { max-height: 100% !important; top: 0; }
A single loop solution with a variable for min and an array for collected count.
function getLeastDuplicateItems(array) {
var hash = Object.create(null),
temp = [],
min = 1;
array.forEach(function (a) {
var p = (temp[hash[a]] || []).indexOf(a);
hash[a] = (hash[a] || 0) + 1;
temp[hash[a]] = temp[hash[a]] || [];
temp[hash[a]].push(a);
if (min > hash[a]) {
min = hash[a];
}
if (p === -1) {
return;
}
temp[hash[a] - 1].splice(p, 1);
if (min === hash[a] - 1 && temp[hash[a] - 1].length === 0) {
min++;
}
}, []);
return temp[min];
}
var data = [
[1, 1, 2, 2, 3, 3, 3],
[1, 2, 2, 3, 3, 4],
[4, 4, 4, 6, 6, 4, 7, 8, 5, 5, 6, 3, 4, 6, 6, 7, 7, 8, 3, 3],
];
console.log(data.map(getLeastDuplicateItems));
.as-console-wrapper { max-height: 100% !important; top: 0; }

Find a group of objects in an array with javascript

Question has been moved to CodeReview: https://codereview.stackexchange.com/questions/154804/find-a-list-of-objects-in-an-array-with-javascript
Having an array of objects - such as numbers - what would be the most optimal (Memory and CPU efficiency) way if finding a sub group of objects? As an example:
demoArray = [1,2,3,4,5,6,7]
Finding [3,4,5] would return 2, while looking for 60 would return -1.
The function must allow for wrapping, so finding [6,7,1,2] would return 5
I have a current working solution, but I'd like to know if it could be optimized in any way.
var arr = [
1,
5,2,6,8,2,
3,4,3,10,9,
1,5,7,10,3,
5,6,2,3,8,
9,1]
var idx = -1
var group = []
var groupSize = 0
function findIndexOfGroup(g){
group = g
groupSize = g.length
var beginIndex = -2
while(beginIndex === -2){
beginIndex = get()
}
return beginIndex
}
function get(){
idx = arr.indexOf(group[0], idx+1);
if(idx === -1 || groupSize === 1){
return idx;
}
var prevIdx = idx
for(var i = 1; i < groupSize; i++){
idx++
if(arr[getIdx(idx)] !== group[i]){
idx = prevIdx
break
}
if(i === groupSize - 1){
return idx - groupSize + 1
}
}
return -2
}
function getIdx(idx){
if(idx >= arr.length){
return idx - arr.length
}
return idx
}
console.log(findIndexOfGroup([4,3,10])) // Normal
console.log(findIndexOfGroup([9,1,1,5])) // Wrapping
You could use the reminder operator % for keeping the index in the range of the array with a check for each element of the search array with Array#every.
function find(search, array) {
var index = array.indexOf(search[0]);
while (index !== -1) {
if (search.every(function (a, i) { return a === array[(index + i) % array.length]; })) {
return index;
}
index = array.indexOf(search[0], index + 1);
}
return -1;
}
console.log(find([3, 4, 5], [1, 2, 3, 4, 5, 6, 7])); // 2
console.log(find([6, 7, 1, 2], [1, 2, 3, 4, 5, 6, 7])); // 5
console.log(find([60], [1, 2, 3, 4, 5, 6, 7])); // -1
console.log(find([3, 4, 5], [1, 2, 3, 4, 6, 7, 3, 4, 5, 9])); // 6
.as-console-wrapper { max-height: 100% !important; top: 0; }
My take on the problem is to use slice() and compare each subarray of length equal to the group's length to the actual group array. Might take a bit long, but the code is short enough:
// The array to run the tests on
var arr = [
1,
5, 2, 6, 8, 2,
3, 4, 3, 10, 9,
1, 5, 7, 10, 3,
5, 6, 2, 3, 8,
9, 1
];
// Check arrays for equality, provided that both arrays are of the same length
function arraysEqual(array1, array2) {
for (var i = array1.length; i--;) {
if (array1[i] !== array2[i])
return false;
}
return true;
}
// Returns the first index of a subarray matching the given group of objects
function findIndexOfGroup(array, group) {
// Get the length of both arrays
var arrayLength = array.length;
var groupLength = group.length;
// Extend array to check for wrapping
array = array.concat(array);
var i = 0;
// Loop, slice, test, return if found
while (i < arrayLength) {
if (arraysEqual(array.slice(i, i + groupLength), group))
return i;
i++;
}
// No index found
return -1;
}
// Tests
console.log(findIndexOfGroup(arr,[4,3,10])); // Normal
console.log(findIndexOfGroup(arr,[9,1,1,5])); // Wrapping
console.log(findIndexOfGroup(arr,[9,2,1,5])); // Not found
If the group is longer than the array, some errors might occur, but I leave it up to you to extend the method to deal with such situations.

Finding chained, incremented values in array

I'm trying to find all values in an array that would form a chain of incremented values - all referencing back to a certain starting value. Increments can go both "up" and "down".
array = [10, 2, 3, 5, 9, 11]
Starting with the number 2 should return:
[2, 3]
Starting with the number 10 should return:
[9, 10, 11]
There are of course plenty of inefficient ways of doing this, but I'm asking this here because doing this efficiently is important for my case and I'm such a JS newbie.
You can use Array.prototype.includes() to check if a number exists in an array. If the number is before the base reference add it using unshift, if after add it using push:
var array = [10, 2, 3, 5, 9, 11];
function findChain(array, num) {
if(!array.includes(num)) {
return [];
}
const result = [num];
let before = num - 1;
let after = num + 1;
while(array.includes(before)) {
result.unshift(before--);
}
while(array.includes(after)) {
result.push(after++);
}
return result;
}
console.log('Ref 2 -', findChain(array, 2));
console.log('Ref 5 -', findChain(array, 5));
console.log('Ref 10 -', findChain(array, 10));
console.log('Ref 20 -', findChain(array, 20));
A quick solution :
var array = [10, 2, 3, 5, 9, 11, 14, 89, 12, 8];
var trouver = nombre => {
var result = [];
if (array.indexOf(nombre) !== -1) result.push(nombre);
else return result;
for(var chiffre = nombre+1; array.indexOf(chiffre) !== -1; chiffre++) result.push(chiffre);
for(var chiffre = nombre-1; array.indexOf(chiffre) !== -1; chiffre--) result.push(chiffre);
return result.sort((a,b) => a-b);
}
console.log(trouver(9)); //[ 8, 9, 10, 11, 12 ]
Another solution could be a double chained list for it.
function getValues(array, value) {
var object = Object.create(null),
result,
o;
array.forEach(function (a) {
object[a] = object[a] || { value: a, pre: object[a - 1] || null, succ: object[a + 1] || null };
if (object[a - 1]) {
object[a - 1].succ = object[a];
}
if (object[a + 1]) {
object[a + 1].pre = object[a];
}
});
o = object[value];
if (o) {
result = [];
while (o.pre) {
o = o.pre;
}
while (o.succ) {
result.push(o.value);
o = o.succ;
}
result.push(o.value);
}
return result;
}
var array = [10, 2, 3, 5, 9, 11, 14, 89, 12, 8];
console.log(getValues(array, 2));
console.log(getValues(array, 10));
console.log(getValues(array, 42));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Try this approach
Sort the array first
Iterate array items one by one, keep pushing the start-counter if current-item value is not bigger than last-item by 1, else reset the start-counter to current-index.
For Example :
var arr = [10, 2, 3, 5, 9, 11];
function getAllSequences(arr) {
arr.sort(function(a, b) {
return a - b
});
var startIndex = 0;
var endIndex = 0;
var lastItem = 0;
var chains = [];
arr.forEach(function(item, index) {
if (index > 0) {
if ((item - lastItem) > 1) {
extractChain(chains, arr, startIndex, endIndex);
startIndex = index;
} else {
endIndex = index;
if (index == arr.length - 1) {
extractChain(chains, arr, startIndex, endIndex);
}
}
}
lastItem = item;
});
return chains;
}
console.log(getAllSequences(arr));
function extractChain(chains, arr, startIndex, endIndex) {
var value = arr.slice(startIndex, endIndex + 1);
if (value.length > 0) {
chains.push(value);
}
}

Categories