Related
I'd like to get output from input.
It should be pushed element like chasing tails and it couldn't be duplicated.
The rule is very simple.
Firstly, if there is the same element with input[0][1] in input[i][0],
you can put the first value of the element in a result.
The element is [1,5] so result becomes [[0,1]].
Now you can just repeat. The first element of [5,29] is the same element with [1,5][1] and result becomes [[0,1,5]]
I've been really frustrating for few weeks to solve this issue. Please help. Any comments would be appreciated.
input =
[
[ 0, 1 ], [ 0, 2 ], [ 0, 3 ],
[ 0, 4 ], [ 1, 5 ], [ 2, 6 ],
[ 3, 7 ], [ 4, 10 ], [ 4, 11 ],
[ 4, 12 ], [ 4, 13 ], [ 5, 29 ],
[ 6, 29 ], [ 7, 8 ], [ 8, 29 ],
[ 9, 29 ], [ 12, 18 ], [ 13, 19 ],
[ 17, 29 ], [ 18, 29 ], [ 19, 29 ],
[ 21, 29 ], [ 24, 29 ], [ 26, 29 ],
[ 28, 29 ]
]
output = [
[0,1,5,29],[0,2,6,29],[0,3,7,8,29],[0,4,10],[0,4,11],
[0,4,12,18,29],[0,4,13,19,29]
]
You can write solver as a generator with an input of t and a starting query of q. Other answers suggest reshaping your input or other functional techniques that iterate over the input multiple times. This simple imperative-style technique uses only one pass (per recursive call). The use of a generator allows you to find all solutions, but can be paused/stopped at any time, for any other reason -
function* solver (t, q) {
let atLeastOnce = false
for (const [parent, child] of t) {
if (parent == q) {
atLeastOnce = true
for (const sln of solver(t, child))
yield [parent, ...sln]
}
}
if (!atLeastOnce) {
yield [q]
}
}
const input =
[[0,1],[0,2],[0,3],[0,4],[1,5],[2,6],[3,7],[4,10],[4,11],[4,12],[4,13],[5,29],[6,29],[7,8],[8,29],[9,29],[12,18],[13,19],[17,29],[18,29],[19,29],[21,29],[24,29],[26,29],[28,29]]
for (const sln of solver(input, 0))
console.log(JSON.stringify(sln))
[0,1,5,29]
[0,2,6,29]
[0,3,7,8,29]
[0,4,10]
[0,4,11]
[0,4,12,18,29]
[0,4,13,19,29]
Generators are iterable and so you can collect all results in an array using Array.from -
const all = Array.from(solver(input, 0))
console.log(all)
[
[0,1,5,29],
[0,2,6,29],
[0,3,7,8,29],
[0,4,10],
[0,4,11],
[0,4,12,18,29],
[0,4,13,19,29],
]
Combine generators with an optimized input type for even better results.
Based on how I understood your output, what you want is kind of like a domino-chain. You seed the output with arrays start contain 0 in the first element, e.g. [0,1] and [0,2]..., and then you simply find the next array to join to the chain based on the last item in the array vs the first item in the next entry.
This process is recursive in nature, since you start with the seeds and have no idea how far/deep you will need to join. To break this down, we can do this:
Separate your input into seeds and non-seeds (nodes).
Start with seeds
Iterate through the seeds and invoke a function, that will recursively add nodes to your seed. The trick is that you want to duplicate the seed array, when multiple paths can be found
The function should invoke itself recursively, and here's a quick logic:
/**
* #method
* #params arr The seed array (which will grow in length)
* #params nodes The collection of non-seeds
*/
function chain(arr, nodes) {
// We first find whatever nodes left that we can domino-chain to the current seed
const nextNodes = nodes.filter(node => node[0] === arr[arr.length - 1]);
// If nothing is to be found, return the array
if (!nextNodes.length) return [arr];
// Otherwise we go through all the next nodes and create a copy of the current seed
// And then append the next node to it
return nextNodes.map(nextNode => {
return chain([...arr, nextNode[1]], nodes);
}).flat();
}
See proof-of-concept below:
const input = [
[0, 1],
[0, 2],
[0, 3],
[0, 4],
[1, 5],
[2, 6],
[3, 7],
[4, 10],
[4, 11],
[4, 12],
[4, 13],
[5, 29],
[6, 29],
[7, 8],
[8, 29],
[9, 29],
[12, 18],
[13, 19],
[17, 29],
[18, 29],
[19, 29],
[21, 29],
[24, 29],
[26, 29],
[28, 29]
];
const seeds = input.filter(entry => entry[0] === 0);
const nodes = input.filter(entry => entry[0] !== 0);
function chain(arr, nodes) {
const nextNodes = nodes.filter(node => node[0] === arr[arr.length - 1]);
if (!nextNodes.length) return [arr];
return nextNodes.map(nextNode => {
return chain([...arr, nextNode[1]], nodes);
}).flat();
}
const output = seeds.map(seed => {
return chain(seed, nodes);
}).flat();
console.log(output);
I would split the problem in 2:
Create a tree-like structure from your list. Each node in your tree contains a reference to all possible next nodes.
Write a recursive function that walks the tree to find all possible paths
Here's an implementation of that approach:
const input =
[
[ 0, 1 ], [ 0, 2 ], [ 0, 3 ],
[ 0, 4 ], [ 1, 5 ], [ 2, 6 ],
[ 3, 7 ], [ 4, 10 ], [ 4, 11 ],
[ 4, 12 ], [ 4, 13 ], [ 5, 29 ],
[ 6, 29 ], [ 7, 8 ], [ 8, 29 ],
[ 9, 29 ], [ 12, 18 ], [ 13, 19 ],
[ 17, 29 ], [ 18, 29 ], [ 19, 29 ],
[ 21, 29 ], [ 24, 29 ], [ 26, 29 ],
[ 28, 29 ]
];
const nodes = {};
// Store all paths between nodes
for (const [ start, end ] of input) {
nodes[end] = nodes[end] || { id: end, children: [] };
nodes[start] = nodes[start] || { id: start, children: [] };
nodes[start].children.push(nodes[end]);
}
// Find all paths from 0
const getPaths = ({ children, id }) => children.length === 0
? [[ id ]]
: children.flatMap(
n => getPaths(n).map(p => [ id, ...p ])
)
console.log(getPaths(nodes[0]));
You can do that with a single loop too, and follow your rules:
if edge starts (start) with 0, just push it into the result
otherwise try finding an already existing path in result which contains start
if the existing path ends with the start, just append edge's ending location
if the existing path contains the start somewhere inside, put a copy of path up to that position into result, and append edge's ending location.
let input = [[0, 1], [0, 2], [0, 3], [0, 4], [1, 5], [2, 6], [3, 7], [4, 10], [4, 11],
[4, 12], [4, 13], [5, 29], [6, 29], [7, 8], [8, 29], [9, 29], [12, 18],
[13, 19], [17, 29], [18, 29], [19, 29], [21, 29], [24, 29], [26, 29],
[28, 29]];
let result = [];
for (let edge of input) {
let start = edge[0];
if (start === 0) { // <--- 1. (0 => push for sure)
result.push(edge);
} else {
for (let path of result) {
let position = path.indexOf(start);
if (position > 0) { // <--- 2. (can't be 0, but >= would work too)
if (position !== path.length - 1) { // <--- 4. (need a copy)
path = path.slice(0, position + 1);
result.push(path);
}
path.push(edge[1]); // <--- 3. 4. (whatever path is)
break;
}
}
}
}
console.log(JSON.stringify(result));
(JSON.stringify() is just used for formatting)
This is a similar question as this one: Complete missing sequence in array with zeros in javascript
However, I can't seem to go around this problem. This is my array:
const array = [
[5, 'a', 2.3],
[6, 'a', 1.7],
[7, 'a', 5.4],
[8, 'a', 2.8],
[9, 'a', 8.5],
[10, 'a', 9.2],
[2, 'b', 1.6],
[5, 'b', 5.7],
[6, 'b', 8.9],
[7, 'b', 3.5],
[8, 'b', 6.1],
[9, 'b', 1.8],
[10, 'b', 7.4],
];
console.log(array);
First element: this is my reference value, it ranges from 1 to 10.
Second element: this is a category value.
Third element: this is a value that belongs to the second element, which happened at a timestamp that belongs to the first element.
My issue: I need to make sure that all the unique categories in the second element of the array (e.g., a and b) have the following sequence in the first element: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]. If they do not have one of these numbers, then I need to create it, and then assign null to the third element.
Therefore, this is my expected output:
[
[1, 'a', null],
[2, 'a', null],
[3, 'a', null],
[4, 'a', null],
[5, 'a', 2.3],
[6, 'a', 1.7],
[7, 'a', 5.4],
[8, 'a', 2.8],
[9, 'a', 8.5],
[10, 'a', 9.2],
[1, 'b', null],
[2, 'b', 1.6],
[3, 'b', null],
[4, 'b', null],
[5, 'b', 5.7],
[6, 'b', 8.9],
[7, 'b', 3.5],
[8, 'b', 6.1],
[9, 'b', 1.8],
[10, 'b', 7.4],
];
Any ideas?
You can create a range from 1 to 10, loop over it and when you can't find an association in your array, create a new element and push it.
Do that for every category and you're good.
const range = new Array(0).fill().map((_, i) => i + 1); // from 1 to 10
const categories = array
.map(x => x[1]) // get categories
.filter((val, i, self) => self.indexOf(val) === i) // uniq
categories.forEach(categ => {
range.forEach(n => {
const alreadyInArray = array.some(x => x[0] === n && x[1] === categ);
if (!alreadyInArray) {
const newEntry = [n, categ, null];
array.push(newEntry);
}
});
})
You can of course replace the forEach with classic for loops
A functional solution, first get the categories, then for each category fill the corresponding array.
const array = [
[5, 'a', 2.3],
[6, 'a', 1.7],
[7, 'a', 5.4],
[8, 'a', 2.8],
[9, 'a', 8.5],
[10, 'a', 9.2],
[2, 'b', 1.6],
[5, 'b', 5.7],
[6, 'b', 8.9],
[7, 'b', 3.5],
[8, 'b', 6.1],
[9, 'b', 1.8],
[10, 'b', 7.4],
];
const getMissingIndicesFromCategory = (tuples) => {
const indices = tuples.map(tuple => tuple[0])
const fullIndices = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
return fullIndices.filter(index => !indices.includes(index));
}
const createMissingTuples = (missingIndices, category) => {
return missingIndices.map(index => [index, category, null])
}
const completeCategoryTuples = (array, category) => {
const categoryTuples = array.filter(tuple => tuple[1] === category)
const missingIndices = getMissingIndicesFromCategory(categoryTuples)
const missingTuples = createMissingTuples(missingIndices, category)
return [...categoryTuples, ...missingTuples].sort((tuple1, tuple2) => tuple1[0] > tuple2[0] ? 1 : -1)
}
const getAllUniqueCategories = (array) => Array.from(new Set(array.map(tuple => tuple[1])))
const fillArray = (array) => {
const categories = getAllUniqueCategories(array)
return categories.flatMap(category => completeCategoryTuples(array, category))
}
const fullArray = fillArray(array)
console.log(fullArray)
I've looked at a number of other posts and can't resolve my case.
I am working on a music/chord playing program and I want to filter out all chords (aka objects) that include one or more notes (numbers in my program) that are out of key.
I have an array named chordLibrary filled with objects (e.g., {notesInChord: [5, 8], chordName: 'Major'}). I have another array with numbers (outOfKeyNotes = [11, 12];). I want to .filter and return only the objects in chordLibrary that do not include the numbers in outOfKeyNotes in the element notesInChord.
For example:
// THIS is the original array:
const chordLibrary = [
{ notesInChord: [5, 8], chordName: 'Major' },
{ notesInChord: [5, 8, 11], chordName: 'Dominant 7th' },
{ notesInChord: [4, 8, 12], chordName: 'Minor Major 7th' }
];
// THIS is what I hope to end up with after filtering for the array [11,12]:
let chordsInKey = [
{ notesInChord: [5, 8], chordName: 'Major' },
];
Here is my program which is currently not working. it simply returns the whole original array.
const chordLibrary = [
{ notesInChord: [5, 8], chordName: 'Major' },
{ notesInChord: [5, 8, 11], chordName: 'Dominant 7th' },
{ notesInChord: [4, 8, 12], chordName: 'Minor Major 7th' }
];
let outOfKeyNotes = [11, 12];
console.log(chordLibrary.length);
let chordsInKey = chordLibrary.filter(function(item) {
return !outOfKeyNotes.includes(item.notesInChord);
});
console.log(chordsInKey);
console.log(chordsInKey.length);
If I change the program so that chordLibrary is simply an array of number values instead of an array of objects, it works fine. It just doesn't suit my needs. Here is a working example of that:
let chordLibrary = [1,2,3,11,12];
let outOfKeyNotes = [11, 12];
console.log(chordLibrary.length);
let chordsInKey = chordLibrary.filter(function(item) {
return !outOfKeyNotes.includes(item);
});
console.log(chordsInKey);
console.log(chordsInKey.length);
What am I missing? Thanks
You're trying to use includes to see if an array of values includes another array of values.
Instead, you can use Array.every in your filtering mechanism, which makes sure every element in an array passes a specific test.
I would change
return !outOfKeyNotes.includes(item.notesInChord);
to
return item.notesInChord.every(note => !outOfKeyNotes.includes(note));
You can use Array#some() to check if any of the notesInChord values exist in outOfKeyNotes
const chordLibrary = [
{ notesInChord: [5, 8], chordName: 'Major' },
{ notesInChord: [5, 8, 11], chordName: 'Dominant 7th' },
{ notesInChord: [4, 8, 12], chordName: 'Minor Major 7th' }
];
let outOfKeyNotes = [11, 12];
let chordsInKey = chordLibrary.filter(function(item) {
return !item.notesInChord.some(function(v){
return outOfKeyNotes.includes(v);
});
});
console.log(chordsInKey);
I have nested array data and I would like to extract all nested arrays to be siblings of their parent. I am pretty close, but I am getting an extra empty array in the results and I cannot figure out where it is coming from or how to get rid of it.
Note: I would really like to understand why this is happening and how to get rid of it in my function, and not just a .filter(arr => arr.length) on my results list.
This is my attempt so far:
var arrs = [
[1, 2, [3, 4], 5],
[6, [7, 8, 9, [10, 11]]],
[12, 13],
[[14, 15], [16, 17]],
[[1], 4, [1, 1], 4]
];
// Desired Output
// [
// [1, 2, 5],
// [3, 4],
// [6],
// [7, 8, 9],
// [10, 11],
// [12, 13],
// [14, 15],
// [16, 17],
// [4, 4]
// [1]
// [1, 1]
// ]
function extractArrays (arr) {
return arr.reduce((res, curr) => {
if (Array.isArray(curr)) {
res = res.concat(extractArrays(curr));
}
else {
res[0].push(curr);
}
return res;
}, [[]]);
}
console.log(extractArrays(arrs));
// Results:
// [
// [], <-- Where is this coming from?
// [ 1, 2, 5 ],
// [ 3, 4 ],
// [ 6 ],
// [ 7, 8, 9 ],
// [ 10, 11 ],
// [ 12, 13 ],
// [], <-- Also here
// [ 14, 15 ],
// [ 16, 17 ],
// [ 4, 4 ],
// [ 1 ],
// [ 1, 1 ]
// ]
.as-console-wrapper {
max-height: 100% !important;
}
Element like [[14, 15], [16, 17]] will introduce a [] after recursion. This should be handled by checking length.
var arrs = [
[1, 2, [3, 4], 5],
[6, [7, 8, 9, [10, 11]]],
[12, 13],
[[14, 15], [16, 17]],
[[1], 4, [1, 1], 4]
];
function extractArrays (arr, acc=[]) {
if (arr.length == 0 ) return acc;
let pure = arr.filter(elm => !Array.isArray(elm));
if (pure.length > 0) {
acc.push(pure);
}
acc.concat(arr.filter(elm => Array.isArray(elm)).map(elm => extractArrays(elm, acc)));
return acc;
}
console.log(extractArrays(arrs));
You can try the following code
var arrs = [
[1, 2, [3, 4], 5],
[6, [7, 8, 9, [10, 11]]],
[12, 13],
[
[14, 15],
[16, 17]
], // <-- added additional test case
[
[1], 4, [1, 1], 4
]
];
function extractArrays(arr) {
return arr.reduce((res, curr, i) => {
if (Array.isArray(curr)) {
res = res.concat(extractArrays(curr));
} else {
let index = 0;
for (let j = 0; j <= i; j++) {
if (!Array.isArray(arr[j])) {
res[index] ? res[index].push(curr) : res.push([curr]);
break;
} else {
index++;
}
}
}
return res;
}, []); // <-- no initial empty array inside here
}
console.log(extractArrays(arrs));
I just wanted to share my approach to this problem, I enjoyed trying to solve it, in my case I also passed an array to the extractArrays method, in order to make easier to capture and filter every array inside the arrs param.
let result = [];
extractArrays(arrs, result);
console.log(result);
function extractArrays(arr, result) {
let newResult = arr.reduce((acc, curr) => {
if (Array.isArray(curr)) {
extractArrays(curr, result);
} else {
acc.push(curr);
}
return acc;
}, []);
newResult.length && result.push(newResult);
}
You can check it when you return from function. stackblitz
function extractArray(arr) {
const res = arr.reduce((res, curr) => {
if(!Array.isArray(curr)){
return [[...res[0], curr], ...res.slice(1)]
}
return [...res, ...extractArray(curr)]
}, [[]]);
return res[0].length ? res : res.slice(1);
}
EDIT: More performant function (check stackblitz link)
function extractFaster(arr) {
let res = [0];
function recExtract(arr) {
let hasNonArrayElm = false;
let index = res.length -1;
arr.forEach(curr => {
if (!Array.isArray(curr)) {
hasNonArrayElm ? res[index].push(curr) : res.splice(index, 0, [curr]);
hasNonArrayElm = true;
return;
}
recExtract(curr);
});
}
recExtract(arr);
res.splice(-1, 1)
return res;
}
EDIT: The answer below the line is a great way to flatten arrays, but I suggested it because I misunderstood this question. I will leave it in case it benefits someone to know, but in order to keep an accurate record, I'll also update my answer to address the problem posed in the question.
The accepted answer seems sufficient enough, but I'll try my hand at it. I would use Array.reduce to cover everything with one swoop, and inside use Array.filter to separate the normal items from the array items, then use the spread ... operator on the nested arrays so everything gets pushed to the same level, after recursively calling the same extract function on all nested arrays. Honestly, the explanation might be harder to understand than the code, have a look:
const data = [
[1, 2, [3, 4], 5],
[6, [7, 8, 9, [10, 11]]],
[12, 13],
[[14, 15], [16, 17]],
[[1], 4, [1, 1], 4]
]
const extractChildArrays = arrs => arrs.reduce((acc, cur) => {
const nestedArrs = cur.filter(a => Array.isArray(a))
const normalItems = cur.filter(a => !Array.isArray(a))
acc.push(normalItems, ...extractChildArrays(nestedArrs))
return acc
}, [])
console.log(extractChildArrays(data))
UPDATE: Array.flat() is now accepted as part of the spec and it's supported by all modern browsers except Edge.
In ES6 there is actually an experimental array method called flat(). As of the writing of this answer, it's only compatible with Chrome, but a polyfill might be worth looking into, because it's SO EASY!
The first parameter of flat() is depth, so with the help of another answer, you can easily figure that out dynamically.
const data = arrs = [
[1, 2, [3, 4], 5],
[6, [7, 8, 9, [10, 11]]],
[12, 13],
[[14, 15], [16, 17]],
[[1], 4, [1, 1], 4]
]
const flattenArray = arr => {
const getDepth = a => Array.isArray(a) ?
1 + Math.max(...a.map(getDepth)) : 0
return arr.flat(getDepth(arr))
}
console.log(flattenArray(data))
I have an array in the form:
var a1 = [
['AA', 1],
['AA', 2],
['AA', 3],
['BB', 7],
['BB', 8],
['BB', 9]
];
I want to transform it into:
output = [
['AA':1,2,3],
['BB':7,8,9]
]
I need to transform it this way so I can put my JSON formatted data that comes from SQL into a highcharts graph that seems to need the array series as follows
https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/demo/streamgraph/
Try something like this
var a1 = [
['AA', 1],
['AA', 2],
['AA', 3],
['BB', 7],
['BB', 8],
['BB', 9]
];
function generateObj(array) {
const obj = {}
array.forEach(entry => {
obj[entry[0]] = obj[entry[0]] || []
obj[entry[0]].push(entry[1])
})
return obj
}
console.log(generateObj(a1))