2

I want to do this:

["a","b","c","d"] -> ["ab","bc","cd"]

exactly like in Join elem with next one in a functional style, but in JavaScript.

Since JavaScript doesn't have a sliding function, I'm left wondering what would be the best approach. I could do something with reduce, but it would be messy since I'm not an expert, so I'd like some advice.

Kamil Kiełczewski
  • 85,173
  • 29
  • 368
  • 345
JoseHdez_2
  • 4,040
  • 6
  • 27
  • 44
  • Using the [`mapAdjacent`](https://stackoverflow.com/a/57786248/783743) function, you can write `mapAdjacent((x, y) => x + y, ["a","b","c","d"])`. – Aadit M Shah Oct 18 '19 at 09:53
  • @AaditMShah isn't `mapAdjacent` just a synonym for `fold1`? And even if not, the task at hand seems to be a folding rather than a mapping. –  Oct 18 '19 at 10:05
  • @bob No, it's not. The `fold1` function is just a fold on non-empty structures. On the other hand, the `mapAdjacent` function is a pairwise mapping of adjacent elements. For example, `fold1 (<>) [a,b,c,d]` would expand to `a <> b <> c <> d` whereas `mapAdjacent (<>) [a,b,c,d]` would expand to `[a <> b, b <> c, c <> d]`. – Aadit M Shah Oct 18 '19 at 12:49
  • @bob Every task in FP is a folding task. To use any data structure, you have to fold it. This is because the fold is the elimination rule of the data structure. A map is just a specific type of fold. In fact, in my linked answer I've implemented `mapAdjacent` using `reduceRight`. What the OP wants to do is map over adjacent pairs of elements. Hence, it makes perfect sense to use `mapAdjacent`. – Aadit M Shah Oct 18 '19 at 12:57
  • @AaditMShah I agree with `fold1`. But neither `["a","b","c","d"] -> ["ab","bc","cd"]` nor the example within your answer is structure preserving, hence I would rename it to `foldAdjacent`. –  Oct 18 '19 at 14:19
  • @bob No. The name is fine. It's doesn't preserve the structure of the array. Hence, it's not a functor. However, it does map over the adjacent pairs of elements, i.e. the spaces in between the elements. Hence, it is indeed a mapping. The name `map` is not only reserved for endofunctors. On the other hand, calling it `foldAdjacent` is completely wrong because we're not folding the pairs of adjacent elements into a single value. The `foldAdjacent` function would have the type `((a, a) -> b -> b) -> b -> [a] -> b`. The `mapAdjacent` function has the type `((a, a) -> b) -> [a] -> [b]`. – Aadit M Shah Oct 19 '19 at 03:34
  • @AaditMShah Objection! I think it is quite the opposite. A map has more constraints as a fold and thus is less general and thus should be used more restricted. A fold has an explicit accumulator, which allows the folded value to be of different type then the inner collection type. While it must be a single value it still can be a composite value, yet with less elements then the original one. –  Oct 19 '19 at 14:50
  • @bob In mathematics, a [map](https://en.wikipedia.org/wiki/Map_(mathematics)) is a synonym of a function or a morphism, in the sense that it "maps" inputs to outputs. For example, the `mapList` function applies a map of type `a -> b` to every element in `[a]`. Similarly, the `mapAdjacent` function applies a map of type `(a, a) -> b` to every pair of adjacent elements in `[a]`. Hence, the name. – Aadit M Shah Oct 19 '19 at 18:32
  • @bob Also, what exactly are you objecting to? I made a lot of statements and I don't know which statement you have an objection with. What according to you "is quite the opposite"? Furthermore, what do you mean when you say that "map has more constraints as a fold"? What are these additional constraints that you are talking about? The functor laws? The `mapAdjacent` function doesn't belong to the functor typeclass and hence doesn't need to be structure preserving. If you have a problem with the name `map` in `mapAdjacent` then as I said before the name `map` is not only reserved for functors. – Aadit M Shah Oct 19 '19 at 18:46
  • @AaditMShah First of all, I don't want to upset you. Your work is magnificant. However, when we talk about `map` in the array context we usually mean the Functor typeclass and Functor has more constraints than Foldable. In contrast, you use the term map in its general sense, i.e. a mapping from domain to codomain, for instance. I just think this is misleading when we talk about arrays, hence my suggestion to name your functin `foldAdjacent`. –  Oct 22 '19 at 13:09
  • 1
    @bob You can think of `mapAdjacent(f, xs)` as `adjacent(xs).map(f)` where `adjacent` is a function of the type `[a] -> [(a, a)]`. Similarly, `foldAdjacent(f, a, xs)` would be equivalent to `adjacent(xs).reduce(f, a)`. Here's an [implementation in Haskell](https://gist.github.com/aaditmshah/6001a6c468cd803999f457abf45a2370). Just as `concatMap` is defined as [`concat` composed with `map`](https://stackoverflow.com/q/20279306/783743), the `mapAdjacent` function is defined as `map` composed with `adjacent`. If it was `foldr` composed with `adjacent` then I'd call it `foldAdjacent`, but it's not. – Aadit M Shah Oct 22 '19 at 22:07
  • 1
    @bob By the way, the `Functor` type class is orthogonal to the `Foldable` type class. You can have functors which are not foldable. For example, a [`Reader`](https://hackage.haskell.org/package/transformers-0.5.6.2/docs/Control-Monad-Trans-Reader.html) is a functor which is not foldable. You can also have data types which are foldable but aren't functors. For example, a [`Set`](http://hackage.haskell.org/package/containers-0.4.2.0/docs/Data-Set.html) is a [foldable which is not a functor](https://stackoverflow.com/a/8359279/783743). Hence, the `Functor` type class doesn't have more constraints – Aadit M Shah Oct 22 '19 at 22:36
  • 1
    @bob We say that a type class `A` has more constraints than another type class `B` if and only if `A` is a subclass of `B`. For example, `Applicative` has more constraints than `Functor` because `Applicative` is a subclass of `Functor`. Now, `Functor` is neither a super class nor a subclass of `Foldable`. Hence, it's wrong to say that `Functor` has more constraints, or less constraints, than `Foldable`. They are two orthogonal type classes. You can't compare them. – Aadit M Shah Oct 22 '19 at 22:41

10 Answers10

2

You could use Array.from() to create an array with length of 1 less than that of the input array

const input = ["a", "b", "c", "d"],
      length = input.length - 1,
      output = Array.from({ length }, (_, i) => input[i] + input[i+1]);
      
console.log(output)
adiga
  • 34,372
  • 9
  • 61
  • 83
2

Map each element to itself concatenated with the element at i - 1, then slice off the first element in the result:

const input = ["a","b","c","d"];
const output = input
  .map((elm, i, arr) => arr[i - 1] + elm)
  .slice(1);
console.log(output);
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
1

Using map

var a=["a","b","c","d"]
var x=a.map(function(e,j){
return e+a[j+1]})
x.pop()
console.log(x)

Use a for loop

var a=["a","b","c","d"]
for(let i=0;i<a.length-1;i++)
a[i]=a[i]+a[i+1];
a.pop()
console.log(a)
ellipsis
  • 12,049
  • 2
  • 17
  • 33
1

You could use Array.fill() and .map() method to achieve required result

See below code snippet

const arr = ["a","b","c","d"];

let res = new Array(arr.length-1).fill('').map((v,i)=>arr[i]+arr[i+1]);

console.log(res);
Narendra Jadhav
  • 10,052
  • 15
  • 33
  • 44
1

try to use always ES6 syntax

let a=["a","b","c","d"]
let x=a.map((e,j)=> e+a[j+1])
x.splice(x.length-1, 1)
console.log(x)
mohit dutt
  • 43
  • 3
1

This is a bit advanced, but at least "functional", as asked:

// functional "library"

let range = n => Array(n).fill().map((_, i) => i);
let sliding = n => a => range(a.length - n + 1).map(i => a.slice(i, i + n));
let map = f => a => a.map(f);
let join = d => a => a.join(d);
let apply = (a, f) => f(a);
let pipe = (...fns) => a => fns.reduce(apply, a);

// our function 

let pairs = pipe(sliding(2), map(join('')))

console.log(pairs(['a', 'b', 'c', 'd']))

Surely you can do the same with or a similar library.

customcommander
  • 17,580
  • 5
  • 58
  • 84
georg
  • 211,518
  • 52
  • 313
  • 390
1

You can create a more general function that generates all kinds of chunks:

const foldChunks = (len, step) => f => init => xs => {
  const aux = (acc, i) =>
    i + len > xs.length ? acc
      : i + step > xs.length ? f(acc) (xs.slice(i, i + len))
      : aux(f(acc) (xs.slice(i, i + len)), i + step);

  return aux(init, 0);
};

const xs = ["a","b","c","d","e","f","g","h"];

console.log(
  foldChunks(2, 1) (acc => ([s, t]) => (acc.push(s + t), acc)) ([]) (xs));
  
console.log(
  foldChunks(2, 2) (acc => ([s, t]) => (acc.push(s + t), acc)) ([]) (xs));
  
console.log(
  foldChunks(2, 3) (acc => ([s, t]) => (acc.push(s + t), acc)) ([]) (xs));
  
console.log(
  foldChunks(3, 1) (acc => ([s, t, u]) => (acc.push(s + t + u), acc)) ([]) (xs));
1

Try

["a","b","c","d"].reduce((a,c,i,arr)=> arr[i+1] ? [...a,c+arr[i+1]] : a, [])

let r=["a","b","c","d"].reduce((a,c,i,arr)=> arr[i+1] ? [...a,c+arr[i+1]] : a, [])
console.log(r);
Kamil Kiełczewski
  • 85,173
  • 29
  • 368
  • 345
0

You could take a generator and a wanted length of the items.

function* joinNext(array, length = 2) {
    var i = 0;
    while (i + length < array.length + 1) yield array.slice(i, i++ + length).join('');
}


var array = ["a", "b", "c", "d"],
    result = [...joinNext(array)];

console.log(result);
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
0

You could use parameter destructuring to fetch the first two items, join them and process the rest of the list in a recursive call.

const concat = ([x, ...xs], ys = []) =>
  (x !== undefined && xs[0] !== undefined)
    ? concat(xs, [...ys, x + xs[0]])
    : ys;

console.log(concat(['a', 'b', 'c', 'd']));
customcommander
  • 17,580
  • 5
  • 58
  • 84