This is the question:
Given a positive integer num, return the sum of all odd Fibonacci numbers that are less than or equal to num.
The first two numbers in the Fibonacci sequence are 1 and 1. Every additional number in the sequence is the sum of the two previous numbers. The first six numbers of the Fibonacci sequence are 1, 1, 2, 3, 5 and 8.
For example, sumFibs(10) should return 10 because all odd Fibonacci numbers less than or equal to 10 are 1, 1, 3, and 5.
This is what I tried
function sumFibs(num, total = [1, 1], n = (total.length - 1 + total.length - 2)) {
if(n == num){
return total;
}
total.push(n);
sumFibs(num, n = (total.length - 1 + total.length - 2), total);
};
Question
Is it possible to use my method to make this work, if so how do I fix the syntax? If not, how would you solve the problem.
Many thanks!
continuation passing style
Continuation passing style effectively gives you programmatic return. Using a CPS function recursively can make program complexity evaporate into thin air -
const identity = x =>
x
const sumfib = (n = 0, then = identity) =>
n <= 0
? then(0, 1, 1) // base case
: sumfib // inductive: solve smaller subproblem
( n - 1
, (sum, fib, temp) =>
then(sum + fib, temp, fib + temp)
)
console.log
( sumfib(0) // 0 = 0
, sumfib(1) // 1 = 0 + 1
, sumfib(2) // 2 = 0 + 1 + 1
, sumfib(3) // 4 = 0 + 1 + 1 + 2
, sumfib(4) // 7 = 0 + 1 + 1 + 2 + 3
, sumfib(5) // 12 = 0 + 1 + 1 + 2 + 3 + 5
, sumfib(6) // 20 = 0 + 1 + 1 + 2 + 3 + 5 + 8
, sumfib(7) // 33 = 0 + 1 + 1 + 2 + 3 + 5 + 8 + 13
)
loop/recur
loop and recur give us the ability to write recursive programs like the one above, but will not encounter a stack overflow error -
const recur = (...values) =>
({ recur, values })
const loop = f =>
{ let r = f()
while (r && r.recur === recur)
r = f(...r.values)
return r
}
const sumfib = (n = 0) =>
loop // <-- loop with vars
( ( m = n
, sum = 0
, fib = 1
, temp = 1
) =>
m <= 0 // <-- exit condition
? sum // <-- base case
: recur // <-- recur with updated vars
( m - 1
, sum + fib
, temp
, temp + fib
)
)
console.log
( sumfib(0) // 0 = 0
, sumfib(1) // 1 = 0 + 1
, sumfib(2) // 2 = 0 + 1 + 1
, sumfib(3) // 4 = 0 + 1 + 1 + 2
, sumfib(4) // 7 = 0 + 1 + 1 + 2 + 3
, sumfib(5) // 12 = 0 + 1 + 1 + 2 + 3 + 5
, sumfib(6) // 20 = 0 + 1 + 1 + 2 + 3 + 5 + 8
, sumfib(7) // 33 = 0 + 1 + 1 + 2 + 3 + 5 + 8 + 13
)
streamz
so-called streams are interesting because they can possibly generate infinite values, but we don't have to compute them all at once. Again we can define our program in simple terms and let useful primitives do all of the hard work -
const fibs =
stream(0, _ =>
stream(1, _ =>
streamAdd(fibs, fibs.next)))
console.log(streamTake(fibs, 10))
// [ 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 ]
console.log(streamTake(streamSum(fibs), 10))
// [ 0, 1, 2, 4, 7, 12, 20, 33, 54, 88 ]
We just implement stream, streamAdd, streamSum, and streamTake -
const emptyStream =
Symbol('emptyStream')
const stream = (value, next) =>
( { value
, get next ()
{ delete this.next
return this.next = next()
}
}
)
const streamAdd = (s1, s2) =>
s1 === emptyStream || s2 === emptyStream
? emptyStream
: stream
( s1.value + s2.value
, _ => streamAdd(s1.next, s2.next)
)
const streamSum = (s, sum = 0) =>
s === emptyStream
? emptyStream
: stream
( sum + s.value
, _ => streamSum(s.next, sum + s.value)
)
const streamTake = (s = emptyStream, n = 0) =>
s === emptyStream || n <= 0
? []
: [ s.value, ...streamTake(s.next, n - 1) ]
Expand the snippet below to verify the results in your own browser -
const emptyStream =
Symbol('emptyStream')
const stream = (value, next) =>
( { value
, get next ()
{ delete this.next
return this.next = next()
}
}
)
const streamAdd = (s1, s2) =>
s1 === emptyStream || s2 === emptyStream
? emptyStream
: stream
( s1.value + s2.value
, _ => streamAdd(s1.next, s2.next)
)
const streamSum = (s, sum = 0) =>
s === emptyStream
? emptyStream
: stream
( sum + s.value
, _ => streamSum(s.next, sum + s.value)
)
const streamTake = (s = emptyStream, n = 0) =>
s === emptyStream || n <= 0
? []
: [ s.value, ...streamTake(s.next, n - 1) ]
const fibs =
stream(0, _ =>
stream(1, _ =>
streamAdd(fibs, fibs.next)))
console.log(streamTake(fibs, 10))
// [ 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 ]
console.log(streamTake(streamSum(fibs), 10))
// [ 0, 1, 2, 4, 7, 12, 20, 33, 54, 88 ]
Four things
(1) You don't return the result of the recursive call, therefore it does never get passed up to the caller:
sumFibs(4, [1, 1]) -> sumFibs(4, [1, 1, 2]) -> sumFibs(4, [1, 1, 2, 3])
<- [1, 1, 2, 3]
// v the return you do
// v the return you need too
(2) In the recursive call, the order of arguments is wrong.
(3) I guess instead of taking the arrays length minus 1, you want to access the property at that position in the total array.
(4) Why do you actually n as an argument? As it is only depending on total, it could also just be a variable:
function sumFibs(num, total = [1, 1]) {
const n = total[total.length - 1] + total[total.length - 2];
if(n > num){
return total;
}
total.push(n);
return sumFibs(num, total);
}
console.log(sumFibs(19));
This can be solved without an array accumulator; use n as a counter and curr and prev vars to store the data necessary to compute the Fibonacci series. Whenever we have an odd curr, add it to the running total and pass it up the call stack.
const sumOddFibs = (n, curr=1, prev=0) => {
if (curr < n) {
return sumOddFibs(n, curr + prev, curr) + (curr % 2 ? curr : 0);
}
return 0;
};
console.log(sumOddFibs(10));
As an aside, recursion is a pretty poor tool for just about anything that involves a sequential 0..n counter. Iteration makes more sense: less overhead, easier to understand and no risk of blowing the call stack. I'd also separate computation of the Fibonacci series (which is a good use case for a generator) from filtering oddness and summing so that each step is independent and can be reused:
const sum = arr => arr.reduce((a, e) => a + e);
const odds = arr => arr.filter(e => e % 2);
function *fibsBelow(n) {
for (let prev = 0, curr = 1; curr < n;) {
yield curr;
const tmp = curr;
curr += prev;
prev = tmp;
}
}
console.log(sum(odds([...fibsBelow(10)])));
Related
Is there a function how can I easily get the sum of alternating result:
Here is my simple raw list:
let x = [1, 2 , 3 , 4 , 5 , 6 ,7];
expected result:
sum by first: 1 + 3 + 5 + 7 = 16
sum by second: 2 + 4 + 6 = 12
sum by third: 3 + 6 = 9
just want to get the sum by alternating index, any suggetsions/comments TIA
You could do this:
let x = [1, 2 , 3 , 4 , 5 , 6 ,7];
function sumBy(firstelem){
var i = firstelem -1,
sum =0;
for(let k=i; k<x.length; k=k+2){
sum = sum + x[k];
}
return sum;
}
console.log(sumBy(1));
console.log(sumBy(2));
console.log(sumBy(3));
You can create your own custom function for these as follows:
const myList = [1, 2, 3, 4, 5, 6, 7];
let skipTake = (take, list) => {
let filtered = list.filter((a, i) => {
if (take == 1) {
return i % 2 == 0;
} else if (take == 2) {
return i != 0 && i % take == 1;
} else {
return i != 0 && (i + 1) % take == 0;
}
});
return filtered.reduce((agg, cur) => agg += cur);
}
console.log("first", skipTake(1, myList))
console.log("second", skipTake(2, myList))
console.log("third", skipTake(3, myList))
console.log("fourth", skipTake(4, myList))
A simple for loop will do the trick
The index inside the loop starts from 1 and goes till length-1 because of your specific requirements.
let x = [1, 2, 3, 4, 5, 6, 7];
const index = 3; //for example
let sum = 0;
for (let i = 1; i < x.length + 1; i++)
sum += (i % index == 0) ? x[i - 1] : 0;
console.log(sum);
Filter and reduce.
Change the if statement to change which indexes are selected. This one will select all even numbered indexes.
let x = [1, 2 , 3 , 4 , 5 , 6 ,7];
let y = x.filter((element, index) => {
if (index % 2 == 0) {
return element;
}
}).reduce((a, b) => a + b);
I was working on this problem to create a function using a reduce method that will get the max number in an array.
The instructor's answer is:
const numbers = [1, 2, 3, 4, 4, 5, 1, 3, 4];
const max = getMax(numbers);
console.log(max);
function getMax(array) {
if (array.length === 0) return undefined;
return array.reduce((accumulator, current) => {
return (accumulator > current) ? accumulator : current
});
I tried something like this:
return array.reduce((accumulator, current) => {
if (accumulator < current)
console.log(accumulator, current);
return accumulator = current;
});
I added console.log (accumulator, current) because I wanted to see what's going on with my code. The log shows as follows:
console.log of my code
1 2
2 3
3 4
4 5
1 3
3 4
4
Question 1. I'd like to know why my function didn't give the right output (it returned 4, not the correct output 5). Shouldn't "accumulator" stay 5 when it is assigned as 5 during the loop?
Question 2. Why do I need to return (or add return in front of) array in the function, when there is already a return below the if statement?
You didn't use { ... } after your if statement, so only the first line console.log(...) is happening when the condition is met. The accumlator = current line always happens for each iteration. You must use return when using imperative style if statement. However you can skip return when using functional style expressions, ie (accumulator, current) => accumulator < current ? current : accumulator which says "if accumulator is less than current, return current, else return accumulator".
Consider this decomposed program. When we see max as an independent function, it helps us see precisely the type of function reduce is expecting -
const max = (a = 0, b = 0) =>
a < b // if a is less than b
? b // return b
: a // otherwise return a
const getMax = (numbers = []) =>
numbers.length === 0 // if numbers.length is zero
? undefined // return undefined
: numbers.reduce(max) // otherwise return reduction
console.log(getMax([1, 2, 3, 4, 4, 5, 1, 3, 4]))
// 5
console.log(getMax([]))
// undefined
console.log(getMax())
// undefined
We can see reduce is produces the following computation -
// given
[1, 2, 3, 4, 4, 5, 1, 3, 4]
// starting with the first two
r = max(1, 2)
// then the next number
r = max(r, 3)
// then the next number
r = max(r, 4)
// then the next number
r = max(r, 4)
Or without intermediate r = ... -
max(max(max(max(max(max(max(max(1, 2), 3), 4), 4), 5), 1), 3), 4)
We could write getMax without reduce, if we wanted -
const max = (a = 0, b = 0) =>
a < b
? b
: a
const getMax = (numbers = []) =>
numbers.length === 0 // without any numbers,
? undefined // there can be no max.
: numbers.length === 1 // if we only have one,
? numbers[0] // we already know max.
: max(numbers[0], getMax(numbers.slice(1))) // else
console.log(getMax([1, 2, 3, 4, 4, 5, 1, 3, 4]))
// 5
console.log(getMax([]))
// undefined
console.log(getMax())
// undefined
Or maybe you haven't learned slice yet. You can use an array index, i, to step thru your array -
const max = (a = 0, b = 0) =>
a < b
? b
: a
const getMax = (numbers = [], i = 0) =>
numbers.length === 0 // without any numbers,
? undefined // there can be no max.
: i + 1 >= numbers.length // if the next i is not in bounds,
? numbers[i] // this is the last number
: max(numbers[i], getMax(numbers, i + 1)) // else
console.log(getMax([1, 2, 3, 4, 4, 5, 1, 3, 4]))
// 5
console.log(getMax([]))
// undefined
console.log(getMax())
// undefined
Destructuring assignment can be used as well -
const max = (a = 0, b = 0) =>
a < b
? b
: a
const getMax = ([ num, ...more ] = []) =>
more.length === 0
? num
: max(num, getMax(more))
console.log(getMax([1, 2, 3, 4, 4, 5, 1, 3, 4]))
// 5
console.log(getMax([]))
// undefined
console.log(getMax())
// undefined
This might show you how you can invent your own reduce -
const max = (a = 0, b = 0) =>
a < b
? b
: a
const reduce = (f, a = [], i = 0) =>
a.length === 0 // without any numbers,
? undefined // there can be no reduction.
: i + 1 >= a.length // if the next i is not in bounds,
? a[i] // this is the last element
: f(a[i], reduce(f, a, i + 1)) // else
const getMax = (numbers = []) =>
reduce(max, numbers) // <-- our reduce!
console.log(getMax([1, 2, 3, 4, 4, 5, 1, 3, 4]))
// 5
console.log(getMax([]))
// undefined
console.log(getMax())
// undefined
Try use Math.max method:
const numbers = [1, 2, 3, 4, 4, 5, 1, 3, 4]
numbers.reduce((acc, rec) => Math.max(acc, rec))
//5
or
function max(numbers) {
return list.reduce((acc, rec) => acc > rec ? acc : rec)
}
if you need find max value without Math.max.
I was trying to create an even number array of Fibonacci series using Functional Programming - below code
let a = [1, 2];
const r = (n) =>
Array.from(
a[a.length - 1] + a[a.length - 2] <= n ?
a.push(a[a.length - 1] + a[a.length - 2]) && r(n) :
a
)
.filter(v => !(v % 2))
//.reduce((s, v) => s+=v, 0)
console.log(r(56))
It is giving correct array but when I wanted to calculate the sum (using reduce method by commenting the last line) it is giving 0 as a result
let a = [1, 2];
const r = (n) =>
Array.from(
a[a.length - 1] + a[a.length - 2] <= n ?
a.push(a[a.length - 1] + a[a.length - 2]) && r(n) :
a
)
.filter(v => !(v % 2))
.reduce((s, v) => s+=v, 0)
console.log(r(56))
in Repl.it (Link - https://repl.it/#rahul4sap/1). However, when I try to paste the same in Chrome Dev tools it is giving correct output. Can someone please help me explain why different behavior in Chrome Dev tool and Repl.it (same behaviour I see in local Node server)
Also, it will be good if someone please help me in fixing this as well (Please note I wanted to solve this in as much Functional way as possible)
Thanks in advance!
You could separate the functions an dget the fibonacci array first and then filter the array, and so on.
This approach uses a recursion by handing over a new build array.
const
add = (a, b) => a + b,
f = (n, a = [1, 2]) => a[a.length - 1] + a[a.length - 2] < n
? f(n, [...a, a[a.length - 1] + a[a.length - 2]])
: a,
r = n => f(n)
.filter(v => !(v % 2))
.reduce(add, 0);
console.log(r(56));
Consider a simple recursive function, fibs -
const fibs = (n = 0, a = 0, b = 1) =>
n <= 0
? []
: [ a, ...fibs(n - 1, b, a + b) ]
console.log(fibs(10)) // first 10 fib numbers
// [ 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 ]
Now add your .filter -
const fibs = (n = 0, a = 0, b = 1) =>
n <= 0
? []
: [ a, ...fibs(n - 1, b, a + b) ]
const evens =
fibs(10)
.filter(n => !(n & 1))
console.log(evens)
// [ 0, 2, 8, 34 ]
Now add your .reduce -
const fibs = (n = 0, a = 0, b = 1) =>
n <= 0
? []
: [ a, ...fibs(n - 1, b, a + b) ]
const sumEvens =
fibs(10)
.filter(n => !(n & 1))
.reduce((r, n) => r + n, 0)
console.log(sumEvens)
// 44
To see how you can compute fibonacci using other functional programming techniques, see this recent Q&A
Thank you for this. But I am looking for pushing element in an array (probably in a single function) until certain condition is met (like create Fibbonacci array until the last element is less than 100).
You change n = 0 to until = 0 and change the exit condition of your loop from n <= 0 to a > until -
const fibs = (until = 0, a = 0, b = 1) =>
a > until
? []
: [ a, ...fibs(until, b, a + b) ]
console.log(fibs(100))
// [ 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89 ]
const sumEvens =
fibs(100)
.filter(n => !(n & 1))
.reduce((r, n) => r + n, 0)
console.log(sumEvens)
// 4
You may receive result in one line when you know how many fibonacci numbers do you need.
For example this code filter even numbers from the first 10 fibonacci numbers and calculate their sum:
let arr = (n) => [1, 2, ...Array(n-2)].reduce((acc, rec, idx) => (idx < 2) ? [...acc, rec] : [...acc, (acc[idx-2] + acc[idx-1])],[])
.filter(it => !(it % 2))
.reduce((s, v) => s+=v, 0)
console.log(arr(10))
I'm trying to sum a nested array [1,2,[3,4],[],[5]] without using loops but I don't see what's wrong with what I have so far.
function sumItems(array) {
let sum = 0;
array.forEach((item) => {
if (Array.isArray(item)) {
sumItems(item);
} else {
sum += item;
}
});
return sum;
}
try with
function sumItems(array) {
let sum = 0;
array.forEach((item) => {
if(Array.isArray(item)) {
sum += sumItems(item);
} else {
sum += item;
}
})
return sum;
}
recursion is a functional heritage
Recursion is a concept that comes from functional style. Mixing it with imperative style is a source of much pain and confusion for new programmers.
To design a recursive function, we identify the base and inductive case(s).
base case - the list of items to sum is empty; ie, item is Empty. return 0
inductive case 1 - the list of items is not empty; ie, there must be at least one item. if the item is a list, return its sum plus the sum of the rest of the items
inductive case 2 - there is at least one item that is not an array. return this item plus the sum of the rest of the items
const Empty =
Symbol ()
const sumDeep = ([ item = Empty, ...rest ] = []) =>
item === Empty
? 0
: Array.isArray (item)
? sumDeep (item) + sumDeep (rest)
: item + sumDeep (rest)
console.log
( sumDeep ([ [ 1, 2 ], [ 3, 4 ], [ 5, [ 6, [] ] ] ]) // 21
, sumDeep ([ 1, 2, 3, 4, 5, 6 ]) // 21
, sumDeep ([]) // 0
, sumDeep () // 0
)
As a result of this implementation, all pain and suffering are removed from the program. We do not concern ourselves with local state variables, variable reassignment, or side effects like forEach and not using the return value of a function call.
recursion caution
And a tail-recursive version which can be made stack-safe. Here, we add a parameter cont to represent our continuation which effectively allows us sequence the order of + operations without growing the stack – changes in bold
const identity = x =>
x
const sumDeep = ([ item = Empty, ...rest ] = [], cont = identity) =>
item === Empty
? cont (0)
: Array.isArray (item)
? sumDeep (item, a =>
sumDeep (rest, b =>
cont (a + b)))
: sumDeep (rest, a =>
cont (item + a))
Usage is identitcal
console.log
( sumDeep ([ [ 1, 2 ], [ 3, 4 ], [ 5, [ 6, [] ] ] ]) // 21
, sumDeep ([ 1, 2, 3, 4, 5, 6 ]) // 21
, sumDeep ([]) // 0
, sumDeep () // 0
)
performance enhancement
As #גלעד ברקן points out, array destructuring syntax used above (eg ...rest) create copies of the input array. As demonstrated in his/her answer, an index parameter can be used which will avoid creating copies. This variation shows how the index technique can also be used in a tail-recursive way
const identity = x =>
x
const sumDeep = (items = [], i = 0, cont = identity) =>
i >= items.length
? cont (0)
: Array.isArray (items [i])
? sumDeep (items [i], 0, a =>
sumDeep (items, i + 1, b =>
cont (a + b)))
: sumDeep (items, i + 1, a =>
cont (items [i] + a))
console.log
( sumDeep ([ [ 1, 2 ], [ 3, 4 ], [ 5, [ 6, [] ] ] ]) // 21
, sumDeep ([ 1, 2, 3, 4, 5, 6 ]) // 21
, sumDeep ([]) // 0
, sumDeep () // 0
)
Here's a version without using loops:
function f(arr, i){
if (i == arr.length)
return 0;
if (Array.isArray(arr[i]))
return f(arr[i], 0) + f(arr, i + 1);
return arr[i] + f(arr, i + 1);
}
console.log(f([1,2,[3,4],[],[5]], 0));
You could define a callback for using with Array#reduce, which check if an item is an array and uses this function again for that array.
function add(s, v) {
return Array.isArray(v)
? v.reduce(add, s)
: s + v;
}
var array = [1, 2, [3, 4], [], [5]];
console.log(array.reduce(add, 0));
You may do as follows;
var sumNested = ([a,...as]) => (as.length && sumNested(as)) + (Array.isArray(a) ? sumNested(a) : a || 0);
console.log(sumNested([1,2,3,[4,[5,[6]]],7,[]]));
The function argument designation [a,…as] means that when the function is fed with a nested array like [1,2,3,[4,[5,[6]]],7,[]] then a is assigned to the head which is 1 and as is assigned to the tail of the initial array which is [2,3,[4,[5,[6]]],7,[]]. The rest should be easy to understand.
function arraySum (array) {
if (array.length > 0) {
return arraySum(array[0]) + arraySum(array.slice(1));
}
if (array.length === 0) {
return 0;
} else {
return array;
}
};
This is similar to some of the other solutions but might be easier for some to read:
function Sum(arr) {
if (!arr.length) return 0;
if (Array.isArray(arr[0])) return Sum(arr[0]) + Sum(arr.slice(1));
return arr[0] + Sum(arr.slice(1));
}
console.log(Sum([[1],2,[3,[4,[5,[6,[7,[8,9,10],11,[12]]]]]]])) // 78
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.