Sorting an array of object - javascript

I wish to sort an array of medals. My first sort returns an array sorted according to the gold medals. I then wish to range those which are having the same gold but silver medals are different (same for bronze). I use the following codes that actually makes me run out of memory. This is my code:
static sort(data) {
let sorted = data.sort((a, b) => b.medal.gold - a.medal.gold);
let next, temp, current;
for (let i = 0; i < sorted.length; i++) {
current = sorted[i].medal;
if (sorted[i+1]) next = sorted[i+1].medal;
if (next) {
if (current.gold === next.gold) {
if (current.silver < next.silver) {
temp = sorted[i+1];
sorted[i+1] = sorted[i];
sorted[i] = temp;
}
else if (current.silver === next.silver) {
if (current.bronze < next.bronze) {
temp = sorted[i+1];
sorted[i+1] = sorted[i];
sorted[i] = temp;
}
}
}
}
}
return sorted;
}

You'll want to improve your compare function so it takes care of that requirement:
data.sort((a, b) => (b.medal.gold - a.medal.gold)
|| (b.medal.silver - a.medal.silver)
|| (b.medal.bronze - a.medal.bronze) )
And then you don't need the (endless) for loop at all.

You have to set next to null somewhere, because it keeps the value from the previous iteration and the if(next) is always true. Afterwards the function will always create one more element and add it in the array (sorted[i+1] = sorted[i]) until you run out of memory.
Here is a working example:
var rawData =
[{ id: 1, medal: {gold: 2, silver: 1, bronze: 1}},
{ id: 2, medal: {gold: 2, silver: 1, bronze: 2} },
{ id: 3, medal: {gold: 5, silver: 1, bronze: 4} } ];
function sortData(data) {
let sorted = data.sort((a, b) => b.medal.gold - a.medal.gold);
let next, temp, current;
for (let i = 0; i < sorted.length; i++) {
next = undefined;
current = sorted[i].medal;
if (sorted[i+1]) next = sorted[i+1].medal;
if (next) {
if (current.gold === next.gold) {
if (current.silver < next.silver) {
temp = sorted[i+1];
sorted[i+1] = sorted[i];
sorted[i] = temp;
}
else if (current.silver === next.silver) {
if (current.bronze < next.bronze) {
temp = sorted[i+1];
sorted[i+1] = sorted[i];
sorted[i] = temp;
}
}
}
}
}
return sorted;
};
console.log(sortData(rawData))
Please note that in the function you are using medal instead of medals as the data you have provided in one of your comments.

Related

Inconsistency, when returning index of duplicate values

I'm trying to create an algorithm to find duplicate values in a list and return their respective indexes, but the script only returns the correct value, when I have 2 equal elements:
array = [1,2,0,5,0]
result -> (2) [2,4]
Like the example below:
array = [0,0,2,7,0];
result -> (6) [0, 1, 0, 1, 0, 4]
The expected result would be [0,1,4]
Current code:
const numbers = [1,2,0,5,0];
const checkATie = avgList => {
let averages, tie, n_loop, currentAverage;
averages = [... avgList];
tie = [];
n_loop = 0;
for(let n = 0; n <= averages.length; n++) {
currentAverage = parseInt(averages.shift());
n_loop++
for(let avg of averages) {
if(avg === currentAverage) {
tie.push(numbers.indexOf(avg),numbers.indexOf(avg,n_loop))
};
};
};
return tie;
}
console.log(checkATie(numbers));
if possible I would like to know some way to make this code more concise and simple
Use a Set
return [...new Set(tie)]
const numbers1 = [1,2,0,5,0];
const numbers2 = [0,0,2,7,0];
const checkATie = avgList => {
let averages, tie, n_loop, currentAverage;
averages = [... avgList];
tie = [];
n_loop = 0;
for(let n = 0; n <= averages.length; n++) {
currentAverage = parseInt(averages.shift());
n_loop++
for(let avg of averages) {
if(avg === currentAverage) {
tie.push(avgList.indexOf(avg),avgList.indexOf(avg,n_loop))
};
};
};
return [...new Set(tie)]
}
console.log(checkATie(numbers1));
console.log(checkATie(numbers2));
I hope this help you.you can use foreach function to check each item of array
var array = [0,0,2,7,0];
var result = [] ;
array.forEach((item , index)=>{
if(array.findIndex((el , i )=> item === el && index !== i ) > -1 ){
result.push(index)
}
})
console.log(result);
//duplicate entries as an object
checkDuplicateEntries = (array) => {
const duplicates = {};
for (let i = 0; i < array.length; i++) {
if (duplicates.hasOwnProperty(array[i])) {
duplicates[array[i]].push(i);
} else if (array.lastIndexOf(array[i]) !== i) {
duplicates[array[i]] = [i];
}
}
console.log(duplicates);
}
checkDuplicateEntries([1,2,0,5,0]);
// hope this will help
Create a lookup object with value and their indexes and then filter all the values which occurred more than once and then merge all indexes and generate a new array.
const array = [1, 2, 0, 5, 0, 1, 0, 2],
result = Object.values(array.reduce((r, v, i) => {
r[v] = r[v] || [];
r[v].push(i);
return r;
}, {}))
.filter((indexes) => indexes.length > 1)
.flatMap(x => x);
console.log(result);

Find elements that commonly appear next to each other in an array

I am trying to find values that commonly appear next to each other in an array.
E.G. given the array:
["dog","cat","goat","dog","cat","elephant","dog","cat","pig","seal","dog","cat","pig","monkey"]
it should return something similar to:
[[["dog","cat"],4],[["cat","pig"],2],[["dog","cat","pig"],2]]
Here is some better data: https://pastebin.com/UG4iswrZ
Help would be greatly appreciated. Here is my current failed attempt at doing something similar:
function findAssociations(words){
var temp = [],tempStore = [],store = [],found = false;
//loop through the words counting occurrances of words together with a window of 5
for(var i = 0;i<words.length-1;i++){
if(i % 5 == 0){
//on every fith element, loop through store attempting to add combinations of words stored in tempStore
for(var j = 0;j<5;j++){
temp = []
//create the current combination
for(var k = 0;k<j;k++){
temp.push(tempStore[k]);
}
//find if element is already stored, if it is, increment the occurrence counter
for(var k = 0;k<store.length;k++){
if(store[k][0]===temp){
found = true;
store[k][1] = store[k][1]+1;
}
}
//if it isn't add it
if(found == false){
store.push([temp,1]);
}
found == false;
}
tempStore = [];
} else {
//add word to tempStore if it i isnt a multiple of 5
tempStore.push(words[i]);
}
}
}
This script is doesn't remove combinations that appear once,it doesn't sort the output by occurrences, nor does it work. It is just an outline of how a possible solution might work (as suggested by benvc).
Here is a generic solution working with multiple group sizes.
You specify a range of group sizes, for example [2,4] for groups of 2 to 4 elements and a minimum number of occurrences.
The function then generates all groups of neighbours of the given sizes, sorts each group and counts the duplicates. The sorting step can be removed is the order in the groups matters.
The duplicates are counted by creating a dictionary whose keys are the group elements sorted and jointed with a special marker. The values in the dictionary are the counts.
It then returns the groups sorted by occurences and then by group size.
const data = ["dog","cat","goat","dog","cat","elephant","dog","cat","pig","seal","dog","cat","pig","monkey"];
function findSimilarNeighbors(groupSizeRange, minOccurences, data) {
const getNeighbors = (size, arr) => arr.reduce((acc, x) => {
acc.push([]);
for (let i = 0; i < size; ++ i) {
const idx = acc.length - i - 1;
(acc[idx] || []).push(x);
}
return acc;
}, []).filter(x => x.length === size);
const groups = [];
for (let groupSize = groupSizeRange[0]; groupSize <= groupSizeRange[1]; ++groupSize) {
groups.push(...getNeighbors(groupSize, data));
}
const groupName = group => group.sort().join('###'); // use a separator that won't occur in the strings
const groupsInfo = groups.reduce((acc, group) => {
const name = groupName(group);
acc[name] = acc[name] || {};
acc[name] = { group, count: (acc[name].count || 0) + 1 };
return acc;
}, {});
return Object.values(groupsInfo)
.filter(group => group.count >= minOccurences)
.sort((a, b) => {
const countDiff = b.count - a.count;
return countDiff ? countDiff : b.group.length - a.group.length;
})
.map(({ group, count }) => [group, count]);
};
console.log(findSimilarNeighbors([2, 4], 2, data));
console.log(findSimilarNeighbors([4, 4], 2, data));
Here is what I came up with. It only finds pairs, but you could modify it to find sets of 3, 4, etc, based on what you % by
const animals = ['dog','cat','goat','dog','cat','elephant','dog','cat','pig','seal','dog','cat','pig','monkey'];
let pairs = ',';
animals.forEach((animal, i) => {
let separator = ',';
if (i % 2 === 0) {
separator = ';'
}
pairs += animal + separator;
});
const evenPairs = pairs.split(',');
const oddPairs = pairs.split(';');
const allPairs = evenPairs.concat(oddPairs).map(pair => pair.replace(/[;,]/, ' '));
let result = {}
allPairs.forEach(pair => {
if (pair.length) {
if (result[pair] === undefined) {
result[pair] = 1;
} else {
result[pair]++;
}
}
});
results in:
dog: 1
cat elephant: 1
cat goat: 1
cat pig: 2
dog cat: 4
elephant dog: 1
goat dog: 1
monkey : 1
pig monkey: 1
pig seal: 1
seal dog: 1
https://stackblitz.com/edit/typescript-wvuvnr
You need to be clear what you mean by close and how close. Just looking at first neighbours you could try:
const findAssociations = words => {
const associations = {}
for (let i = 0; i < words.length - 1; i++) {
const word = words[i]
const wordRight = words[i+1]
const wordOne = word < wordRight ? word : wordRight;
const wordTwo = word < wordRight ? wordRight : word;
const keys = Object.keys(associations)
const key = `${wordOne}:${wordTwo}`
if (keys.indexOf(key) >= 0) {
associations[key]++
} else {
associations[key] = 1
}
}
const keys = Object.keys(associations)
const values = Object.values(associations)
const zipped = keys.map((key, index) => [key, values[index]])
zipped.sort((a, b) => a[1] < b[1] ? 1 : -1);
return zipped;
}
https://stackblitz.com/edit/js-3ppdit
You can use this function inside another function and add every time an element to ["dog", "cat"]
const arr = ["dog", "cat", "goat", "dog", "cat", "dog", "cat", "elephant", "dog", "cat", "pig", "seal", "dog", "cat", "pig", "monkey"]
const findArrayInArray = (arr1, arr2) => {
let count = 0,
arrString1 = arr1.join(""),
arrString2 = arr2.join("");
while (arrString2.indexOf(arrString1) > -1) {
count += 1;
arrString2 = arrString2.replace(arrString1, '');
}
return count;
}
console.log(`["dog", "cat"] exist ${findArrayInArray(["dog", "cat"], arr)} times`)
Assuming each item in the list is a delimiter of a set, and each set counts once for each item (i.e. ["dog", "cat", "goat"] counts as ["dog", "cat"] and ["dog", "cat", "goat"], and assuming you don't want any single occurrences, then here's one way:
const full_list = ["dog","cat","goat","dog","cat","dog","cat","elephant","dog","cat","pig","seal","dog","cat","pig","monkey"];
// create list of unique items
const distinct = (value, index, self) => {
return self.indexOf(value) ===index;
}
const unique_items = full_list.filter(distinct);
// get all patterns
var pre_report = {};
for (var i in unique_items) {
item = unique_items[i];
var pattern = [item];
var appending = false;
for (var j = full_list.indexOf(item) + 1; j < full_list.length; ++j) {
const related_item = full_list[j];
if (item == related_item) {
pattern = [item]
continue;
}
pattern.push(related_item);
if (pattern in pre_report) {
++pre_report[pattern];
} else {
pre_report[pattern] = 1;
}
}
}
// filter out only single occurring patterns
var report = {};
for (key in pre_report) {
if (pre_report[key] > 1) {
report[key] = pre_report[key];
}
}
console.log(report);
produces:
{ 'dog,cat': 5, 'dog,cat,pig': 2, 'cat,pig': 2 }

Target specific attributes with merge sort

Implemented the merge sort algorithm in my javascript code.
I'm wonder how I can target specific attributes like date, title, name etc for sorting in an array when calling merge sort like mergeSort(array);.
function mergeSort(arr){
var len = arr.length;
if(len <2)
return arr;
var mid = Math.floor(len/2),
left = arr.slice(0,mid),
right =arr.slice(mid);
return merge(mergeSort(left),mergeSort(right));
}
function merge(left, right){
var result = [],
lLen = left.length,
rLen = right.length,
l = 0,
r = 0;
while(l < lLen && r < rLen){
if(left[l] < right[r]){
result.push(left[l++]);
}
else{
result.push(right[r++]);
}
}
return result.concat(left.slice(l)).concat(right.slice(r));
}
Using it in a sort options method. What I want is to print a sorted list. The way the list is sorted will be defined by the users chosen sort option.
function sortConfig(array, sortOption){
if(sortOption == 'title') mergeSort(array.Title);
//..etc
}
To implement the behavior with an optional argument, you could do it in the following way:
function mergeSort(arr, compare = (item => item))
This would set compare function to be the item itself when running the merge
and then we update the calling of the merge and mergeSort itself, where they now all get the compare argument
return merge(mergeSort(left, compare), mergeSort(right, compare), compare);
and ofcourse the declaration for your merge function itself
function merge(left, right, compare)
Which then calls the compare function upon comparison, like here:
if (compare(left[l]) < compare(right[r]))
This lets you choose wether you wish to give an argument or not wen you call your mergeSort function, like:
console.log(mergeSort(nrs).join(','));
console.log(mergeSort(nrs, n => -n).join(','));
console.log(mergeSort(arr, i => i.id));
console.log(mergeSort(arr, i => i.title));
function mergeSort(arr, compare = (item => item)) {
var len = arr.length;
if (len < 2)
return arr;
var mid = Math.floor(len / 2),
left = arr.slice(0, mid),
right = arr.slice(mid);
return merge(mergeSort(left, compare), mergeSort(right, compare), compare);
}
function merge(left, right, compare) {
var result = [],
lLen = left.length,
rLen = right.length,
l = 0,
r = 0;
while (l < lLen && r < rLen) {
if (compare(left[l]) < compare(right[r])) {
result.push(left[l++]);
} else {
result.push(right[r++]);
}
}
return result.concat(left.slice(l)).concat(right.slice(r));
}
var arr = [{
title: 'test 5',
id: 4
}, {
title: 'test',
id: 0
}, {
title: 'test 3',
id: 2
}, {
title: 'test 4',
id: 3
}];
var nrs = [5, 3, 7, 156, 15, 6, 17, 9];
// and call like
console.log(mergeSort(nrs).join(','));
console.log(mergeSort(nrs, n => -n).join(','));
// or like
console.log(mergeSort(arr, i => i.id));
console.log(mergeSort(arr, i => i.title));
For the sake of brevity, these examples show how to sort an array of objects based on a property with a string value. You would most likely need to create some additional logic to handle different types of properties.
1. Array.sort()
You can do this with the Array.sort() method
Fiddle Example
myThings = [
{ alpha: 'a' },
{ alpha: 'x' },
{ alpha: 'p' },
{ alpha: 'orange' },
{ alpha: 'c' },
{ alpha: 'w' }
];
myThings.sort(function(a, b) {
var alphaA = a.alpha.toUpperCase();
var alphaB = b.alpha.toUpperCase();
if (alphaA < alphaB) return -1;
if (alphaA > alphaB) return 1;
return 0;
});
console.log(myThings);
2. Or, compare array item property value instead of array item value
Fiddle Example
function mergeSort(arr, prop) {
if (arr.length < 2)
return arr;
var middle = parseInt(arr.length / 2);
var left = arr.slice(0, middle);
var right = arr.slice(middle, arr.length);
return merge(mergeSort(left, prop), mergeSort(right, prop), prop);
}
function merge(left, right, prop) {
var result = [];
while (left.length && right.length) {
if (left[0][prop] <= right[0][prop]) {
result.push(left.shift());
} else {
result.push(right.shift());
}
}
while (left.length)
result.push(left.shift());
while (right.length)
result.push(right.shift());
return result;
}
myThings = [
{ alpha: 'a' },
{ alpha: 'x' },
{ alpha: 'p' },
{ alpha: 'orange' },
{ alpha: 'c' },
{ alpha: 'w' }
];
console.log(mergeSort(myThings, 'alpha'));

How to reassign a list (in typescript) based on a "rank" object property?

I am trying to make a function to reassign a list based on their rank property.
For example:(my object has other property)
var array=[
{id:1,rank:2},
{id:18,rank:1},
{id:53,rank:3},
{id:3,rank:5},
{id:19,rank:4},//this item
]
This item {id:19,rank:4} is now in 2d position. The array becomes
item= { currentRank: 4; newRank: 2} //see below
array=[
{id:1,rank:3},
{id:18,rank:1},
{id:53,rank:4},
{id:3,rank:5},
{id:19,rank:2},
]
FYI : These items are re-order after a html drag&drop operation.
So I am trying to make a function to re-assign ranks based on the droped item rank.
I know the drop item new rank and its old rank.
So far I have done the following but it is not working for all cases:
public reorderArray(item: { currentRank: string; newRank: string }, array: { id: string, rank: string }[]): { id: string, rank: string } [] {
let arr = array.map(a => Object.assign({}, a)).sort((a, b) => (parseInt(a.rank) - parseInt(b.rank))).slice();
//To avoid to change the reference??
let isOrdered = arr.every((element, index, array) => {
return array[index + 1] ? element.rank + 1 == array[index + 1].rank : true
});
if (isOrdered && arr[0].rank == (1).toString()) {
if (parseInt(item.currentRank) < parseInt(item.newRank)) {
//on descend un élément dans la liste => +1 entre le currentRank et )le newRank
for (let i = parseInt(item.currentRank); i < parseInt(item.newRank); i++) {
arr[i].rank = (parseInt(arr[i].rank) - 1).toString();
}
arr[parseInt(item.currentRank)].rank = (parseInt(item.newRank)).toString();
}
else if (parseInt(item.currentRank) > parseInt(item.newRank)) {
for (let i = parseInt(item.newRank); i < parseInt(item.currentRank); i++) {
arr[i].rank = (parseInt(arr[i].rank) + 1).toString();
}
arr[parseInt(item.currentRank)].rank = (parseInt(item.newRank) + 1).toString();
}
return arr
}
else {
alert("This list is not ordered");
}
}
nb: if array is not properly oredered (rank is 1,3,4...), function doesn't do anything.
You could use an array for splicing and iterate then for the correction of the range.
function changeRank(object) {
ranks.splice(object.newRank - 1, 0, ranks.splice(object.currentRank - 1, 1)[0]);
ranks.forEach(function (a, i) {
a.rank = i + 1;
});
}
var array = [{ id: 1, rank: 2 }, { id: 18, rank: 1 }, { id: 53, rank: 3 }, { id: 3, rank: 5 }, { id: 19, rank: 4 }],
ranks = [];
array.forEach(a => ranks[a.rank - 1] = a);
console.log(array);
changeRank({ currentRank: 4, newRank: 2 });
console.log(array);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I think you might be approaching this incorrectly.
Why not loop through all of the items and then if the rank is equal too or great then the current one increase it's rank? Then once you're done set the rank for the updated item:
Something like this:
for(var x = 0; x < items.length; x++){
if(items[x].rank >= item.newRank && items[x].rank <= item.currentRank){
items[x].rank++;
}
}
item.rank = item.newRank;
This logic must work. I've done it with the concept of array. Consider array index as rank.
if (new_rank < current_rank)
{
item = arr[current_rank]
i = new_rank;
temp = arr[i];
i++;
while(i<current_rank)
{
temp1 = arr[i];
arr[i] = temp;
temp = temp1;
i++;
}
arr[new_rank] = item;
}
else
{
item = arr[current_rank]
i = new_rank;
temp = arr[i];
i--;
while(i>current_rank)
{
temp1 = arr[i];
arr[i] = temp;
temp = temp1;
i--;
}
arr[new_rank] = item;
}

I'm having trouble displaying my randomly selected object

I am having trouble displaying the random object and the properties of that random object. The goal of this project is to have a list of stockItems, and when I press a button, it selects a determined number of those objects and displays them in an HTML p tag. Right now when I try to display it, it prints out as [object]. The goal is to have the properties of the selected object on different lines.
Here is the code I am working with:
function buildShopItems(count) {
var shopItems = [], i, itemIndex;
count = stockItems.length < count ? stockItems.length : count;
function getUniqueRandomItem() { //from stock
var item;
while (true) {
item = stockItems[Math.floor(Math.random() * stockItems.length)];
if (shopItems.indexOf(item) < 0) return item;
}
}
for (i = 0; i < count; i++) {
shopItems.push(getUniqueRandomItem());
}
return shopItems;
console.log(shopItems);
}
var stockItems = [
{ item: "sword", type: "weapon", weight: "5 lbs.", cost: "10 gold" },
{ item: "hammer", type: "weapon", weight: "8 lbs.", cost: "7 gold" }
//...
];
var shopItems = buildShopItems(1);
console.log(shopItems);
document.getElementById("item").innerHTML = shopItems.item;
document.getElementById("type").innerHTML = shopItems.type;
document.getElementById("weight").innerHTML = shopItems.weight;
document.getElementById("cost").innerHTML = shopItems.cost;
The problem was with your usage of indexOf. You can use indexOf to search for an object because in javascript you can't compare object using == or === and indexOf uses ===. Also made some syntax updates for you.
'use strict'
const stockItems = [
{ item: "sword", type: "weapon", weight: "5 lbs.", cost: "10 gold" },
{ item: "hammer", type: "weapon", weight: "8 lbs.", cost: "7 gold" }
];
function isEquivalent(a, b) {
// Create arrays of property names
const aProps = Object.getOwnPropertyNames(a);
const bProps = Object.getOwnPropertyNames(b);
// If number of properties is different,
// objects are not equivalent
if (aProps.length != bProps.length) {
return false;
}
for (let i = 0; i < aProps.length; i++) {
const propName = aProps[i];
// If values of same property are not equal,
// objects are not equivalent
if (a[propName] !== b[propName]) {
return false;
}
}
// If we made it this far, objects
// are considered equivalent
return true;
}
// normal indexof will not work with object because it uses strict equality
function myIndexOf(array, object) {
for (let i = 0; i < array.length; i++) {
if (isEquivalent(array[i], object)) return i;
}
return -1;
}
function getUniqueRandomItem(shopItems) { //from stock
var item;
while (true) {
item = stockItems[Math.floor(Math.random() * stockItems.length)];
if (myIndexOf(shopItems, item) < 0) return item;
}
}
function buildShopItems(count) {
count = stockItems.length < count ? stockItems.length : count;
const shopItems = [];
for (let i = 0; i < count; i++) {
const item = getUniqueRandomItem(shopItems);
shopItems.push(item);
}
return shopItems;
}
const shopItems = buildShopItems(1);
console.log(shopItems);

Categories