Sort Array by occurrence of its elements - javascript

I'm looking for an elegant way of sorting an array by the occurrence of its elements.
For example, in:
['pear', 'apple', 'orange', 'apple', 'orange', 'apple']
the output should look like
['apple', 'orange', 'pear']
I have tried to loop through the array and save the occurrence in another temporary array, but this solution was quite bad.

It would require two loops.
var arr = ['pear', 'apple', 'orange', 'apple', 'orange', 'apple'];
//find the counts using reduce
var cnts = arr.reduce( function (obj, val) {
obj[val] = (obj[val] || 0) + 1;
return obj;
}, {} );
//Use the keys of the object to get all the values of the array
//and sort those keys by their counts
var sorted = Object.keys(cnts).sort( function(a,b) {
return cnts[b] - cnts[a];
});
console.log(sorted);

Map the values into a fruit→count associated object:
var counted = fruits.reduce(function (acc, fruit) {
if (acc[fruit]) {
acc[fruit]++;
} else {
acc[fruit] = 1;
}
return acc;
}, {});
Map that object into a sortable array:
var assoc = counted.keys().map(function (fruit) {
return [fruit, counted[fruit]];
});
Sort the array:
assoc.sort(function (a, b) { return a[1] - b[1]; });
Extract the values:
var result = assoc.map(function (i) { return i[0]; });

You can reduce your array to remove duplicates and then sort with custom comparator:
var sorted = ['pear', 'apple', 'orange', 'apple', 'orange', 'apple'].reduce(function(result, item) {
result.every(function(i) {
return i != item;
}) && result.push(item);
return result;
}, []).sort(function(i1, i2) {
return i1 > i2;
});
console.log(sorted);

With linq.js it is pretty easy (with example):
var array = ['pear', 'apple', 'orange', 'apple', 'orange', 'apple'];
var res = Enumerable.From(array).GroupBy(function (x) { return x; }).Select(function (x) { return { key: x.Key(), count: x.Count() } }).OrderByDescending(function (x) { return x.count }).Select(function (x) { return x.key}).ToArray();

You could try out lodash. All you need to do is group, sort, then map and you are done.
var arr = ['pear', 'apple', 'orange', 'apple', 'orange', 'apple'];
document.body.innerHTML = _(arr).chain().groupBy()
.sortBy(function(a) {
return -a.length; // <- Note: Remove the negation to sort in ascending order.
}).map(function(a) {
return a[0];
}).value().join(', ');
<script src="https://cdn.rawgit.com/lodash/lodash/master/dist/lodash.js"></script>

Try this:
var arr = ['pear', 'apple', 'orange', 'apple', 'orange', 'apple'];
var result = [];
var count = 0;
arr.sort();
for (var i = 0; i < arr.length; i++) {
count++;
if (arr[i] != arr[i + 1]) {
result.push({
"count": count,
"value": arr[i]
});
count = 0;
}
}
result.sort(function(a, b) {
if (a.count < b.count) return 1;
if (a.count > b.count) return -1;
return 0;
});
console.log(result.map(function(item){return item.value}));

Related

get previous item seen in JS for of loop?

in my for element of array loop, I want to access the elements besides the current one. Specifically, the previous or next element. I would also like this to reach across the first/last element barrier. How can this be achieved?
ie. given:
my_fruits = ['apple', 'banana', 'grapes', 'blueberries']
for (const fruit of my_fruits) {
// When fruit === 'banana', I want to access 'apple',
// when fruit === 'blueberries' I want to access 'grapes'.
// Also when it's the last element, I want to acknowledge that and access the first.
}
Due to how I'm dealing with async/await in this local, .forEach is something I'd prefer to avoid.
Thanks
You should use forEach loop and use the second parameter which is index.
const arr = ['apple', 'banana', 'grapes', 'blueberries']
arr.forEach((x,i) => {
let prev = i > 0 ? arr[i - 1] : null;
let next = i < arr.length ? arr[i + 1] : null;
console.log(`current:${x} next:${next} previous: ${prev}`)
})
If you don't want to use forEach you can use for..in. But besure about this you are not adding any properties other than indexes on the array.
const arr = ['apple', 'banana', 'grapes', 'blueberries']
for(let i in arr){
console.log(`current:${arr[+i]} next:${arr[+i+1]} previous: ${arr[+i-1]}`)
}
You could use forEach. The third parameter of the callback function is the array. So, you could destructure it to get the [i-1] and [i+1] th items
const my_fruits = ['apple', 'banana', 'grapes', 'blueberries']
my_fruits.forEach((item, i, { [i-1]: prev, [i+1]: next }) => {
console.log(item, prev, next)
})
You can access the index of the current element.
var my_fruits = ['apple', 'banana', 'grapes', 'blueberries']
for (const [index,value] of my_fruits.entries()) {
if(index) console.log(my_fruits[index-1])
}
You can use a regular for (let i = 0; i < len; i++)
let my_fruits = ['apple', 'banana', 'grapes', 'blueberries'];
for (let i = 0, len = my_fruits.length; i < len; i++)
{
if (i == 0)
{
console.log("I am at first iteration. The last item is : "+ my_fruits[len - 1]);
}
if (i > 0)
{
console.log("I have now : " + my_fruits[i] + " and I have access to " + my_fruits[i - 1]);
}
if (i + 1 == len)
{
console.log("I am at last iteration. The first item is : "+ my_fruits[i - 1]);
}
}
I hope that what You need
const my_fruits = ['apple', 'banana', 'grapes', 'blueberries']
for (let fruit in my_fruits) {
fruit = parseInt(fruit);
if (! my_fruits[fruit + -1]) {
console.log(my_fruits[0] + " => " + my_fruits[fruit + my_fruits.length - 1]);
} else {
console.log(my_fruits[fruit] + " => " + my_fruits[fruit + -1]);
}
}
const arr = ['apple', 'banana', 'grapes', 'blueberries']
arr.forEach((x,i) => {
let prev = i > 0 ? arr[i - 1] : null;
let next = i + 1 < arr.length ? arr[i + 1] : null;
console.log(`current:${x} next:${next} previous: ${prev}`)
})
I used Maheer Ali's code above, but I had to make a change.
I put i + 1 < arr.length instead of i < arr.length because otherwise it always returns true (the index, starting from 0, will always be less than the length of the array, if it exists).

How to push object to array from for loop properly in JavaScript?

I want to make an array that including object by for loop but there is a problem, The shape what I want is below :
[
{ data: 'apple', label: 'Fruits' },
{ data: 'banana', label: 'Fruits' },
{ data: 'mango', label: 'Fruits' }
]
So I tried to below way, but It's not working properly.
var arr = [];
obj = {};
var fruits = ['banana', 'apple', 'mango'];
var label = 'Fruits';
for (var i=0; i<fruits.length; i++){
obj['data'] = fruits[i];
obj['label'] = label;
arr.push(obj);
}
console.log(arr);
The result is just same object pushed.
[
{ data: 'apple', label: 'Fruits' },
{ data: 'apple', label: 'Fruits' },
{ data: 'apple', label: 'Fruits' }
]
Is this because of closer function ? How can I make it well?
That's happening because the obj object is referencing to the same object and it is updated in each iteration.
The same object obj is referenced inside the loop
Move the object declaration inside the loop to create a new object in each iteration.
for(var i = 0; i < fruits.length; i++) {
var obj = {}; // <---- Move declaration inside loop
obj['data'] = fruits[i];
obj['label'] = label;
arr.push(obj);
}
var arr = [];
var fruits = ['banana', 'apple', 'mango'];
var label = 'Fruits';
for(var i = 0; i < fruits.length; i++) {
var obj = {};
obj['data'] = fruits[i];
obj['label'] = label;
arr.push(obj);
}
console.log(arr);
A simple way to avoid this is using Array#map to create new array from old.
var arr = fruits.map(fruit => ({
data: fruit,
label: label
}));
var arr = [],
fruits = ['banana', 'apple', 'mango'],
label = 'Fruits';
var arr = fruits.map(fruit => ({
data: fruit,
label: label
}));
console.log(arr);
You are always overwriting the same object.
You need after the for line
obj = {};
for creating an empty object
var arr = [],
obj,
fruits = ['banana', 'apple', 'mango'],
label = 'Fruits',
i;
for (i = 0; i < fruits.length; i++){
obj = {}; // <----- new Object
obj['data'] = fruits[i];
obj['label'] = label;
arr.push(obj);
}
document.write('<pre>' + JSON.stringify(arr, 0, 4) + '</pre>');
A shorter way would be the use of Array#map()
var arr = [],
fruits = ['banana', 'apple', 'mango'],
label = 'Fruits';
arr = fruits.map(function (a) {
return { data: a, label: label };
});
document.write('<pre>' + JSON.stringify(arr, 0, 4) + '</pre>');
You should create new object obj inside the loop, you was always reference to the same object.
var arr = [];
var fruits = ['banana', 'apple', 'mango'];
var label = 'Fruits';
for (var i = 0; i < fruits.length; i++) {
var obj = {};
obj['data'] = fruits[i];
obj['label'] = label;
arr.push(obj);
}
I've had a similar issue. This is because when you do following, you're only pushing a 'reference' to the object and when the object value is updated in the loop, the object that was pushed also changes value because it was merely a 'reference' and eventually the last value that was set in the loop is also set in the 'references', hence you see multiple values of the last object that was pushed.
arr.push(obj);
So, to tackle this issue you do the following:
arr.push(JSON.parse(JSON.stringify(obj))); //see Note below
The other way to prevent references is by doing the following:
let obj = { a: 1 };
let copy = Object.assign({}, obj); // Object.assign(target, ...sources)
obj.a = 2;
console.log(copy); // { a: 1 }
console.log(obj); // { a: 2 }
Note: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#warning_for_deep_clone

search an array for duplicates, javascript [duplicate]

This question already has answers here:
Get all non-unique values (i.e.: duplicate/more than one occurrence) in an array
(97 answers)
Closed 6 years ago.
Okay I'm trying to search an array and find the duplicates and return the number of times each of the duplicates occurs. This is what I have so far, I need to pass in two arguments first the array being searched and then a specific term within that array:
countMatchingElements = function(arr, searchTerm){
var count = 0;
for(i = 0; i <= arr.length; i++){
count++;
}
return count;
};
Array I want to search:
var arrayToSearch = ['apple','orange','pear','orange','orange','pear'];
var arrayToSearch = ['apple', 'orange', 'pear', 'orange', 'orange', 'pear'];
var counter = {};
arrayToSearch.forEach(function(e) {
if (!counter[e]) {
counter[e] = 1;
} else {
counter[e] += 1
}
});
console.log(counter); //{ apple: 1, orange: 3, pear: 2 }
Something like this might do the trick:
var arrayToSearch = ['apple', 'orange', 'pear', 'orange', 'orange', 'pear'];
countMatchingElements = function(arr, searchTerm) {
return arr.filter(function(item) { return item === searchTerm; }).length;
};
document.writeln('"orange" appears ' + countMatchingElements(arrayToSearch, 'orange') + ' times.');

Dedup array and sort array by most frequent occurrence (with LoDash)

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()

sorting string function by custom alphabet javascript

Trying to sort an array of strings based on a custom alphabet. Probably some unnecessary code in there, but that was a couple different iterations mixed into one.
I am doing a base sort of the first letters, and if that doesn't work, I call the deep sort function and start working down the letters. But the result is only sorted by first letter, and the latter sorting seems to be arbitrary.
Any help?
var wordArray = ['apple', 'abbot', 'aatrophy', 'banana', 'berry', 'cherrypie', 'cherry', 'candy', 'grapefruit', 'pear', 'pizza', 'zebra', 'cigarette', 'guitar'];
var wordToLetterArray = [];
// var sortingString = "kwfhjrsbdtqmxaopzvieulgcny";
var sortingString = "abcdefghijklmnopqrstuvwxyz";
var deepSort = function(wordArray1, wordArray2) {
var forLoopIterations = 0;
if (wordArray1 && wordArray2) {
if (wordArray1.length > wordArray2.length) {
forLoopIterations = wordArray2.length;
} else {
forLoopIterations = wordArray1.length;
}
for (var i = 0; i <= forLoopIterations; i++) {
if (sortingString.indexOf(wordArray1[i]) > sortingString.indexOf(wordArray2[i])) {
return -1;
} else if (sortingString.indexOf(wordArray1[i]) < sortingString.indexOf(wordArray2[i])) {
return 1
} else {
if (i >= forLoopIterations) {
if (wordArray1.length > wordArray2.length) {
return 1;
} else if (wordArray1.length < wordArray2.length) {
return -1
} else {
return 0
}
} else {
}
}
};
} else {
return 0;
}
}
var populateWordToLetterArray = function() {
for (var i = 0; i <= wordArray.length - 1; i++) {
wordToLetterArray.push([]);
for (var x = 0; x <= wordArray[i].length - 1; x++) {
wordToLetterArray[i].push(wordArray[i][x]);
};
};
sortWordArraybyFirstLetter();
}
var sortWordArraybyFirstLetter = function sortWordArraybyFirstLetter() {
wordArray.sort(function(a, b) {
var aIndex = sortingString.indexOf(a[0]);
var bIndex = sortingString.indexOf(b[0]);
if (aIndex > bIndex) {
return 1;
} else if (aIndex < bIndex) {
return -1;
} else {
return deepSort(wordToLetterArray[wordArray.indexOf(a)], wordToLetterArray[wordArray.indexOf(b)]);
}
})
}
populateWordToLetterArray();
console.log(wordArray);
console.log(wordToLetterArray);
Make a function that "translates" a word into your custom alphabet and then sort the words by comparing their "translations":
function translate(str, alphabet) {
var abc = "abcdefghijklmnopqrstuvwxyz";
return [].map.call(str, function(c) {
return alphabet[abc.indexOf(c)] || c;
}).join("");
}
var wordArray = ['apple', 'abbot', 'aatrophy', 'banana', 'berry', 'cherrypie', 'cherry', 'candy', 'grapefruit', 'pear', 'pizza', 'zebra', 'cigarette', 'guitar'];
var sortingString = "kwfhjrsbdtqmxaozpvieulgcny";
wordArray.sort(function(a, b) {
return translate(a, sortingString).localeCompare(translate(b, sortingString));
});
document.write(wordArray)
This isn't particularly efficient, but there's room for optimizations.
It's very difficult to reason about code when you're nesting that deep. What you need is a clean way of producing a function to compare two strings based on your sort order. Once you have that, everything gets simpler.
The following should work for that:
function makeComparer(order) {
var ap = Array.prototype;
// mapping from character -> precedence
var orderMap = {},
max = order.length + 2;
ap.forEach.call(order, function(char, idx) {
orderMap[char] = idx + 1;
});
function compareChars(l, r) {
var lOrder = orderMap[l] || max,
rOrder = orderMap[r] || max;
return lOrder - rOrder;
}
function compareStrings(l, r) {
var minLength = Math.min(l.length, r.length);
var result = ap.reduce.call(l.substring(0, minLength), function (prev, _, i) {
return prev || compareChars(l[i], r[i]);
}, 0);
return result || (l.length - r.length);
}
return compareStrings;
}
var comparer = makeComparer('abcdefghijklmnopqrstuvwxyz');
console.log(comparer('apple', 'abbot'));
console.log(comparer('abbot', 'apple'));
console.log(comparer('apple', 'apple'));
console.log(comparer('apple', 'apple pie'));
console.log(comparer('apple pie', 'apple'));
Once you have that, sorting is as simple as using the built-in sort method:
var comparer = makeComparer('abcdefghijklmnopqrstuvwxyz');
var wordArray = ['apple', 'abbot', 'aatrophy', 'banana',
'berry', 'cherrypie','cherry', 'candy',
'grapefruit', 'pear', 'pizza', 'zebra',
'cigarette', 'guitar'];
wordArray.sort(comparer);
Full solution:
function makeComparer(order) {
var ap = Array.prototype;
// mapping from character -> precedence
var orderMap = {},
max = order.length + 2;
ap.forEach.call(order, function(char, idx) {
orderMap[char] = idx + 1;
});
function compareChars(l, r) {
var lOrder = orderMap[l] || max,
rOrder = orderMap[r] || max;
return lOrder - rOrder;
}
function compareStrings(l, r) {
var minLength = Math.min(l.length, r.length);
var result = ap.reduce.call(l.substring(0, minLength), function (prev, _, i) {
return prev || compareChars(l[i], r[i]);
}, 0);
return result || (l.length - r.length);
}
return compareStrings;
}
var wordArray = ['apple', 'abbot', 'aatrophy', 'banana',
'berry', 'cherrypie','cherry', 'candy',
'grapefruit', 'pear', 'pizza', 'zebra',
'cigarette', 'guitar'];
var comparer = makeComparer('abcdefghijklmnopqrstuvwxyz');
console.log(wordArray.slice().sort(comparer));
var weirdComparer = makeComparer("kwfhjrsbdtqmxaopzvieulgcny");
console.log(wordArray.slice().sort(weirdComparer));

Categories