1

Is there someone who is familiar with functional programming in javascript that can help me parse this data from google place API:

`periods: [
  { close: { day: 1, time: '1400' }, open: { day: 1, time: '1100' } },
  { close: { day: 1, time: '2200' }, open: { day: 1, time: '1900' } },
  { close: { day: 2, time: '1400' }, open: { day: 2, time: '1100' } },
  { close: { day: 2, time: '2200' }, open: { day: 2, time: '1900' } },
  { close: { day: 3, time: '1400' }, open: { day: 3, time: '1100' } },
  { close: { day: 3, time: '2200' }, open: { day: 3, time: '1900' } },
  { close: { day: 4, time: '1400' }, open: { day: 4, time: '1100' } },
  { close: { day: 4, time: '2200' }, open: { day: 4, time: '1900' } },
  { close: { day: 5, time: '1400' }, open: { day: 5, time: '1100' } },
  { close: { day: 5, time: '2200' }, open: { day: 5, time: '1900' } },
  { close: { day: 6, time: '2200' }, open: { day: 6, time: '1100' } },
];`

so each entry in the array is an object. The object contains open and close object that represent the day of the week and opening/closing hour.

This place is open from 11:00 - 14:00 and 19:00 - 22:00 during weekdays. 11:00 - 22:00 on Saturdays. Closed on Sundays because no entry has got the day : 0.

How can I use functional programming to parse this array into an array like this:

`openingHours = [
    "Sundays: Closed"
    "Mondays: 11:00 - 14:00 and 19:00 - 22:00",
    "Tuesdays: 11:00 - 14:00 and 19:00 - 22:00",
    "Wednesdays: 11:00 - 14:00 and 19:00 - 22:00",
    "Thursdays: 11:00 - 14:00 and 19:00 - 22:00",
    "Fridays: 11:00 - 14:00 and 19:00 - 22:00",
    "Saturdays: 11:00 - 22:00"
]`
  • Can you do it without functional programming? Have you tried anything? – Bergi Jan 26 '18 at 12:56
  • No but I really want to learn functional programming. I know how to do basic stuff but this is just too complicated for me. I'm building an app and I need to parse this so I wanted to try to make use of functional programming and hopefully learn something in the process. – Herdís María Sigurðardóttir Jan 26 '18 at 13:05
  • Functional programming is all about composing functionality from smaller functions. So write a function that gets you all the periods of a certain day, write a function that builds the expected string for a day from that, and then write a function that builds the object for the whole week. I'm sure you can do that. – Bergi Jan 26 '18 at 13:13
  • Also you need to consider what should happen when there is a `period` whose opening day does not match its closing day. None of the current answers can handle this. – Bergi Jan 26 '18 at 15:36

5 Answers5

1

My attempt: https://jsfiddle.net/4u53tb1p/1/

const days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];

const openingHours =
    periods.map(p => ({ 
    day: p.open.day, 
    time: `${p.open.time} - ${p.close.time}`
  }))
  .reduce((acc, current) => {
    let time = acc[current.day];
    time.push(current.time);
    return Object.assign([], acc, { [current.day]: time });
  }, days.map(d => []))
  .map((p, index) => {
    const status = p.length == 0 ? "Closed" : p.join(" and ");
    return `${days[index]}: ${status}`;
  });

Breakdown:

The first map() convert from initial structure to:

[
    {day: 1, time: "1100 - 1400"},
    {day: 1, time: "1900 - 2200"},
    {day: 2, time: "1100 - 1400"},
    {day: 2, time: "1900 - 2200"},
    {day: 3, time: "1100 - 1400"},
    {day: 3, time: "1900 - 2200"},
    {day: 4, time: "1100 - 1400"},
    {day: 4, time: "1900 - 2200"},
    {day: 5, time: "1100 - 1400"},
    {day: 5, time: "1900 - 2200"},
    {day: 6, time: "1100 - 2200"}
]

The reduce takes objects that have the same day number, group them together (.i.e. putting their time in the same array):

[
    [],
    ["1100 - 1400", "1900 - 2200"],
    ["1100 - 1400", "1900 - 2200"],
    ["1100 - 1400", "1900 - 2200"],
    ["1100 - 1400", "1900 - 2200"],
    ["1100 - 1400", "1900 - 2200"],
    ["1100 - 2200"]
]

Notice that I provide a nested array with the same length as days array to the reducer() function as initial accumulator. This is to pad the previous map() output with any day that doesn't have entry (.i.e. Sunday in this case). Sunday doesn't have any entry, so at the end of reduce(), the value is empty array

And final step is to transform the previous nested array into desired output, which is quite straightforward: map the index to days array to get the label of the day

This solution is not using any 3rd party library

Phuong Nguyen
  • 2,960
  • 1
  • 16
  • 25
  • Could you explain this line for me? return Object.assign([], acc, { [current.day]: time }); I know what object.assign does, I'm just not sure about this { [current.day]: time }. Can you place time at certain index in the array by doing this? Can you do the same using the spread operator? – Herdís María Sigurðardóttir Jan 26 '18 at 17:10
  • @HerdísMaríaSigurðardóttir yes, that code means to put time at the index of current.day. I guess you can use spread operator, but a bit more troublesome: https://stackoverflow.com/a/38181008/1273147 – Phuong Nguyen Jan 27 '18 at 01:04
  • Thank you for this, I am actually more familiar with the spread operator than Object.assign. So I think it's easier to wrap my head around the example you gave me. – Herdís María Sigurðardóttir Jan 27 '18 at 09:43
0

Here is a solution.

First we have to add a MyGroupBy function

var groupBy = function(xs, key1, key2) {
  return xs.reduce(function(rv, x) {
    (rv[x[key1][key2]] = rv[x[key1][key2]] || []).push(x);
    return rv;
  }, {});
};

let periods = [
  { close: { day: 1, time: '1400' }, open: { day: 1, time: '1100' } },
  { close: { day: 1, time: '2200' }, open: { day: 1, time: '1900' } },
  { close: { day: 2, time: '1400' }, open: { day: 2, time: '1100' } },
  { close: { day: 2, time: '2200' }, open: { day: 2, time: '1900' } },
  { close: { day: 3, time: '1400' }, open: { day: 3, time: '1100' } },
  { close: { day: 3, time: '2200' }, open: { day: 3, time: '1900' } },
  { close: { day: 4, time: '1400' }, open: { day: 4, time: '1100' } },
  { close: { day: 4, time: '2200' }, open: { day: 4, time: '1900' } },
  { close: { day: 5, time: '1400' }, open: { day: 5, time: '1100' } },
  { close: { day: 5, time: '2200' }, open: { day: 5, time: '1900' } },
  { close: { day: 6, time: '2200' }, open: { day: 6, time: '1100' } },
];

invok our function "MyGroupBy"

openingHoursArr = myGroupBy(periods, 'open', 'day');

openingHours must be an object

let openingHours = {
    "Sundays": openingHoursArr[0] ? openingHoursArr[0].map( i => i.open.time+ ' - ' +i.close.time ).join(' and ') : "Closed",
    "Mondays": openingHoursArr[1] ? openingHoursArr[1].map( i => i.open.time+ ' - ' +i.close.time ).join(' and ') : "Closed",
    "Tuesdays": openingHoursArr[2] ? openingHoursArr[2].map( i => i.open.time+ ' - ' +i.close.time ).join(' and ') : "Closed",
    "Wednesdays": openingHoursArr[3] ? openingHoursArr[3].map( i => i.open.time+ ' - ' +i.close.time ).join(' and ') : "Closed",
    "Thursdays": openingHoursArr[4] ? openingHoursArr[4].map( i => i.open.time+ ' - ' +i.close.time ).join(' and ') : "Closed",
    "Fridays": openingHoursArr[5] ? openingHoursArr[5].map( i => i.open.time+ ' - ' +i.close.time ).join(' and ') : "Closed",
    "Saturdays": openingHoursArr[6] ? openingHoursArr[6].map( i => i.open.time+ ' - ' +i.close.time ).join(' and ') : "Closed"
};

enjoy.

Mahmoud
  • 868
  • 11
  • 27
0

Here's another options...

var data = {periods: [
  { close: { day: 1, time: '1400' }, open: { day: 1, time: '1100' } },
  { close: { day: 1, time: '2200' }, open: { day: 1, time: '1900' } },
  { close: { day: 2, time: '1400' }, open: { day: 2, time: '1100' } },
  { close: { day: 2, time: '2200' }, open: { day: 2, time: '1900' } },
  { close: { day: 3, time: '1400' }, open: { day: 3, time: '1100' } },
  { close: { day: 3, time: '2200' }, open: { day: 3, time: '1900' } },
  { close: { day: 4, time: '1400' }, open: { day: 4, time: '1100' } },
  { close: { day: 4, time: '2200' }, open: { day: 4, time: '1900' } },
  { close: { day: 5, time: '1400' }, open: { day: 5, time: '1100' } },
  { close: { day: 5, time: '2200' }, open: { day: 5, time: '1900' } },
  { close: { day: 6, time: '2200' }, open: { day: 6, time: '1100' } },
]};

var days = "Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday".split(", ");
var hours = {};
days.map(itm => hours[itm] = `${itm}s: Closed`);

data = data.periods.map(itm => ({
  day: days[itm.close.day],
  open: itm.open.time.match(/\d{2}/g).join(":"),
  close: itm.close.time.match(/\d{2}/g).join(":")
})).reduce((acc, itm) => {
  let str = `${itm.day}s: ${itm.open} - ${itm.open}`;
  acc[itm.day] = ~acc[itm.day].indexOf('Closed') ? str : acc[itm.day] + ` and ${str}`;
  return acc;
}, hours);

data = days.map(itm => data[itm]);

console.log(data);
I wrestled a bear once.
  • 22,983
  • 19
  • 69
  • 116
0

Here's yet another options...

const DAYS =
  [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ]

const format = (open, close) =>
  `${open.time} - ${close.time}`

const main = (periods) =>
{
  const hours = 
    periods.reduce ((m, { open, close }) =>
      m.has (open.day)
        ? m.set (open.day, [ ...m.get (open.day), format (open, close) ])
        : m.set (open.day, [ format (open, close) ])
      , new Map ())
  
  return DAYS.map ((name, day) =>
    hours.has (day)
      ? name + ': ' + hours.get (day) .join (' and ')
      : name + ': ' + 'Closed')
}

const data =
    [ { close : { day : 1, time : "1400" }, open : { day : 1, time : "1100" } }
    , { close : { day : 1, time : "2200" }, open : { day : 1, time : "1900" } }
    , { close : { day : 2, time : "1400" }, open : { day : 2, time : "1100" } }
    , { close : { day : 2, time : "2200" }, open : { day : 2, time : "1900" } }
    , { close : { day : 3, time : "1400" }, open : { day : 3, time : "1100" } }
    , { close : { day : 3, time : "2200" }, open : { day : 3, time : "1900" } }
    , { close : { day : 4, time : "1400" }, open : { day : 4, time : "1100" } }
    , { close : { day : 4, time : "2200" }, open : { day : 4, time : "1900" } }
    , { close : { day : 5, time : "1400" }, open : { day : 5, time : "1100" } }
    , { close : { day : 5, time : "2200" }, open : { day : 5, time : "1900" } }
    , { close : { day : 6, time : "2200" }, open : { day : 6, time : "1100" } }
    ]
    
console.log (main (data))

This solution is not using any 3rd party library

Mulan
  • 129,518
  • 31
  • 228
  • 259
0

So here is how I solved it. I used the solution from @Phuong Nguyen. I also found out that if a place is open 24/7 then the data is represented as:

`periods = [
    {open: {day: 0, time: "0000"}}
]`

So I had to add that into the getOpeningHours function.

Because the application is in Icelandic the days are in Icelandic (you can learn a little bit of Icelandic if you like)

`const days = [
  'Sunnudagar',
  'Mánudagar',
  'Þriðjudagar',
  'Miðvikudagar',
  'Fimmtudagar',
  'Föstudagar',
  'Laugardagar',
];`

`const createTimeStrings = item => {
  return {
    day: item.open.day,
    time: ``${item.open.time
      .split('')
      .reduce(addColon, [])
      .join('')} - ${item.close.time
      .split('')
      .reduce(addColon, [])
      .join('')}``,
  };
};`

`const addColon = (acc, curr, index) =>
  index === 2 ? [...acc, ':', curr] : [...acc, curr];`

This function changes the data structure to:

`[
    {day: 1, time: "11:00 - 14:00"},
    {day: 1, time: "19:00 - 22:00"},
    {day: 2, time: "11:00 - 14:00"},
]`


`const getHoursbyDay = (acc, curr) => {
  let time = acc[curr.day];
  let newTime = [...time, curr.time];
  return [...acc.slice(0, curr.day), newTime, ...acc.slice(curr.day + 1)];
};`

This function changes the data to :

`[
    [],
    ["1100 - 1400", "1900 - 2200"],
    ["1100 - 1400", "1900 - 2200"],
    ["1100 - 1400", "1900 - 2200"],
    ["1100 - 1400", "1900 - 2200"],
    ["1100 - 1400", "1900 - 2200"],
    ["1100 - 2200"]
]`

The main function that uses the other helper functions and deals with when places are open 24/7

`export const getOpeningHours = periods => {
  /* Clients can rely on always-open being represented
  as an open period containing day with value 0 and
  time with value 0000, and no close.
  */
  if (
    periods.length === 1 &&
    periods[0].open.day === 0 &&
    periods[0].open.time === '0000'
  ) {
    return ['Opið allan sólarhringinn alla daga'];
  }

  // otherwise, parse openinghours
  return periods
    .map(createTimeStrings)
    .reduce(getHoursbyDay, days.map(d => []))
    .map((arr, index) => {
      const hours = arr.length === 0 ? 'Lokað' : arr.join(' og ');
      return `${days[index]}: ${hours}`;
    });
};`