24
optimizedRoute = ['Bengaluru', 'Salem', 'Erode', 'Tiruppur', 'Coimbatore']

result = [
  {start: bengaluru, end: salem},
  {start: salem, end: erode},
  {start: erode, end: tiruppur},
  {start: tiruppur, end: coimbatore},
]

I want to convert optimizedRoute to result. I want to do this with ES6 .reduce(). Here is what I've tried:

const r = optimizedRoute.reduce((places, place, i) => {
  const result: any = [];
  places = []
  places.push({
    startPlace: place,
    endPlace: place
  });
  // result.push ({ startplace, endplace, seats: 4 });
  // console.log(result);
  return places;
}, {});
console.log(r)
Knocktorius Maxima
  • 371
  • 2
  • 6
  • 20
kartik
  • 294
  • 3
  • 15

10 Answers10

14

You could use reduce for getting start and end part of the route and return the end for the next start.

getParts = a => (                   // take a as array and return an IIFE
    r => (                          // with an initialized result array
        a.reduce((start, end) => (  // reduce array by taking two values
            r.push({ start, end }), // push short hand properties
            end                     // and take the last value as start value for next loop
        )),
        r                           // finally return result
    )
)([]);                              // call IIFE with empty array

const getParts = a => (r => (a.reduce((start, end) => (r.push({ start, end }), end)), r))([]);

var optimizedRoute = ['Bengaluru', 'Salem', 'Erode', 'Tiruppur', 'Coimbatore']

console.log(getParts(optimizedRoute));
.as-console-wrapper { max-height: 100% !important; top: 0; }

@EDIT Grégory NEUT adding explaination

// Two thing to know first :

// When no initial value is provided,
// Array.reduce takes the index 0 as first value and start to loop at index 1

// Doing (x, y, z)
// Will execute the code x, y and z

// Equivalent to :

// x;
// y;
// z;

let ex = 0;

console.log((ex = 2, ex = 5, ex = 3));

// So about the code

const getParts = (a) => {
  // We are creating a new function here so we can have an array where to
  // push data to
  const func = (r) => {
    // Because there is no initial value
    //
    // Start will be the value at index 0 of the array
    // The loop is gonna start at index 1 of the array
    a.reduce((start, end) => {
      console.log(start, end);

      r.push({
        start,
        end,
      });

      return end;
    });

    return r;
  };

  return func([]);
};

// Equivalent
const getPartsEquivalent = (a) => {
  const r = [];

  // Because there is no initial value
  //
  // Start will be the value at index 0 of the array
  // The loop is gonna start at index 1 of the array
  a.reduce((start, end) => {
    console.log(start, end);

    r.push({
      start,
      end,
    });

    return end;
  });

  return r;
};

var optimizedRoute = ['Bengaluru', 'Salem', 'Erode', 'Tiruppur', 'Coimbatore']

console.log(getPartsEquivalent(optimizedRoute));
.as-console-wrapper {
  max-height: 100% !important;
  top: 0;
}
Orelsanpls
  • 22,456
  • 6
  • 42
  • 69
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • 4
    That's impressive but tough to get it! – sjahan Jul 18 '18 at 08:22
  • 1
    Even though this might be a (good) solution, I think this is too difficult for the OP to understand given skills of OP. – Giorgi Moniava Jul 18 '18 at 08:35
  • Nina, could you add some more detail to explain the steps of this awesome one-liner? – sjahan Jul 18 '18 at 08:36
  • @Nina Scholz I can't understand why the first pushed value is not `{ start: undefined, end: 'Bengaluru',} `. Is this something about reduce ignoring the first value when there is no initial value ? – Orelsanpls Jul 18 '18 at 08:47
  • I've got my response [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce#Description) the first value is the index 0 value when no intiial value is provided (and loop start at 1). – Orelsanpls Jul 18 '18 at 08:53
  • 1
    @GrégoryNEUT, right, if reduce does not start with an initial value, then the first two elements are taken as accumulator and item. the index starts with `1`. for illustration, you might try this: `[4, 5, 6, 7].reduce((a, b, i) => (console.log(i, a, b), b));` – Nina Scholz Jul 18 '18 at 08:53
  • @NinaScholz Thx. I've took the liberty to add explaination about your code. Because it can be tricky to understand. ofc it's your post and your answer, I totally understand if you want to modify or delete what I've added. – Orelsanpls Jul 18 '18 at 09:01
  • @nina scholz, Thanks for your solutions. Why do you use closure in your solution? – kartik Jul 18 '18 at 11:08
  • you need a variable for the result and if you do not want to declare in the body, you could take a closure/IIFE for the variable. but the real answer is, i like the style ;-) – Nina Scholz Jul 18 '18 at 11:12
  • @NinaScholz I didn't know about the syntax (a, b, c) where all expressions are evaluated but the last is returned! That's pretty cool in that kind of situation, thank you very much! – sjahan Jul 18 '18 at 13:37
  • @sjahan, it's the [comma operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comma_Operator), but actualy i am wondering where is it in my code? – Nina Scholz Jul 18 '18 at 16:18
  • @NinaScholz in the reducer function, you use it to return `end`: `(r.push({ start, end }), end)`. That's what puzzled me in the beginning! Really glad to have discovered this thanks to you :) – sjahan Jul 18 '18 at 16:33
  • right, that pattern is two times in the code, one for returning `end` and the other for returning the result `r`. – Nina Scholz Jul 18 '18 at 16:37
12

Another approach is to use map method in combination with slice. For map function, you have to pass a callback function as argument which will be applied for every item from your given array.

optimizedRoute = ['Bengaluru', 'Salem', 'Erode', 'Tiruppur', 'Coimbatore']
var result = optimizedRoute
                .slice(0, -1)
                .map((item, index) => ({start : item, end : optimizedRoute[index + 1]}));
console.log(result);
Mihai Alexandru-Ionut
  • 47,092
  • 13
  • 101
  • 128
11

I don't really understand the "with reduce" requirement, since the corresponding code using a loop is immediately readable and requires no explanation:

const optimizedRoute = ['Bengaluru', 'Salem', 'Erode', 'Tiruppur', 'Coimbatore'];
const result = new Array(optimizedRoute.length - 1);

for (let i = 0; i < result.length; ++i) {
  result[i] = {
    start: optimizedRoute[i],
    end: optimizedRoute[i + 1]
  };
}

console.log(result)

It's fun to do clever things sometimes but some of the answers are very complicated compared to this!

Tom Fenech
  • 72,334
  • 12
  • 107
  • 141
5

Here is an example with reduce. I'm not sure this is the most natural way to do this though!

Using reduce feels quite overkill and in that kind of case (but that's just my opinion), where I'd naturally use an index, well, I'd go for a simple for loop.

const optimizedRoute = ['Bengaluru', 'Salem', 'Erode', 'Tiruppur', 'Coimbatore'];
let startCity;
const result = optimizedRoute.reduce((acc, city) => {
  if(startCity) {
    acc.push({start: startCity, end: city});
  }
  startCity = city;
  return acc;
}, []);

console.log(result);
sjahan
  • 5,720
  • 3
  • 19
  • 42
3

Since you asked for reduce, here is one way to do it:

let optimizedRoute = ['Bengaluru', 'Salem', 'Erode', 'Tiruppur', 'Coimbatore']
   
let res = optimizedRoute.reduce((accum, item, i)=>{
   if(i == optimizedRoute.length - 1) 
      return accum;
   accum.push({start: item, end: optimizedRoute[i+1]})
   return accum;
}, [])

console.log(res);
Giorgi Moniava
  • 27,046
  • 9
  • 53
  • 90
3

reduce doesn't really fit the bill here as you're not trying to reduce the array to a single value.

In a perfect world we would have multi-array map version, usually known as zip, that we could use like

const result = zipWith(optimisedRoute.slice(0, -1),
                       optimisedRoute.slice(1),
                       (start, end) => ({start, end}));

but there is none in JavaScript. The best alternative is to map over a range of indices into the route using Array.from:

const result = Array.from({length: optimisedRoute.length - 1}, (_, index) => {
     const start = optimisedRoute[index];
     const end = optimisedRoute[index + 1];
     return {start, end};
});
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • This approach is very similar to what Mihai Alexandru-Ionut and Tom Fenech did, you should upvote their answers too! – Bergi Jul 18 '18 at 18:20
  • @saila Oops, fixed. The flow is pretty simple, `Array.from({length: …}, …)` is a [known trick](https://stackoverflow.com/q/36947847/1048572) that is basically equivalent to `range(0, …).map(…)` – Bergi Jul 19 '18 at 16:33
  • @ Bergi which is best as per o notations. – kartik Jul 21 '18 at 04:41
  • @saila I think all the solutions presented here have linear runtime – Bergi Jul 21 '18 at 12:19
2

The following code is using of Spread operator, Ternary operator and Array.reduce.

const optimizedRoute = [
  'Bengaluru',
  'Salem',
  'Erode',
  'Tiruppur',
  'Coimbatore',
];

// Look if we are at dealing with the last value or not
// If we do only return the constructed array
// If we don't, add a new value into the constructed array.

// tmp is the array we are constructing
// x the actual loop item
// xi the index of the item
const lastItemIndex = optimizedRoute.length - 1;

const ret = optimizedRoute.reduce((tmp, x, xi) => xi !== lastItemIndex ? [
  ...tmp,

  {
    start: x,
 
    // We access to the next item using the position of
    // the current item (xi)
    end: optimizedRoute[xi + 1],
  },
] : tmp, []);

console.log(ret);
Orelsanpls
  • 22,456
  • 6
  • 42
  • 69
2

I simplified nina scholz answer, As per nina’s idea, use reduce for getting start and end part of the route and return the end for the next start.

getParts = a => {
  const result = [];
    a.reduce((start, end) => {
      result.push({ start, end });
      return end;
    });
    return result;
};
var optimizedRoute = ['Bengaluru', 'Salem', 'Erode', 'Tiruppur', 'Coimbatore'];
console.log(this.getParts(optimizedRoute));
kartik
  • 294
  • 3
  • 15
0

I prefer readability over just short code that solves it

optimizedRoute.reduce((routes, city, index) => {
  const firstCity = index === 0;
  const lastCity = index === optimizedRoute.length - 1;
  if (!firstCity) {
    routes.last().end = city;
  }
  if (!lastCity) {
    routes.push({ start: city });
  }
  return routes;
}, []);

Also, that solution made shorter but killing readability (at least for me), could be:

optimizedRoute.reduce((routes, city) => {
  routes.last().start = city;
  routes.push({ end: city });
  return routes;
}, [{}]).slice(1, -1);

And about last(), it's a function i normally use for readability:

Array.prototype.last = function() { 
  return this[this.length - 1] 
}
Francute
  • 148
  • 1
  • 6
-1

Solution with ReduceRight, if anyone is looking for.

optimizedRoute.reduceRight((acc, d, i, arr) => 
             i == 0 
                ? acc 
                : [{ start: arr[i -1], end: d }, ...acc]  
            , [])
Nitish Narang
  • 4,124
  • 2
  • 15
  • 22
  • Pls explain why downvote here, I added this answer if anyone is looking for solution with reduceRight (which is similar to reduce). Thanks. – Nitish Narang Oct 17 '18 at 08:34