Let's say I have an array [0, null, null, 3, null, null, null, 11].
I want to fill null values with numbers based on previous and next known number (and index?), so I get [0, 1, 2, 3, 5, 7, 9, 11]. What is the most efficient way to do so?
I'm thinking about something that could count nulls between two known numbers and then just get the size of one step. But these steps will be different between pairs
I'm working on a chart where some values may be missing so I have to fill possible values.
This is what I've tried, but I think it's extremely inefficient and messy. I would prefer to use ramda.js or some functional approach.
const data = [0, null, null, 3, null, null, null, 11]
const getStep = (arr, lastKnown = 0, counter = 1) => {
const val = arr[0];
if (val !== null) {
return (val - lastKnown) / counter
} else {
return getStep(arr.slice(1), lastKnown, ++counter)
}
}
let lastKnown = null
let currentStep = null
const filledData = data.map((x, i) => {
if (x !== null) {
lastKnown = x
currentStep = null
return x
}
if (currentStep !== null) {
lastKnown = lastKnown + currentStep
} else {
currentStep = getStep(data.slice(i), lastKnown)
}
return currentStep + lastKnown
})
console.log(filledData)
// UPDATE: I've selected THIS ANSWER as correct, but If you're interested in a solution you should definitely check all answers here. There are some very interesting ideas.
You could iterate the array and if a null value is found, a look ahead is made for the next numbers and the gaps until the number are filled by taking a linear approach.
var array = [0, null, null, 3, null, null, null, 11],
i = 0, j, delta;
while (i < array.length) {
if (array[i] !== null) {
i++;
continue;
}
j = i;
while (array[++j] === null);
delta = (array[j] - array[i - 1]) / (j - i + 1);
do {
array[i] = delta + array[i - 1];
i++;
} while (i < j)
}
console.log(array);
ES6 with a closure over the next index with a numerical value, the real last value of the predecessor and the delta for adding for the next value if not given.
var array = [0, null, null, 3, null, null, null, 11],
result = array.map(((j, last, delta) => (v, i, a) => {
if (v !== null) return last = v;
if (i < j) return last += delta;
j = i;
while (++j < a.length && a[j] === null) ;
delta = (a[j] - last) / (j - i + 1);
return last += delta;
})());
console.log(result);
One way to do this using for loops and counting:
var skips = 0;
var last;
for (var i=0; i<arr.length; i++){
var current = arr[i]
if (current !== null) {
// If there are skipped spots that need to be filled...
if (skips > 0){
// Calculate interval based on on skip count, and difference between current and last
var interval = (current-arr[last])/(skips+1);
// Fill in the missing spots in original array
for (var j=1; j<=skips; j++){
arr[last+j] = arr[last]+(interval*j)
}
}
last = i; // update last valid index
skips = 0; // reset skip count
}
// If null, just increment skip count
else {
skips++
}
}
Another approach to this is to convert your input array into a list of "segments" capturing the start value, end value and size of each segment. You can then use R.chain to build up the list with a linear step between the start and end values of each segment.
const input = [0, null, null, 3, null, null, null, 11]
// recursively convert the sparse list of numbers into a list of segments
const segmentNull = xs => {
if (xs.length === 0) {
return []
} else {
const [y, ...ys] = xs
const count = R.takeWhile(R.isNil, ys).length + 1
const next = R.dropWhile(R.isNil, ys)
return next.length > 0
? R.prepend({ start: y, end: next[0], count }, segmentNull(next))
: []
}
}
// segmentNull(input)
//=> [{"count": 3, "end": 3, "start": 0}, {"count": 4, "end": 11, "start": 3}]
// produce a list of `count` values linearly between `start` and `end` values
const linearRange = (start, end, count) =>
R.times(n => (end - start) * (n + 1) / count + start, count)
// linearRange(3, 11, 4)
//=> [5, 7, 9, 11]
// convert the list of segments into a list of linear values between segments
const buildListFromSegments = R.chain(({ start, end, count }) =>
linearRange(start, end, count))
// buildListFromSegments(segmentNull(input))
//=> [1, 2, 3, 5, 7, 9, 11]
// ^-- note the leading 0 is missing
// prepend the initial value to the result of `buildListFromSegments`
const fn = xs => R.prepend(xs[0], buildListFromSegments(segmentNull(xs)))
console.log(fn(input))
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.min.js"></script>
An O(n*m) solution where n is the count of all elements and m is the count of nulls.
Algorithm assumes there will always be valid digita at index positions 0 and length-1.
function fillInTheBlanks(a){
var s, //step
r = a.reduce(function([t,ns,r], e){ // [temp, nulls array, result accumulator]
e === null ? ns.push(e)
: t === void 0 ? t = e
: (s = (e-t)/(ns.length+1),
r.push(t,...ns.map((_,i) => t+(i+1)*s)),
ns = [],
t = e);
return [t,ns,r];
}, [void 0,[],[]]);
return r[2].concat(r[0]);
}
var arr = [0, null, null, 3, null, null, null, 11],
res = fillInTheBlanks(arr);
console.log(JSON.stringify(res));
Here is my quick solution with ramda:
const xs = [0, null, null, 3, null, null, null, 11]
const scanWithIndex = R.addIndex(R.scan)
const notNil = R.complement(R.isNil)
const mapWithIndex = R.addIndex(R.map)
const zipArrays = R.zipWith(R.concat)
// number of cons nulls for nth element
const consNulls = R.drop(1, R.scan((acc, x) => R.isNil(x) ? (acc + 1) : 0, 0, xs))
// length of ongoing null sequence for each element
const consNullsSeqLens = R.drop(1, scanWithIndex((acc, x, ind) =>{
if (x !== 0 && acc !== 0) return acc
const rest = R.drop(ind, consNulls)
return R.findIndex(R.equals(0), rest)
}, 0, consNulls))
// previous non null value for each el
const prevNonNulls = R.scan((acc, x) => R.isNil(x) ? acc : x, 0, xs)
// next non null value for each el
const nextNonNulls = mapWithIndex((x, ind) => {
const rest = R.drop(ind, xs)
return R.find(notNil, rest)
}, xs)
// function to calculate missing values based on zipped arrays
const calculateMissingValue = ([x, seqN, seqLen, next, prev]) =>
R.isNil(x) ? prev + (next - prev) / (seqLen + 1) * seqN : x
R.map(
calculateMissingValue,
// zips 5 lists together
zipArrays(
zipWith(R.append, consNullsSeqLens, R.zip(xs, consNulls)),
R.zip(nextNonNulls,prevNonNulls)
)
)
Repl link
While the answer from #bubulik42 shows that you can use Ramda in doing this, I'm not sure Ramda is going to be much of a help. (Disclaimer: I'm one of Ramda's authors.)
My first pass at this looked like this:
const intersperseNulls = pipe(
reduce(
({vals, prev, nilCount}, curr) => isNil(curr)
? {vals: vals, prev: prev, nilCount: nilCount + 1}
: (nilCount < 1)
? {vals: append(curr, vals), prev: curr, nilCount: 0}
: {
vals: append(curr, concat(vals, times(n => prev + (n + 1) * (curr - prev) / (nilCount + 1), nilCount))),
prev: curr,
nilCount: 0
},
{vals: [], prev: undefined, nilCount: 0},
),
prop('vals')
);
This uses the usually-functional reduce call, but it is a somewhat odd use of it, choosing to pass state through all the iterations rather than a simple accumulator. Note how similar it looks if I remove the Ramda infrastructure:
const steps = (b, e, c) => {
const results = []
for (let i = 0; i < c; i++) {results.push(b + (i + 1) * (e - b) / (c + 1));}
return results;
}
const intersperseNulls = array => array.reduce(
({vals, prev, nilCount}, curr) => (curr == null)
? {vals: vals, prev: prev, nilCount: nilCount + 1}
: (nilCount < 1)
? {vals: vals.concat(curr), prev: curr, nilCount: 0}
: {
vals: vals.concat(steps(prev, curr, nilCount)).concat(curr),
prev: curr,
nilCount: 0
},
{vals: [], prev: undefined, nilCount: 0},
).vals
only times was difficult to replace.
But in the end, I prefer the non-Ramda solution from #Nina Scholz. It's simpler, more easily readable, and doesn't try any trickery.
You can see these in the Ramda REPL.
To expand a little bit the question: Fill missing numeric values in an array?.
The following will fill any zero, in the most natural way, related to others numbers in the array.
To create referential scales 📈, with natural increments.
/* Array Zero Values Natural fill
Create a referential scale, as a ruler */
const naturalFill = (array) => {
let missing = [];
let keys = [];
let increment = 0;
for (let i = 0; i <= array.length; i++) {
if (array[i] !== 0) {
keys.push(i)
}
}
for (let i = 0; i < keys.length-2; i++) {
let slots = keys[i+1] - keys[i],
min = array[keys[i]],
max = array[keys[i+1]];
increment = ((max - min) / slots);
let afill = [...Array(slots + 1)].map((x, y) => +(min + increment * y).toFixed(4)).slice(0, -1);
missing = [...missing, ...afill]
}
let upfill = [...Array(keys[0] + 1)].map((x, y) => +(array[keys[0]] - increment * y).toFixed(4)).reverse().slice(0, -1);
let downfill = [...Array(keys[keys.length - 2] + 1)].map((x, y) => +(array[keys[keys.length - 2]] + increment * y).toFixed(4));
return [...upfill, ...missing, ...downfill]
}
// Example 1
console.log(
naturalFill( [0, 0, 14, 0, 107, 0, 314, 0, 400, 0, 832, 987, 0, 0] )
)
// Example 2, generate array of epoch intervals
console.log(
naturalFill( [0,0, Date.now()-60*60*24, 0,0,0,0,0, Date.now(), 0,0,0] )
)
This might be useful in many ways, like to create graph charts referentials 📊.
Or simply to measure a previously scaled object from any key step point.
We can use it to generate sequential timestamps, as the example 2.
Related
Given an array and an index, I'd like to return n values surrounding the value at index.
For instance:
Array: [0,1,2,3,4,5,6]
index: 1
values: 2
Result:
[6,0,1,2,3]
slice comes in handy but I can't get the wrapping requirement working. Is the simplest solution to concat the array and work from that? ([...array, ...array])
You could take the double array and an offset with adjustment for slicing.
const
getValues = (array, index, values) => {
let offset = index - values;
if (offset < 0) offset += array.length;
return [...array, ...array].slice(offset, offset + 2 * values + 1);
},
array = [0, 1, 2, 3, 4, 5, 6],
result = getValues(array, 1, 2);
console.log(...result);
I'm not sure this adds much to the answers here already unless it might need to wrap over a size greater than the array length:
const circle_number_for_size = (circle_size) => (number) => {
const rem = number % circle_size;
return rem + (rem < 0 ? circle_size : 0);
};
const circle_slice = (array, start, end) => {
const circle_number = circle_number_for_size(array.length);
let new_array = [];
for(let i = start; i <= end; i++)
new_array.push(array[circle_number(i)]);
return new_array;
};
const neighbours = (array, centre_index, offset) =>
circle_slice(array, centre_index - offset, centre_index + offset);
console.log( ...neighbours([0,1,2,3,4,5,6], 1, 2) );
console.log( ...neighbours([0,1,2,3,4,5,6,7,8,9], 0, 21) );
I want to write a function with for-loops that finds the index of the number 1 in an array and returns the difference to the index of the number 2 that is closest to number 1 (number 1 only appears once). For instance:
Input: [1, 0, 0, 0, 2, 2, 2]
Output: 4
Input: [2, 0, 0, 0, 2, 2, 1, 0, 0 ,2]
Output: 1
My try
function closest (array) {
let elem=array.findIndex(index=>index === 1)
let numberplus=0;
let numberminus=0;
for (let i=elem; i<array.length; i++){
if (array[elem+1] === 2)
{numberplus+=array[elem+1]-elem;}
break;
}
for (let i=elem; i>=0; i--) {
if (array[elem-1] ===2)
{numberminus+=array[elem-1]-elem;}
break;
}
if (numberplus < numberminus) {
return numberplus
} else {
return numberminus}
}
When invoked, the function just returns '0'. Thanks for reading!
Take the position of 1 as starting point and loop up and (if necessary) down the array:
const log = (arr, d) => console.log(`mimimal distance [${arr.join()}]: ${d}`);
const arr = [2, 0, 0, 0, 2, 2, 1, 0, 0, 2];
const arr2 = [1, 0, 0, 0, 2, 2, 2];
const arr3 = [2, 0, 1, 0, 2, 2, 2];
const arr4 = [2, 1, 0, 0, 2, 2, 2];
log(arr, clostes(arr));
log(arr2, clostes(arr2));
log(arr3, clostes(arr3));
log(arr4, clostes(arr4));
function clostes(arr) {
// determine position of 1
const indxOf1 = arr.indexOf(1);
// create array of distances
const distances = [0, 0];
// forward search
for (let i = indxOf1; i < arr.length; i += 1) {
if (arr[i] === 2) {
break;
}
distances[0] += arr[i] !== 2 ? 1 : 0;
}
// if 1 is # position 0 backwards search
// is not necessary and minimum equals the
// already found maximum
if (indxOf1 < 1) {
distances[1] = distances[0];
return Math.min.apply(null, distances);
}
// backwards search
for (let i = indxOf1; i >= 0; i -= 1) {
if (arr[i] === 2) {
break;
}
distances[1] += arr[i] !== 2 ? 1 : 0;
}
return Math.min.apply(null, distances);
}
Something like this will do the job. You could make the code shorter but I've tried to make it clear. Once we find 1, start at that index and keep checking adjacent indices. We also do bounds checking to ensure we don't overflow either end.
function closest(arr) {
const index = arr.findIndex(n => n === 1);
const len = arr.length;
let offset = 1;
while (true) {
const before = index - offset;
const after = index + offset;
const beforeBad = before < 0;
const afterBad = after >= len;
// It's necessary to check both, we could exceed the bounds on one side but not the other.
if (beforeBad && afterBad) {
break;
}
if ((!beforeBad && arr[before] === 2) || (!afterBad && arr[after] === 2)) {
return offset;
}
++offset;
}
return -1;
}
You could approach this using entries and reduce.
const arr = [2, 0, 0, 0, 2, 2, 1, 0, 0 ,2];
const goal = arr.indexOf(1);
const indices = [];
// Find all the indices of 2 in the array
for (let x of arr.entries()) {
if (x[1] === 2) indices.push(x[0]) ;
}
// Find the index that is closest to your goal
const nearestIndex = indices.reduce((prev, curr) => {
return (Math.abs(curr - goal) < Math.abs(prev - goal) ? curr : prev);
}); // 5
console.log(Math.abs(goal - nearestIndex)); // 1
How about this:
Output = Input.map((cur,idx,arr)=>cur==2?Math.abs(idx-arr.indexOf(1)):Infinity).sort()[0]
You could avoid for loops here in favor of a more functional style. The function minDist takes m, n, and array as arguments, and returns the minimum distance between the first occurrence of m and any occurrence of n in an array.
First, map is used to create an array with pairs for each element containing the distance to the target m element and the value of the current element. Then filter is used to keep only the pairs representing n elements. Then sort is used so that the pairs representing the closest elements are at the beginning of the array. Finally, the [0] pair of the sorted array represents the closest element, and the [0] element of this closest pair is the minimum distance.
function minDist(m, n, array) {
let index = array.indexOf(m);
return array
.map((x, i) => [Math.abs(i - index), x])
.filter(p => p[1] === n)
.sort()[0][0];
}
console.log(minDist(1, 2, [1, 0, 0, 0, 2, 2, 2]));
console.log(minDist(1, 2, [2, 0, 0, 0, 2, 2, 1, 0, 0, 2]));
I need help making this function after taking an array and another array (duplicate) that has just the numbers that are duplicated in the first array (for example array=[1,2,3,1,2,3,4,5,6,6], duplicate=[1,2,3,6]).
I want it to return an array as follow: finalArray1=[[1,1],[2,2],[3,3],4,5,[6,6]].
let input = [1,2,4,591,392,391,2,5,10,2,1,1,1,20,20];
let sortArray = array => {
return array.sort(function(a, b) {
return a - b;});
}
function findDuplicates(data) {
let duplicate = [];
data.forEach(function(element, index) {
// Find if there is a duplicate or not
if (data.indexOf(element, index + 1) > -1) {
// Find if the element is already in the duplicate array or not
if (duplicate.indexOf(element) === -1) {
duplicate.push(element);
}
}
});
return duplicate;
}
let newArray = (array, duplicate) => {
for( var i = 0; i < 3; i++ ){
for( var j = 0; j < 15; j++ ){
if( duplicate[i] == array[j] ){
let finalArray = new array().push(array[j]);
}
}
return finalArray;
}
}
You could take a Map and return in original order.
The map takes the items in insertation order, which means all item are later in original order. If a key exists, it createsd an array with the value. Otherwise just the item form the array is taken as value for the map.
At the end take only the values from the map and create an array of it.
let input = [1, 2, 4, 591, 392, 391, 2, 5, 10, 2, 1, 1, 1, 20, 20],
result = Array.from(input
.reduce((m, v) => m.set(v, m.has(v) ? [].concat(m.get(v), v) : v), new Map)
.values()
);
console.log(result);
A more traditional approach
let input = [1, 2, 4, 591, 392, 391, 2, 5, 10, 2, 1, 1, 1, 20, 20],
result = input
.sort((a, b) => a -b)
.reduce((r, v, i, a) => {
if (a[i - 1] !== v && v !== a[i + 1]) r.push(v); // check if unique
else if (a[i - 1] !== v) r.push([v]); // check left element
else r[r.length - 1].push(v);
return r;
}, []);
console.log(result);
I think the following can work for you:
const input = [1,2,4,591,392,391,2,5,10,2,1,1,1,20,20];
const inOrder = input.sort((a, b) => a - b);
const unique = inOrder.reduce((a, c) => {
const found = a.find(e => e.includes(c));
if (found) found.push(c);
else a.push([c]);
return a;
}, []);
const result = unique.map(e => e.length > 1 ? e : e[0]);
console.log(result);
I hope this helps!
I'm trying to calculate the mode of an array which I can do, but I want to exclude the 0
This is my code :
const datas = [0, 0, 0, 4, 4, 2, 3, 2, 0];
function mode(numbers) {
var modes = [],
count = [],
i,
number,
maxIndex = 0;
for (i = 0; i < numbers.length; i += 1) {
number = numbers[i];
count[number] = (count[number] || 0) + 1;
if (count[number] > maxIndex) {
maxIndex = count[number];
}
}
for (i in count)
if (count.hasOwnProperty(i)) {
if (count[i] === maxIndex) {
modes.push(Number(i));
}
}
return modes;
}
mode(datas); // output : [0] and I want [4] [2]
Thanks for you time.
You can simply filter out zeroes:
datas = [0, 0, 0, 4, 4, 2, 3, 2, 0];
function mode(numbers) {
// we don't want to consider zeros
// so filter them out
numbers = numbers.filter(function(n) { return n !== 0 });
var modes = [],
count = [],
i, number, maxIndex = 0;
for (i = 0; i < numbers.length; i += 1) {
number = numbers[i];
count[number] = (count[number] || 0) + 1;
if (count[number] > maxIndex) {
maxIndex = count[number];
}
}
for (i in count)
if (count.hasOwnProperty(i)) {
if (count[i] === maxIndex) {
modes.push(Number(i));
}
}
return modes;
}
console.log(mode(datas)) // output : [4] [2]
If you're using ES6, you can use the arrow function syntax:
numbers = numbers.filter(n => n !== 0);
I just wanted to share a way to calculate the mode without for/forEach loops.
Relevant docs:
filter
reduce
Object.keys
Object.values
spread syntax (...)
arrow functions
Math.max
const counted = [0, 0, 0, 4, 4, 2, 3, 2, 0]
.filter(element => element)
.reduce(
(accumulator, currentValue) =>
({
...accumulator,
[currentValue]: (accumulator[currentValue] || 0) + 1
}),
{}
);
const maxCount = Math.max(...Object.values(counted));
const mode = Object.keys(counted).filter(key => counted[key] === maxCount);
console.log(mode);
Here is the prompt: Given an array of integers, return indices of the two numbers such that they add up to a specific target.
You may assume that each input would have exactly one solution.
Example:
Given nums = [2, 11, 15, 7], target = 9,
Because nums[0] + nums[1] = 2 + 7 = 9,
return [0, 1].
Here is my solution, but it doesn't seem to give the output I expected:
var sumTarget = function(array, target) {
var result = [];
var copy = array.slice();
var firstValue = array.shift();
if (array.length === 0) {
return result;
}
for (var i = copy.indexOf(firstValue) + 1; i < copy.length; i++) {
if (firstValue + copy[i] === target) {
Array.prototype.push.apply(result, [copy.indexOf(firstValue), i]);
}
}
return sumTarget(array, target);
};
Something like this:
https://jsfiddle.net/rqp93gpy/2/
function getIndexes(arr, target, offset) {
var result = [], i;
if (arr.length <= 1) return [];
if (offset === undefined) offset = 0;
for (i = 1; i < arr.length; i++) {
if (arr[0] + arr[i] === target) {
result.push([offset, offset + i]);
}
}
return result.concat(getIndexes(arr.slice(1), target, offset + 1));
}
console.log(JSON.stringify(getIndexes([2, 11, 15, 7, 6, 3, 4, 8, 9, 5, 7], 9),
null, 4));
output:
[
[
0,
3
],
[
0,
10
],
[
4,
5
],
[
6,
9
]
]
Although the other answer which is based on a recursive method seems neater it's algorithm is brute and naturally expected to be somewhat less efficient in the performance. This approach is more functional and turns out to be 1.5 times faster on repl.it, 2.5 times faster on Opera, 8 times faster on Firefox and 25 times faster on Chrome console tests.
Roughly the logic is as follows;
Filter out the items greater than the target
Separate odd and even array items by establishing two separate hash table objects (LUT) in which we keep a unique copy of the valid array items as properties and each property has an array of it's indexes.
Perform two separate algorithms depending on target being odd or even
Let's see;
var arr = [4,6,12,5,8,4,3,9,19,5,21,13,8,15,7,23,6,11,10,15,1,12,19,31,14,6,3,16],
tar = 12;
function getIndexes(arr, target){
var odds = {},
evens = {},
results = [],
makeResult = (a,b) => !!b ? a.forEach( e => b.forEach( f => results.push([e,f])))
: a.reduce((p,c,i) => {makeResult([p],a.slice(i)); return c});
arr.forEach((e,i) => e < target ? e%2 == 1 ? !!odds[e] ? odds[e].push(i) : ( odds[e] = [], odds[e].push(i))
: !!evens[e] ? evens[e].push(i) : (evens[e] = [], evens[e].push(i))
: false);
var oko = Object.keys(odds),
oke = Object.keys(evens);
target%2 == 1 ? oko.length <= oke.length ? oko.forEach( e => evens[target-e] && makeResult( odds[e], evens[target-e]))
: oke.forEach( e => odds[target-e] && makeResult(evens[e], odds[target-e]))
: (oko.forEach( e => (e <= target/2 && odds[target-e]) && (e < target/2 ? makeResult( odds[e], odds[target-e])
: makeResult( odds[e]))),
oke.forEach( e => (e <= target/2 && evens[target-e]) && (e < target/2 ? makeResult(evens[e], evens[target-e])
: makeResult(evens[e]))));
return results;
}
document.write('<pre>' + JSON.stringify(getIndexes(arr, tar), 0, 2) + '</pre>');
For those who would like recursion there is a tiny bit of recursion in the makeResults function.
As per performance comparison you may check https://repl.it/CIxd/1 vs https://repl.it/CIxr
Vote for this:
function sumTarget(ar, t) {
var res = [];
for (var i = 0, n = ar.length; i < n-1; i++) {
for (var j = i + 1; j < n; j++) {
if (ar[i] + ar[j] == t) {
res.push({ num1: i, val1: ar[i], num2: j, val2: ar[j] });
}
}
}
console.log(JSON.stringify(res));
}
sumTarget([2, 11, 15, 7], 9);
I hope it will help in your class ;)