0

I want to add a new object between each object in an array using functional programming. The new object is derived using its two neighbors.

Let’s say that we start with an array of integers [1,2,3], I want to add new integers by adding its neighbors so the resulting array looks like this [1,3,2,5,3]

I know of intersperse but that only allows me to add a static element. I also considered chain but that only visits one element at a time

2 Answers 2

2

You can use R.mapAccum to create an array that uses the previous item (saved in the accumulator). Take the result of R.mapAccum, and use R.last to get the array (dropping the accumulator), flatten, and remove the redundant first item:

const { pipe, mapAccum, last, flatten, tail } = R

const fn = pipe(
  mapAccum((a, b) => [b, [a + b, b]], 0),
  last, // take the array of pairs (drop the accumulator)
  flatten, // flatten the array of pairs to an array of numbers
  tail // take all items after the redundant first
)

const numbers = [1, 2, 3]

const result = fn(numbers)

console.log(result) // [1,3,2,5,3]
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.28.0/ramda.min.js" integrity="sha512-t0vPcE8ynwIFovsylwUuLPIbdhDj6fav2prN9fEu/VYBupsmrmk9x43Hvnt+Mgn2h5YPSJOk7PMo9zIeGedD1A==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

Vanilla JS - reduce the array to another array. On each iteration, spread the previous accumulator (acc), create an item by adding the current number to the previous number on the accumulator (or 0 if none), and also include the current number. Slice redundant first element off.

const fn = (cb, arr) => arr.reduce((acc, n) => 
  [...acc, cb(n,  acc.at(-1) ?? 0), n]
, []).slice(1)

const numbers = [1, 2, 3]

const add = (a, b) => a + b
const result = fn(add, numbers)

console.log(result) // [1,3,2,5,3]

Sign up to request clarification or add additional context in comments.

4 Comments

Perfect, that solved it! Just curious, how many times is the list traversed? Is it once for map and once for flatten?
The complexity of each action in the pipe is O(n), so it's actually O(4n), and since we're ignoring constants it's O(n) total. In other words it traverses the array 4 times, but unless you have huge arrays you won't feel the difference.
Nice! But two things: I think Johan was right about the traversals. last -- via nth -- is based on a property access and tail -- via slice -- is based on Array.prototype.slice. Both should be close to O (1). Second, the reduce version is clean, but what would happen if cb(a, 0) would throw an exception? To fix it, I think you might need the same sort of index-hacking I do in my flatMap version... but then you could lose the .slice call.
Yep nth is o(1). Array.proptotype.slice() creates a new array, so it's o(n). Why would cb(a, 0) throw an exception?
1

I played around with some Ramda versions, including one similar to Ori Drori's answer and one that used aperture(2), but nothing was as compelling as this vanilla JS version:

const between = (fn) => (xs) => 
  xs .flatMap ((x, i, a) => i == 0 ? [x] : [fn (a [i - 1], x), x])

const add = (a, b) => a + b

console .log (between (add) ([1, 2, 3]))

We could replace fn (a [i - 1], x) with fn (a [i - 1], x, i, a) if your needs extended beyond working with the two neighbors. Although at that point, I would probably be looking toward scan or mapAccum.

While we could start toward point-free with a version like this:

const between = (fn) => 
  addIndex (chain) ((x, i, a) => i == 0 ? [x] : [fn (a [i - 1], x), x])

getting entirely point-free look difficult and likely to obfuscate more than it clarifies. I wouldn't bother.

2 Comments

Oh man. This answer will require some time to wrap my head around 😁
Nice! I've added another solution that uses reduce.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.