Find the first unused property value in an array of javascript objects - javascript

In looking for a way to efficiently determine the lowest positive integer that is not used as a value of a specific property in any object within an array of objects.
In other words, I'm looking for a function/algorithm that for these arrays:
var example1 = [{ id: 1 }, { id: 2 }, { id: 3 }],
example2 = [{ id: 6 }, { id: 4 }, { id: 2 }],
example3 = [{ id: 2 }, { id: 1 }, { id: 4, otherProp: 3 }];
Would return 4, 1 and 3 respectively. (Obviously using the id-property, in this example.)
I considered using Underscore.js for this, but I couldn't find a way to do it without some ugly nested loops. Does anybody have a better idea?

function next(prop) {
return function(arr) {
var used = arr.reduce(function(o, v) {
o[v[prop]] = true;
return o;
}, {});
for (var i=1; used[i]; i++);
return i;
}
}
var nextId = next("id");
nextId([{ id: 1 }, { id: 2 }, { id: 3 }]) // 4
nextId([{ id: 6 }, { id: 4 }, { id: 2 }]) // 1
nextId([{ id: 2 }, { id: 1 }, { id: 4, otherProp: 3 }]) // 3

One possible approach:
function aiKey(arr, prop) {
var indices = [];
arr.forEach(function(el) {
indices[ el[prop] ] = true;
});
for (var i = 1, l = indices.length; i < l; i++) {
if (indices[i] === undefined) {
break;
}
}
return i;
}
aiKey(example1, 'id'); // 4
aiKey(example2, 'id'); // 1
aiKey(example3, 'id'); // 3

Related

Choose 9 sets of 5 random items from Array without repeating them with Javascript

So I have an array with 45 items, each item has 2 items, local and visitor team.
I need to create a function that creates 9 matchdays with 5 games in each matchday, but the games can't repeat.
I tried doing this:
const getRandomGame = (items, idsToAvoid) => {
const game = getRandomFromArray(items);
if (idsToAvoid.includes(game[0].id, game[1].id))
return getRandomGame(items, idsToAvoid);
return game;
};
const getRandomFromArray = (items) => {
return items[Math.floor(Math.random() * items.length)];
};
// this is inside another function that tries to generate the matchdays
for (let i = 0; i < 9; i++) {
counter++;
let games = [];
let avoidIds = [];
for (let j = 0; j < 5; j++) {
const game = getRandomGame(copyFinalGames, avoidIds);
const localRng = Math.random() < 0.5;
const local = localRng ? game[0] : game[1];
const visitor = localRng ? game[1] : game[0];
avoidIds.push(local.id, visitor.id);
games.push({
id: counter,
local,
visitor,
});
copyFinalGames = copyFinalGames.filter(
(item) => !(item[0].id === game[0].id && item[1].id === game[1].id)
);
}
gamedays.push({
id: i + 1,
games,
});
}
Which doesn't quite work (games are being repeated) + it's not efficient cause it tries to brute force the rule out repeated games. Any ideas?
The original array looks like this:
[{ id: 1 }, { id: 2}],
[{ id: 3 }, { id: 4}],
[{ id: 5 }, { id: 6}] // here only showing three rows, but it has the 45 possible games
OK, here's an attempt:
const matchupList =
[
[{ id: 1 }, { id: 2}],
[{ id: 3 }, { id: 4}],
[{ id: 5 }, { id: 6}],
[{ id: 6 }, { id: 5}],
[{ id: 3 }, { id: 9}],
[{ id: 8 }, { id: 2}],
[{ id: 4 }, { id: 1}],
[{ id: 2 }, { id: 6}],
[{ id: 7 }, { id: 8}],
[{ id: 5 }, { id: 3}],
[{ id: 3 }, { id: 1}],
[{ id: 2 }, { id: 7}],
[{ id: 7 }, { id: 1}],
[{ id: 4 }, { id: 6}],
[{ id: 7 }, { id: 2}],
[{ id: 8 }, { id: 1}],
[{ id: 9 }, { id: 3}],
[{ id: 5 }, { id: 4}],
[{ id: 5 }, { id: 0}],
[{ id: 0 }, { id: 3}],
[{ id: 1 }, { id: 0}],
[{ id: 0 }, { id: 8}],
[{ id: 9 }, { id: 8}]
];
const pickMatch = (list) => list[Math.floor(Math.random() * list.length)];
const pickMatchDay = (matchupList, matchDaySize) =>
{
let matchChosen;
let teamSeen;
let workingList;
do
{
matchChosen = [];
teamSeen = [];
// Create a copy of the original matchup list so we can remove matches we've already tried
workingList = matchupList.concat();
while(matchChosen.length < matchDaySize && workingList.length)
{
let local, visitor;
let teamPairing = pickMatch(workingList);
let fromId = matchupList.indexOf(teamPairing);
teamPairing = teamPairing.map(i => i.id);
// If this pairing has NO teams we've already seen...
if(teamPairing.findIndex(i => teamSeen.includes(i)) === -1)
{
// Randomly decide who is local/visitor
if(Math.random() < 0.5)
[local, visitor] = teamPairing;
else
[visitor, local] = teamPairing;
matchChosen.push(
{
id: matchChosen.length + 1,
local,
visitor,
fromId
});
// Push this pairing's teams to the list of teams we've seen
teamSeen.push(...teamPairing);
}
// Remove this matchup from the possible matchups we can choose
workingList.splice(workingList.indexOf(teamPairing), 1);
}
// Due to the limited number of pairings available (and that pairings are variable),
// it's quite possible we'll reach this point with only 3 or 4 matches chosen,
// but having exhausted all available matchups.
// If this happens, we start over.
// Not really necessary if you can guarantee 45 matchups and 5 matches per day
} while(matchChosen.length < matchDaySize);
return(matchChosen);
};
const gamedays = [];
for(let index = 0; index < 5; index++)
gamedays.push({id: index + 1, games: pickMatchDay(matchupList, 5)});
console.log(gamedays);
Some notes:
Pairings can still be repeated across different days. You might find that team 1 plays team 2 on both day 1 and day 3, for instance.
pickMatchDay has a real possibility of infinitely looping, e.g. you can ask for 6 games, which is just impossible with only 10 teams.

How can add properties to my objects based on the duplicates inside the array?

I have this array of objects
let arr = [
{
id: 1,
},
{
id: 1,
},
{
id: 2,
},
{
id: 1,
},
{
id:4,
},
{
id: 3,
},
{
id:4,
}
]
i need to find and change every object in the array based on condition.
So if there are duplicates in the array i need to set on my objects 100 except last duplicate where i should have 200.
If i don't have any duplicates than i should have again 200
So the output shpuld be
let arr = [
{
id: 1,
number: 100
},
{
id: 1,
number: 100
},
{
id: 2,
number: 200
},
{
id: 1,
number: 200
},
{
id:4,
number: 100
},
{
id: 3,
number: 200
},
{
id:4,
number: 200
}
]
so id 1 has duplicates.
That is why the fiurst occurences are set with number:100 and the last one i set with number:200.
Id 2 has number 200 because there are no duplicates and it is first occurance in the list.
what i tried
I got stuck at
for(let item of arr) {
for(let item2 of arr) {
if(item.id === item2.id) {
item.number = 100;
} else {
item.number = 200;
}
}
}
You can simply iterate through the array in reverse and track which ids you've seen, here using a Set.
const arr = [{ id: 1, }, { id: 1, }, { id: 2, }, { id: 1, }, { id: 4, }, { id: 3, }, { id: 4, }]
let i = arr.length;
const seen = new Set();
while (i--) {
arr[i].number = seen.has(arr[i].id) ? 100 : 200;
seen.add(arr[i].id)
}
console.log(arr)
You can use array.map() to iterate over your array. I think it can provide a nice and concise solution:
const result = arr.map((item, index) => {
const duplicate = arr.filter((_, indx) => indx > index).some((i) => i.id === item.id);
return { ...item, number: duplicate ? 100 : 200 }
});
console.log(result);
We can simply achieve it via Array.map() along with Array.indexOf() & Array.lastIndexOf() methods.
Working Demo :
// Input array
let arr = [{
id: 1,
}, {
id: 1,
}, {
id: 2,
}, {
id: 1,
}, {
id:4,
}, {
id: 3,
}, {
id:4,
}];
// Getting ID's from each object and create a seperate array
let idArr = arr.map(function(item) { return item.id });
// Iterating through the id's array and assigning number property to an original array as per the requirement.
idArr.forEach((item, index) => {
if (idArr.indexOf(item) === idArr.lastIndexOf(item)) {
arr[index].number = 200;
} else {
arr[index].number = 100;
arr[idArr.lastIndexOf(item)].number = 200;
}
});
// Result
console.log(arr);

Move selected array index to a specified location

I'm trying to reorder a selected number of array base on selected ids. For example, I have an array of:
[ { id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 } ]
And a selected id of 1,2,4. The proper arrangement should be:
[ { id: 3 }, { id: 1 }, { id: 2 }, { id: 4 }, { id: 5 }]
I've managed to get the arrangement to work for one selected id, but when multiple ids are selected it fails on different test cases. These all asume the same input as above.
Input 1: [ 1, 2, 4 ], move to index 1:
[ { id: 3 }, { id: 1 }, { id: 2 }, { id: 4 }, { id: 5 } ]
Input 2: [ 1, 3, 4 ], move to index 1:
[ { id: 2 }, { id: 1 }, { id: 3 }, { id: 4 }, { id: 5 } ]
Input 3: [ 1, 3, 5 ], move to index 1:
[ { id: 2 }, { id: 1 }, { id: 3 }, { id: 5 }, { id: 4 } ]
Input 4: [ 1, 2 ], move to index 0 or 1:
[ { id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 } ]
Input 5: [ 4, 5 ], move to index 3 or 4:
[ { id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 } ]
/**
* Function to move array
*/
function array_move(arr, old_index, new_index) {
if (new_index >= arr.length) {
var k = new_index - arr.length + 1;
while (k--) {
arr.push(undefined);
}
}
arr.splice(new_index, 0, arr.splice(old_index, 1)[0]);
};
/**
* Function to find the index
*/
function findWithAttr(array, attr, value) {
for (var i = 0; i < array.length; i += 1) {
if (array[i][attr] === value) {
return i;
}
}
return -1;
}
/**
* Move array to specified position
*/
function moveToSpecifiedInput(selectedImage, iMoveTo) {
selectedImage.reverse();
selectedImage.forEach(function(aData) {
let old_index = findWithAttr(aImageData, 'id', aData);
let new_index = iMoveTo - 1;
array_move(aImageData, old_index, new_index);
});
}
From the clarification in the comments, I think I understand your problem. This is how I’d go about solving it:
function sortArray(array, sortProp, sortValues, sortIndex) {
const elemsBySortVal = array.reduce((obj, elem, idx) => {
obj[elem[sortProp]] = idx;
return obj;
}, {});
let sortedKeys = sortValues.map(val => elemsBySortVal[val]);
let sortedItems = sortedKeys.map(key => array[key]);
let remainingItems = array.filter((_, idx) => !sortedKeys.includes(idx));
return [
...remainingItems.slice(0, sortIndex),
...sortedItems,
...remainingItems.slice(sortIndex),
];
}
console.log(sortArray(
[ { id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 } ],
'id',
[ 1, 2, 4 ],
1,
));
This solution works in three phases:
Phase 1:
This is the most complex part. Here, we take your input array and create a map of sort values against input indices. This is best shown by example:
Input (array):
[ { id: 1 }, { id: 3 }, { id: 5 }, { id: 2 }, { id: 4 } ]
Output (elemsBySortVal):
{
1: 0,
3: 1,
5: 2,
2: 3,
4: 4,
}
Phase 2:
Now we use that map to fetch the indices in the input array of the values passed as the sort values:
Input (sortValues):
[ 1, 2, 4 ]
Output (sortedKeys):
[ 0, 3, 4 ]
This is then mapped to the elements from the input array:
Input (sortedKeys):
[ 0, 3, 4 ]
Output (sortedItems):
[ { id: 1 }, { id: 2 }, { id: 4 } ]
And finally, the remaining items are selected from the input array by using sortedKeys to exclude the already sorted ones:
remainingItems:
[ { id: 3 }, { id: 5 } ]
Note that all operations in phase 2 maintain the order of these arrays, even when elements are removed.
Phase 3:
Now we assemble the output array in 3 parts:
Elements before the sorted section
Elements in the sorted section
Elements after the sorted section
The before and after parts are sliced from remainingItems using sortIndex as a cut point, and the sorted section is simply sortedItems from the previous phase.
Remove the elements that match the IDs from the array, and put them in a new array. Then splice that new array back into the original array.
function move_array_elements(array, ids, new_index) {
let extracted = [];
ids.forEach(id => {
let index = array.findIndex(el => el.id == id);
if (index != -1) {
extracted.push(array[index]);
array.splice(index, 1);
}
});
array.splice(new_index, 0, ...extracted);
return array;
}
const orig_array = [ { id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 } ];
console.log(move_array_elements(orig_array, [1, 2, 4], 1));
You can use filter and splice
Please check the comments
let arr=[{id:1}, {id:2}, {id:3}, {id:4}, {id:5}];
let selected=[1,2,4];
let move_2_index=1;
//filter out selected
const selectedonly = arr.filter(a=>selected.includes(a.id));
//filter out not selected
const notselected = arr.filter(a=>!selected.includes(a.id));
//splice to insert selected on not selectd
notselected.splice(move_2_index, 0, selectedonly);
//flattern the array
final_array=notselected.flat();
console.log(final_array);
Using null as a Placeholder
Details in demo.
Demo
let arr=[{id:1},{id:2},{id:3},{id:4},{id:5}];
Array.prototype.move = function(to, moveFrom) {
// Make a copy of the original array
let that = [...this];
// Declare an empty array
let moveTo = [];
/*
- Compare current index with the numbers in move array (offset)
- Replace each match with a null as a placeholder so that the
indexes of the array is still accurate.
- Place each match into the empty array from previous step.
*/
that.forEach(function(obj, idx, arr) {
if (moveFrom.indexOf(idx +1) !== -1) {
moveTo.push(arr.splice(idx, 1, null));
}
});
// Remove all nulls
that = that.filter(function(obj) {
return obj !== null;
});
/*
Insert the new moving array into the copied array at the index
indicated in the first parameter.
*/
that.splice(to, 0, moveTo.flat())
return that.flat();
};
console.log(JSON.stringify(arr.move(1, [1, 3, 4])));
console.log(JSON.stringify(arr.move(0, [1, 2, 5])));
console.log(JSON.stringify(arr.move(3, [1, 2, 3])));
console.log(JSON.stringify(arr.move(1, [2, 5])));
console.log(JSON.stringify(arr.move(2, [1, 4])));

Flatten 3D array containing objects to 2D removing duplicated objects by it's parameter

I have a 3D array with objects inside:
[
[{ id: 1 }, { id: 2 }],
[{ id: 3 }],
[{ id: 3 }, { id: 4 }]
]
How to flatten it including removing duplicated id parameter?
[{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }]
I think underscore would be helpful with that
var a = [
[{ id: 1 }, { id: 2 }],
[{ id: 3 }],
[{ id: 3 }, { id: 4 }]
];
var flattened = _(a).flatten().uniq('id').value();
Of course you have to include lodash to your webpage.
You can use Underscore flatten and unique to accomplish this. However, whenever you are using multiple underscore operations, it is a good time to consider using the underscore chainging with chain and value:
var data = [
[{ id: 1 }, { id: 2 }],
[{ id: 3 }],
[{ id: 3 }, { id: 4 }]
];
var result = _.chain(data)
.flatten()
.uniq(function(o) {
return o.id;
})
.value();
console.log('result', result);
JSFiddle: http://jsfiddle.net/0udLde0s/3/
Even shorter with current Underscore.js
If you use a recent version of Underscore.js (I tried current which is 1.8.3 right now), you can use .uniq('id') so it makes it even shorter:
var result = _.chain(data)
.flatten()
.uniq('id')
.value();
You can use _.flatten, and _.uniq, like so
var data = [
[{ id: 1 }, { id: 2 }],
[{ id: 3 }],
[{ id: 3 }, { id: 4 }]
];
var result = _.uniq(_.flatten(data), function (el) {
return el.id;
});
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
You don't need any library for this, it's quite simple:
function flatten(arr)
{
var map = {};
var flatArray = [];
function pushToMap(o) {
if(map[o.id])
return;
map[o.id] = true;
flatArray.push(o);
}
function arrRecurse(a) {
if(a.length === undefined)
pushToMap(a);
else {
a.forEach(function(i) {
arrRecurse(i);
});
}
}
arrRecurse(arr);
return flatArray;
}
var _3dArray = [
[{ id: 1 }, { id: 2 }],
[{ id: 3 }],
[{ id: 3 }, { id: 4 }]
];
alert(JSON.stringify(flatten(_3dArray)));
No library, only native JS :
var ar = [
[{ id: 1 }, { id: 2 }],
[{ id: 3 }],
[{ id: 3 }, { id: 4 }]
];
//start
var output = [];
for (var x = 0, al = {}; x < ar.length; x++)
for (var y = 0, t = ar[x][y]; y < ar[x].length; y++, t = ar[x][y])
al[t.id] = (!al[t.id]) ? output.push(t) : 1;
//end
document.body.innerHTML += JSON.stringify(output);

How do I remove duplicate objects in an array by field in Javascript?

I have an array of objects:
[{
id: 1,
name: 'kitten'
}, {
id: 2,
name: 'kitten'
},{
id: 3,
name: 'cat
}]
How do I remove the second kitten? Sorting into an array of names doesn't work, because I can't know if I am deleting id 1 or or id 2. So, I'm not quite sure how to do this.
You can use an additional hash-map to store names found so far. When you process a next object if it's name is already in the hash-map it is a duplicate and you can remove it.
var duplicates = {};
for (var i = 0; i < array.length) {
var obj = array[i];
if (! duplicates[obj.name]) {
duplicates[obj.name] = 1;
i++;
} else {
array.splice(i, 1);
}
}
there is the lodash library.
You could use the uniq
var array = [{
id: 1,
name: 'kitten'
}, {
id: 2,
name: 'kitten'
},{
id: 3,
name: 'cat'
}];
var asd = _.uniq(array,'name');
console.log(asd);
Gives an output:
[ { id: 1, name: 'kitten' }, { id: 3, name: 'cat' } ]
as it written in the documentation "only the first occurence of each element is kept".
var arr =[{
id: 1,
name: 'kitten'
}, {
id: 2,
name: 'kitten'
},{
id: 3,
name: 'cat'
}];
var results = [];
var idsSeen = {}, idSeenValue = {};
for (var i = 0, len = arr.length, name; i < len; ++i) {
name = arr[i].name;
if (idsSeen[name] !== idSeenValue) {
results.push(arr[i]);
idsSeen[name] = idSeenValue;
}
}
console.log(results);

Categories

Resources