Related
I want to sort an array by element frequency. My code works for arrays of strings, but not for arrays of numbers:
const countOccurrences = (arr, val) => arr.reduce((a, v) => (v === val ? a + 1 : a), 0);
function frequencySort(arr){
let d = {}
arr.forEach(i => d[i] = countOccurrences(arr,i))
arr.sort(function(a,b){
return d[b] - d[a]
})
return arr
}
frequencySort(['a','b','b','b','c','c'])) returns [ 'b', 'b', 'b', 'c', 'c', 'a' ]
frequencySort([4, 6, 2, 2, 6, 4, 4, 4]) returns [ 4, 4, 4, 4, 6, 2, 2, 6 ]
The only reason your letters worked is because you didn't have the same number of any two letters, where in your numbers, you have 2 of both 2 and 6.
Here's your snippet, but with 2 a's and 2 c's. You'll see it's out of order just like the numbers.
const countOccurrences = (arr, val) => arr.reduce((a, v) => (v === val ? a + 1 : a), 0);
function frequencySort(arr){
let d = {}
arr.forEach(i => d[i] = countOccurrences(arr,i))
arr.sort(function(a,b){
return d[b] - d[a]
})
return arr
}
console.log(frequencySort(['a','b','b','b','c','c', 'a']))
You need a way to sort instances that have the same number of occurrences. I adapted your forEach loop to give the last index of each letter to your b object and then changed your sort to use that index in case the number of occurrences is the same.
const countOccurrences = (arr, val) => arr.reduce((a, v) => (v === val ? a + 1 : a), 0);
function frequencySort(arr){
let d = {}
arr.forEach((i,index) => d[i] = {
num: countOccurrences(arr,i),
i: index
});
arr.sort(function(a,b){
let diff = d[b].num - d[a].num;
if(diff == 0)
diff = d[b].i - d[a].i;
return diff;
})
return arr
}
console.log(frequencySort(['a','b','b','b','c','c', 'a']))
console.log(frequencySort([4, 6, 2, 2, 6, 4, 4, 4]));
It has nothing to do with the elements being letters or numbers. In you letters array, each letter has unique occurence count (3, 2, 1), therefore they are sorted the way you want to.
However, in your numbers array, "2" and "6" both occur 2 times each. Therefore, your sort callback function returns 0 for them, and they are treated as equal order by the sort function.
In your array of numbers you have the same amount of the number 2 as 6 and your sorting function doesn't care about the actual values it just cares about their counts. So in your example 2 and 6 both have the same priority.
You want to adjust your sorting function to compare values of elements if they have the same amount of occurrences.
You'll need to implement separate comparisons for all the data types you want to accept and decide if you want ascending/descending order.
Here is a basic example for number and string elements:
const countOccurrences = (arr, val) => arr.reduce((a, v) => (v === val ? a + 1 : a), 0);
function frequencySort(arr){
let d = {}
arr.forEach(i => d[i] = countOccurrences(arr,i))
arr.sort(function(a,b){
const r = d[b] - d[a]
if (r != 0) return r
switch (typeof d[a]) {
case 'number': return a - b
case 'string': return a.localeCompare(b)
default: return 0
}
})
return arr
}
console.log(frequencySort(['a','b','b','b','c','c'])) // returns [ 'b', 'b', 'b', 'c', 'c', 'a' ]
console.log(frequencySort([4, 6, 2, 2, 6, 4, 4, 4])) // returns [ 4, 4, 4, 4, 2, 2, 6, 6 ]
A possible approach would first collect all equal array items within an item specific group array by a reduce task ...
console.log(
"grouped ['a','b','b','b','c','c'] ...",
['a','b','b','b','c','c'].reduce((index, item) => {
const groupList =
index[`${ (typeof item) }_${ item }`] ??= [];
groupList.push(item);
return index;
}, {})
);
console.log(
"grouped [4, 6, 2, 2, 6, 4, 4, 4,'4','2','2'] ...",
[4, 6, 2, 2, 6, 4, 4, 4,'4','2','2'].reduce((index, item) => {
const groupList =
index[`${ (typeof item) }_${ item }`] ??= [];
groupList.push(item);
return index;
}, {})
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
The final computation then has to transform ... via Object.values ... the temporary result (as shown above) into an array of arrays of equal items where the former gets 1stly sorted by each array's length (indicates the items frequency) and 2ndly, for arrays of equal length', by a locale compare of each array's first item. The final result is the sorted array's flatted version ...
function sortItemsByFrequency(arr) {
const groupedItems = arr.reduce((index, item) => {
const groupList =
index[`${ (typeof item) }_${ item }`] ??= [];
groupList.push(item);
return index;
}, {});
return Object
.values(groupedItems)
.sort((a, b) =>
// - sort by frequency first indicated by an
// array's length.
// - the higher frequency count wins.
b.length - a.length ||
// in case of equal frequency counts do a
// locale compare of both array's first items.
b[0].toLocaleString().localeCompare(a[0].toLocaleString())
)
.flat();
}
console.log(
"sortItemsByFrequency(['a','b','b','b','c','c']) ...",
sortItemsByFrequency(['a','b','b','b','c','c'])
);
console.log(
"sortItemsByFrequency([4, 6, 2, 2, 6, 4, 4, 4,'4','2','2']) ...",
sortItemsByFrequency([4, 6, 2, 2, 6, 4, 4, 4,'4','2','2'])
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
sort first based on frequency of characters in desc order,if freq is same, then sort alphabetically in asc order.
const str = 'zzzzvnttteeeqqaao';
const frequencySort = (str = '') => {
let map = {}
for (const letter of str) {
map[letter] = (map[letter] || 0) + 1;
};
let res = "";
let sorted = Object.keys(map).sort((a, b) => map[b] < map[a] ? -1 : 1);
//console.log(sorted);
for (let letter of sorted) {
for (let count = 0; count < map[letter]; count++) {
res += letter
}
}
return res;
};
console.log(frequencySort(str));
I have an array like this one:
let array = [14, 42, 1, 3]
And I would like to get the arrays number mapped to this:
[1, 0, 3, 2]
Here is the reason:
1: because 14 is the second biggest number
0: because 42 is the biggest number
3: ...
What I have tried so far:
let sort = (array) => {
let result = []
let x = array.slice(0).sort((a, b) => b - a)
for (let elem of x) {
result.push(array.indexOf(elem))
}
console.log(result)
}
// Working
sort([14, 42, 1, 3]) // [1, 0, 3, 2]
// Not working, includes the index "0" two times
sort([14, 42, 14, 3]) // [1, 0, 0, 3]
// Expected: [1, 0, 2, 3]
You could take the indices and sort them by taking the value from the given array.
const sort = array => [...array.keys()].sort((a, b) => array[b] - array[a]);
console.log(sort([14, 42, 1, 3]));
console.log(sort([14, 42, 14, 3]));
It's because indexOf stops when it finds it's first result.
You could try to change the value to null once it's located the first time, or compare value to values already in the result and ignore those values.
let sort = (array) => {
let result = []
let x = array.slice(0).sort((a, b) => b - a)
for (let elem of x) {
result.push(array.indexOf(elem))
array[array.indexOf(elem)] = null;
}
console.log(result)
}
let sort = (arr) => {
let arr2 = arr.slice().sort((a, b) => b - a);
return arr.map((val) => {
return arr2.indexOf(val);
})
}
console.log(sort([14, 42, 1, 3]));
You can use a tracker object, map value and indexes as key/value pair and when looping though the array take the first index from respective key and shift it as well
let sort = (array) => {
let result = []
let x = array.slice(0).sort((a, b) => b - a)
let tracker = x.reduce((op,inp,i)=>{
op[inp] = op[inp] || []
op[inp].push(i)
return op
},{})
for (let elem of array) {
let val = tracker[elem][0]
tracker[elem].shift()
result.push(val)
}
console.log(result)
}
sort([14, 42, 1, 3]) // working
sort([14, 42, 14, 3]) // includes the index "0" two times
(() => {
function getBiggestOrder (nums) {
const lookup = {}
const result = nums.slice(0).sort((a, b) => b - a).map((num, i) => {
lookup[num] = i
return num
})
return nums.map(n => lookup[n])
}
const op = getBiggestOrder([14, 42, 1, 3])
console.log(op)
return op
})()
You are basically numbering the numbers from biggest to smallest. sort them in a duplicate array from biggest to smallest. And replace the original array numbers with their index in the duplicate array.
Original = [14, 42, 1, 3]
Duplicate = [42, 14, 3, 1]
Duplicate indexes are [0, 1, 2, 3]
so find 42 in the first array and replace it with the index of the 42 in the duplicate array, etc.
I would like to compound values while mapping an array, I tried this but it didn't work:
var array = children.map((child, i) => {
return child.offsetHeight + array[i-1]
})
I would like an array that looks like this:
[1, 5, 3, 2]
to output:
[1, 6, 9, 11]
Using map is not a requirement. But I don't mind using something more intended than a for-loop.
Here an alternative way to other proposals and simple one-liner by using a forEach-loop:
let a = [1, 5, 3, 2],
b = [];
a.forEach((el, it) => { b.push(el + (b[it - 1] || 0)) });
console.log(b)
(b[it - 1] || 0) covers the first iteration where we would access b[-1]
You can use a combination of Array#map, Array#slice and Array#reduce :
.map( ... ) goes through your array
.slice( ... ) cuts a part from your array, from beginning to i+1
.reduce( ... ) returns the sum of the previously cut array
let children = [1, 5, 3, 2];
var array = children.map((child, i) =>
children.slice(0,i+1).reduce((acc, curr) => acc + curr, 0));
console.log(array);
This is one way:
const input = [1, 5, 3, 2];
const result = input.reduce((arr, x, i) =>
i == 0 ? [x] : [...arr, x + arr[arr.length - 1]]
, null)
console.log(result);
Reduce is better than map here, as you get access to the current state, rather than just the current item or the input array.
You can use array#reduce.
var result = [1, 5, 3, 2].reduce((r,v,i) => {
i ? r.push(r[i-1] + v) : r.push(v);
return r;
},[]);
console.log(result);
The easiest solution would be a combination of map slice and reduce:
arr = [1,5,3,2]
result = arr.map((elem, index) => arr.slice(0, index + 1).reduce((a,c) => a+c))
console.log(result)
You can do something like this, you must check at position 0 that array doesn't exist. This solution avoids using reduce and slice each step, improving performance;
var children = [1, 5, 3, 2]
var sum = 0;
var array = children.map((child, i, array) => {
sum = sum + child;
return sum;
})
console.log(array)
Example using for...of:
var arr = [1, 5, 3, 2]
var res = []
var c = 0
for (let item of arr) {
c += item
res.push(c)
}
console.log(res)
//[1, 6, 9, 11]
You could do this with reduce() method instead of map(). So if current index is not 0 you can take last element from accumulator and add current element.
const data = [1, 5, 3, 2]
const result = data.reduce((r, e, i) => {
r.push(i ? +r.slice(-1) + e : e)
return r;
}, []);
console.log(result)
You could also do this with just map() method using thisArg parameter and storing last value inside.
const data = [1, 5, 3, 2]
const result = data.map(function(e) {
return this.n += e
}, {n: 0});
console.log(result)
Or you could just create closure with IIFE and inside use map() method.
const data = [1, 5, 3, 2]
const result = (s => data.map(e => s += e))(0)
console.log(result)
I want to sort only odd numbers without moving even numbers. For example, when I write :
sortArray([5, 3, 2, 8, 1, 4])
The expected result is :
[1, 3, 2, 8, 5, 4]
I am new to JavaScript and I came across a challenge on the Internet that has me perplexed. I normally wouldn't post asking for a solution on the Internet, BUT I have tried for hours and I would like to learn this concept in JavaScript.
The challenge states :
You have an array of numbers.
Your task is to sort ascending odd numbers but even numbers must be on their places.
Zero isn't an odd number and you don't need to move it. If you have an empty array, you need to return it.
Here is my code so far, please take it easy on me I am in the beginning stages of programming.
function sortArray(array) {
let oddNums = [];
for(let i = 0; i < array.length; i++) {
if(array[i] % 2 !== 0) {
oddNums.push(array[i]);
}
}
oddNums = oddNums.sort((a,b)=> a-b);
array.concat(oddNums);
array = array.sort((a,b) => a-b);
return array;
}
You could take a helper array for the odd indices and another for the odd numbers, sort them and apply them back on the previously stored indices of the original array.
var array = [5, 3, 2, 8, 1, 4],
indices = [];
array
.filter((v, i) => v % 2 && indices.push(i))
.sort((a, b) => a - b)
.forEach((v, i) => array[indices[i]] = v);
console.log(array);
Here's a solution using mostly the built-in array methods. Get a list of just the odds, sort it, then map through the original, replacing each item with the first sorted odd if the item is odd, or itself if even:
const array = [5, 3, 2, 8, 1, 4] // to: [1, 3, 2, 8, 5, 4]
function sortOddsOnly(arr) {
const odds = arr
.filter(x => x%2)
.sort((a, b) => a - b);
return arr
.map(x => x%2 ? odds.shift() : x);
}
console.log(sortOddsOnly(array));
I have a solution like this.
Build a sorted odd number array 1st, and then fill the rest of even numbers in order:
const arr = [5, 3, 2, 8, 1, 4];
const odd = arr.filter(i => i%2 !== 0).sort();
let i = 0,
result = [];
arr.forEach(e => {
if (e%2 === 0) {
result.push(e)
} else {
result.push(odd[i]);
i++;
}
});
console.log(result);
just do:
arr.sort((a, b) => a%2 && b%2 ? a - b : 0)
If that works depends on the sort algorithm your browser uses.
A browserindependent version:
for(const [i1, v1] of arr.entries())
for(const [i2, v2] of arr.entries())
if( v1%2 && v2%2 && (i1 < i2) === (v1 > v2))
([arr[i1], arr[i2]] = [v2, v1]);
One of the possible solutions is this. What I have done is created new array odd(array with odd position in original array using Array.prototype.filter) and then sort that array using Array.prototype.sort. Then using Array.prototype.map change value of all odd element of original array with odd array.
x1=[5, 3, 2, 8, 1, 4];
function sortArray(array) {
var odd = array.filter((x,i) => (i+1) % 2 ).sort((a,b) => a > b); //sort odd position and store that in new array
return array.map((x,i) => (i+1) % 2 ? odd.shift() : x ); //if i is odd then replace it with element from
//odd array otherwise keep the element as it is
}
console.log(sortArray(x1));
Here is a possible solution using a slightly customized selection sort :
var xs = [5, 3, 2, 8, 1, 4];
console.log(sortOddsOnly(xs));
function sortOddsOnly (xs) {
var n = xs.length;
for (var i = 0; i < n - 1; i++) {
if (xs[i] % 2 === 1) {
for (var j = i + 1; j < n; j++) {
if (xs[j] % 2 === 1) {
if (xs[i] > xs[j]) {
var min = xs[j];
xs[j] = xs[i];
xs[i] = min;
}
}
}
}
}
return xs;
}
The first two if guarantee that we swap only odd numbers (x % 2 === 1 means "x is odd").
def sort_array(source_array):
b = sorted([n for n in source_array if n % 2 != 0])
c = -1
d = []
for i in source_array:
c = c+1
if i % 2 != 0 :
d.append(c)
for x in range (len(d)):
z = d[x]
source_array[z] = b[x]
return source_array
Say I have an array like this: [1, 1, 2, 2, 3]
I want to get the duplicates which are in this case: [1, 2]
Does lodash support this? I want to do it in the shortest way possible.
You can use this:
_.filter(arr, (val, i, iteratee) => _.includes(iteratee, val, i + 1))
Note that if a number appears more than two times in your array you can always use _.uniq.
Another way is to group by unique items, and return the group keys that have more than 1 item
_([1, 1, 2, 2, 3]).groupBy().pickBy(x => x.length > 1).keys().value()
var array = [1, 1, 2, 2, 3];
var groupped = _.groupBy(array, function (n) {return n});
var result = _.uniq(_.flatten(_.filter(groupped, function (n) {return n.length > 1})));
This works for unsorted arrays as well.
How about using countBy() followed by reduce()?
const items = [1,1,2,3,3,3,4,5,6,7,7];
const dup = _(items)
.countBy()
.reduce((acc, val, key) => val > 1 ? acc.concat(key) : acc, [])
.map(_.toNumber)
console.log(dup);
// [1, 3, 7]
http://jsbin.com/panama/edit?js,console
Another way, but using filters and ecmaScript 2015 (ES6)
var array = [1, 1, 2, 2, 3];
_.filter(array, v =>
_.filter(array, v1 => v1 === v).length > 1);
//→ [1, 1, 2, 2]
Here is another concise solution:
let data = [1, 1, 2, 2, 3]
let result = _.uniq(_.filter(data, (v, i, a) => a.indexOf(v) !== i))
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
_.uniq takes care of the dubs which _.filter comes back with.
Same with ES6 and Set:
let data = [1, 1, 2, 2, 3]
let result = new Set(data.filter((v, i, a) => a.indexOf(v) !== i))
console.log(Array.from(result))
Pure JS solution:
export function hasDuplicates(array) {
return new Set(array).size !== array.length
}
For an array of objects:
/**
* Detects whether an array has duplicated objects.
*
* #param array
* #param key
*/
export const hasDuplicatedObjects = <T>(array: T[], key: keyof T): boolean => {
const _array = array.map((element: T) => element[key]);
return new Set(_array).size !== _array.length;
};
Well you can use this piece of code which is much faster as it has a complexity of O(n) and this doesn't use Lodash.
[1, 1, 2, 2, 3]
.reduce((agg,col) => {
agg.filter[col] = agg.filter[col]? agg.dup.push(col): 2;
return agg
},
{filter:{},dup:[]})
.dup;
//result:[1,2]
here is mine, es6-like, deps-free, answer. with filter instead of reducer
// this checks if elements of one list contains elements of second list
// example code
[0,1,2,3,8,9].filter(item => [3,4,5,6,7].indexOf(item) > -1)
// function
const contains = (listA, listB) => listA.filter(item => listB.indexOf(item) > -1)
contains([0,1,2,3], [1,2,3,4]) // => [1, 2, 3]
// only for bool
const hasDuplicates = (listA, listB) => !!contains(listA, listB).length
edit:
hmm my bad is: I've read q as general question but this is strictly for lodash, however my point is - you don't need lodash in here :)
You can make use of a counter object. This will have each number as key and total number of occurrence as their value. You can use filter to get the numbers when the counter for the number becomes 2
const array = [1, 1, 2, 2, 3],
counter = {};
const duplicates = array.filter(n => (counter[n] = counter[n] + 1 || 1) === 2)
console.log(duplicates)
Hope below solution helps you and it will be useful in all conditions
hasDataExist(listObj, key, value): boolean {
return _.find(listObj, function(o) { return _.get(o, key) == value }) != undefined;
}
let duplcateIndex = this.service.hasDataExist(this.list, 'xyz', value);
No need to use lodash, you can use following code:
function getDuplicates(array, key) {
return array.filter(e1=>{
if(array.filter(e2=>{
return e1[key] === e2[key];
}).length > 1) {
return e1;
}
})
}
Why don't use just this?
_.uniq([4, 1, 5, 1, 2, 4, 2, 3, 4]) // [4, 1, 5, 2, 3]