Let's say I have an array of names such as :
let members = ["John", "Marie", "Ivy", "Daniel"];
Each person has to deliver a unique item to another. Here is an example of 1 combination set :
[
{"John":"Marie"},
{"Marie":"Ivy"},
{"Ivy":"Daniel"},
{"Daniel":"John"},
]
The reciever can only be named once. I'd like to retrieve an array of all combinations possible. What would be the easiest way to get such an array ?
EDIT : Alot of downvotes. Justified I'm afraid. Here is what I worked on, seems... verbose :
//gets all permutations combination of the same array
const perm = members => members.length ? members.reduce((r, v, i) => [ ...r, ...perm([ ...members.slice(0, i), ...members.slice(i + 1) ]).map(x => [ v, ...x ])], []) : [[]]
let permutations = perm(members);
permutations.map(set => {
let valid = true;
let combination = [];
set.map((name, i) => {
let match = {};
if (name === members[i]) {
valid = false;
return
}
match[members[i]] = name;
combination.push(match);
})
if (valid) possibilities.push(combination);
})
try this
let rslt = []
let members = ["John", "Marie", "Ivy", "Daniel"];
members.forEach((member, i) => {
let nextIndex = i + 1;
// if it's the last member
// then pass it to the first one
if (nextIndex === members.length) {
nextIndex = 0;
}
let obj = new Object();
obj[member] = members[nextIndex];
rslt.push(obj);
})
console.log(rslt);
Related
I'm wondering, if there is a way to filter an array or stream and apply a function A to all matches and a function B to all non-matches in JavaScript. Here is some example code that explains it a bit more:
// initial data
var names = ['Matthias', 'Maria', 'Bob', 'Anton'];
var namesWithM;
var namesWithoutM;
// gets only names starting with M, but not the others at the same time
namesWithM = names.filter(name => name.startsWith('M'))
// conditional lambda version
namesWithM = [];
namesWithoutM = [];
names.forEach(name => name.startsWith('M') ? namesWithM.push(name) : namesWithoutM.push(name));
// conditional classical version
namesWithM = [];
namesWithoutM = [];
names.forEach(function(name) {
if (name.startsWith('M'))
namesWithM.push(name)
else
namesWithoutM.push(name);
});
The very first version handles just the matches but uses filter and not forEach. Is there any way to use filter and apply a function for matches and non-matches at once? Something like this pseudo code:
names.filter(name.startsWith('M')).apply(namesWithM::push).or(namesWithoutM::push);
filter returns an array. So you can use this array to fill with name which either starts with M or not.
In the below example the filter is filling the array with name starts with M. In filter callback the name not starting with M are filled in another array
// initial data
var names = ['Matthias', 'Maria', 'Bob', 'Anton'];
var namesWithM;
var namesWithoutM = [];
namesWithM = names.filter((name) => {
if (!name.startsWith('M')) {
namesWithoutM.push(name)
}
return name.startsWith('M');
});
console.log(namesWithM, namesWithoutM);
I would use reduce to group data into 2 mentioned cases. I don't see any reason to use filter here
let names = ['Matthias', 'Maria', 'Bob', 'Anton'];
let [namesWithM, namesWithoutM] = names.reduce((acc, name) => {
if (name.startsWith('M')) {
acc[0] = [...(acc[0] || []), name]
return acc;
}
acc[1] = [...(acc[1] || []), name]
return acc;
}, [])
// simpler version
console.log(namesWithM, namesWithoutM);
let [namesWithM1, namesWithoutM1] = names.reduce((acc, name) => {
const index = Number(!name.startsWith('M'));
acc[index] = [...(acc[index] || []), name];
return acc;
}, [])
console.log(namesWithM1, namesWithoutM1);
const names = ['Matthias', 'Maria', 'Bob', 'Anton'];
function A(item){
console.log('filtered');
console.log(item);
}
function B(item){
console.log('not-ffiltered');
console.log(item);
}
const filteredNames = names.filter(name => {
const isValid = name.startsWith('M')
if(isValid)
A(name)
else
B(name)
return isValid;
})
I want to filter one value from my first array and create a second array with the filtered value.
So far I have that but it does not seem very efficient.
const blacklist = bookingsList.filter(booking => booking.id === id);
const newBookingList = bookingsList.filter(booking => booking.id !== id);
Is there a better way to do this?
I think something like this would be good on a large array or if testing the condition is expensive because you would only loop through the array once
const array1 = [];
const array2 = [];
for (var i = 0; i < input.length; i++) {
const value = input[i];
( testCondition(value) ? array1 : array2 ).push(value);
}
You can do it with a single iteration by using forLoop like
const blacklist = [];
const newBookingList = [];
bookingsList.forEach(booking => {
if(booking.id === id) {
blacklist.push(booking)
}
else {
newBookingList.push(booking)
}
}
You can use forEach() and ternary operator:
const bookingsList = [{id:'black'},{id:'new'}];
const blacklist = [], newBookingList = [], id='black';
bookingsList.forEach(booking => booking.id === id? blacklist.push(booking.id) : newBookingList.push(booking.id));
console.log(blacklist);
console.log(newBookingList);
let blacklist = []
let newBookingList = []
const ID = 10;
let bookingsList=[{id:10}, {id:20}]
bookingsList.forEach(booking => booking.id === ID ? blacklist.push(booking) : newBookingList.push(booking))
console.log(newBookingList)
console.log(blacklist)
I think you can use forEach for that:
const newBookingList = [];
const blacklist = [];
bookingsList.forEach(function(booking) {
if(booking.id === id){
blacklist.push(booking)
}
if(booking.id !== id){
newBookingList.push(booking)
}
})
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
You can use splice method to retrive and remove filtered value.
var bookingList = [{id : 1, name : "A"}, {id: 2, name: "B"}];
var sBookingList = bookingList.splice(bookingList.map(function(b){return b.name}).indexOf("A"), 1);
console.log(sBookingList);
console.log(bookingList);
Trying to find an algorithm that will do the following:
let input = [ 'kooty', 'dlnnoo', 'emor', 'dlnnoo', 'kooty', 'aiprs' ]
function combine(input){
// you should return
[ ['kooty', 'kooty'], ['dlnnoo','dlnnoo'], ['emor'], ['aiprs'] ]
}
I got the answer by using Lodash but i was wondering if there was a way without
function combine(input){
let sortedCity = [];
let finalArr = [];
for(let city in input){
sortedCity.push(input[city].toLowerCase().split('').sort().join(''));
}
let a = lodash.groupBy(sortedCity)
return Object.values(a)
}
combine(input)
let input = [ 'kooty', 'dlnnoo', 'emor', 'dlnnoo', 'kooty', 'aiprs' ]
function combine (input) {
let sorted = input.map(cur => cur.toLowerCase().split('').sort().join(''))
let cache = {} // cache value to index of result array
return sorted.reduce((sum, cur) => {
let index = cache[cur]
if (index !== undefined) {
sum[index].push(cur)
} else {
sum.push([cur])
cache[cur] = sum.length - 1
}
return sum
}, [])
}
combine(input)
I'm trying to filter an Object by an array of arrays, getting back an array of objects.
Like this:
let obj =
{
"a.1":1,
"a.2":2,
"b.1":3,
"b.2":4,
"c.1":5,
"c.2":6
}
let array =
[
["a.1","b.1"],
["a"],
["b","c.1"]
]
let expectedResult =
[
{
"a.1":1,
"b.1":3,
},
{
"a.1":1,
"a.2":2,
},
{
"b.1":3,
"b.2":4,
"c.1":5
},
]
// this is what I came up with
const filterObjectByArray = (obj, arr) =>
Object.keys(obj)
.filter(ch => {
for (var index = 0; index < arr.length; index++)
if (ch.startsWith(arr[index]))
return true;
})
.reduce((ret, key) =>{
ret[key] = obj[key]
return ret
},{})
let result = array.map(arr => filterObjectByArray(obj, arr))
//kind of deepEqual
console.log(JSON.stringify(expectedResult) == JSON.stringify(result))
Is there a easier or more convenient way to do that? I need to do this operation quite often and my object will be up to couple hundreds entries big, so I see a potential bottleneck here.
I would create a one type mapping of the "base" (the letter) to the "real" keys, and then use it to translate the letter to the real keys when create the object.
const obj = {
"a.1": 1,
"a.2": 2,
"b.1": 3,
"b.2": 4,
"c.1": 5,
"c.2": 6
};
const array = [
["a.1", "b.1"],
["a"],
["b", "c.1"]
];
const getBaseKey = (key) => key.match(/^[a-z]+/)[0]; // get the base of the key - the letter. If it's only one letter, you can use key[0]
/** create a one time map of keys by their base **/
const oobjKeysMap = Object.keys(obj).reduce((map, key) => {
const baseKey = getBaseKey(key);
const curr = map.get(baseKey) || [];
curr.push(key);
return map.set(baseKey, curr);
}, new Map());
const result = array.map((sub) => // iterate the array
[].concat(...sub.map((k) => k in obj ? k : oobjKeysMap.get(getBaseKey(k)))) // create the least of "real" keys
.reduce((ret, key) => { // create the object
ret[key] = obj[key];
return ret;
}, {})
);
console.log(result);
I have a list in variable like:
var name_list = some_list
console.log(name_list)
Array[3]
0: Object
name: "Johny"
1: Object
name: "Monty"
2: Object3:
name: "Johny"
I want to get the list with non repetitive list.
How can I do this ?
Update
I tried with this..
var unique_name = [ ...new Set(name_list.map(name => {
return name.name
}))]
It works fine but I want the object that are filtered unique according to name.
Any idea ??
Another approach I don't see in here would be to use a Map
var name_list = [{name: "Johny"}, {name: "Monty"}, {name: "Johny"}];
// Make a mapping of name to object, then pullout objects.
var name_map = new Map(name_list.map(o => [o.name, o]));
var unique_names = [...name_map.values()];
Note, this will take the last object for each name instead of the first, but you could always do name_list.slice().reverse().map( instead of you need specifically the first object found.
reduce over the array keeping a lookup of previous entries to check against.
const arr=[{name:"Johny"},{name:"Monty"},{name:"Johny"}];
function dedupeByKey(arr, key) {
const tmp = {};
return arr.reduce((p, c) => {
const k = c[key];
if (tmp[k]) return p;
tmp[k] = true;
return p.concat(c);
}, []);
}
console.log(dedupeByKey(arr, 'name'));
Or you can filter using a similar approach:
const arr=[{name:"Johny"},{name:"Monty"},{name:"Johny"}];
function dedupeByKey(arr, key) {
const temp = arr.map(el => el[key]);
return arr.filter((el, i) =>
temp.indexOf(el[key]) === i
);
}
console.log(dedupeByKey(arr, 'name'));
Filter to keep only those elements which are the first occurrence of the name (in other words, whose index is the same as the index of the first occurrence):
var name_list = [{name: "Johny"}, {name: "Monty"}, {name: "Johny"}];
var filtered = name_list . filter(
(elt, i, a) => i === a.findIndex(
elt2 => elt.name === elt2.name
)
);
document.getElementById('result').textContent = JSON.stringify(filtered);
<pre id='result'></pre>
This might not be the fastest approach, but it could be the simplest.
You can use this little distinctByProp( theArray, propertyName) function.
I hope it helps
distinctByProp = (theArray, prop) => {
let tempArray = [];
let isFound = obj => tempArray.find( n => n[prop] === obj[prop]);
theArray.forEach( current => !isFound(current) && tempArray.push(current));
return tempArray;
}
Usage is like:
let names_list = [{name: "Johny"}, {name: "Johnyh"}, {name: "Max"}, {name: "Monty"}, {name: "Johnyh"}, {name: "Max"}];
let distinct = distinctByProp(names_list, "name");
console.log(distinct);
I hope it helps
You could use Array#filter().
var name_list = [{ name: "Johny" }, { name: "Monty" }, { name: "Johny" }],
filtered = name_list.filter(a => {
this.my = this.my || Object.create(null);
if (!this.my[a.name]) {
this.my[a.name] = true;
return true;
}
});
document.write('<pre>' + JSON.stringify(filtered, 0, 4) + '</pre>');
This is my resumed form of this type of unique. Not only for one field but for all the root fields of the object.
const unique = l => l.filter(
(e1, i, a) => i === a.findIndex(
e2 => Object.keys(e1)
.map(x => e1[x] === e2[x])
.reduce((x,y) => x && y)
)
)