sçuçu
sçuçu

Reputation: 3070

Transform array of objects to array of array of objects, ie. objects grouped, in JS

How to transform an array of objects to array of array of objects? This is to group the objects in the original array according to some criteria. So, objects similar wrt some criteria in the original array will be put inside a sub array, which is an element of the new array.

I have problems with initial empty array insertion, which it should not be inserted, and keeping temporary states of sub arrays and temporary vars to be used as criteria.

What am I missing here? Is there a more cluttered, maybe less imperative, more functional way to achieve this?

var transform = function (array) {
            var rows = [];
            var parts = [];
            var lastDay = null;
            var i = 0;
            array.forEach(function(item) {
                var currentDay = new Date(item.dt_text).getDay();
                if (currentDay != lastDay) {
                  // not in same  day row
                    rows.push(parts);
                    parts = [];
                    parts.push(item);

                    lastDay = currentDay;
                    i = rows.indexOf(parts);
                    return;
                } else if (currentDay == lastDay){
                    parts.push(item);
                    return;
                }
            });
            return rows;
  },

sample data to be handles bu this function is of that form:

    [
      {
      "dt":1442275200,
      "main":{"temp":285.66,"temp_min":282.93,"temp_max":285.66,"pressure":899.08,"sea_level":1029.64,"grnd_level":899.08,"humidity":84,"temp_kf":2.73},
      "weather":[{"id":800,"main":"Clear","description":"sky is clear","icon":"01n"}],
      "clouds":{"all":0},
      "wind":{"speed":1.18,"deg":34.0052},
      "rain":{},
      "sys":{"pod":"n"},
      "dt_text":"2015-09-15 00:00:00"
      },
      {
      "dt":1442275200,
      "main":{"temp":285.66,"temp_min":282.93,"temp_max":285.66,"pressure":899.08,"sea_level":1029.64,"grnd_level":899.08,"humidity":84,"temp_kf":2.73},
      "weather":[{"id":800,"main":"Clear","description":"sky is clear","icon":"01n"}],
      "clouds":{"all":0},
      "wind":{"speed":1.18,"deg":34.0052},
      "rain":{},
      "sys":{"pod":"n"},
      "dt_text":"2015-09-15 00:00:00"
      },
      {
      "dt":1442228400,
      "main":{"temp":285.66,"temp_min":282.93,"temp_max":285.66,"pressure":899.08,"sea_level":1029.64,"grnd_level":899.08,"humidity":84,"temp_kf":2.73},
      "weather":[{"id":800,"main":"Clear","description":"sky is clear","icon":"01n"}],
      "clouds":{"all":0},
      "wind":{"speed":1.18,"deg":34.0052},
      "rain":{},
      "sys":{"pod":"n"},
      "dt_text":"2015-09-14 00:00:00"
      }
    ]

Upvotes: 0

Views: 122

Answers (3)

Igwe Kalu
Igwe Kalu

Reputation: 14868

Here's an approach that leverages Array.prototype.reduce (provided the array elements are already ordered based on element.dt as your question suggests):

function transform(array) {
    var groups = [[]];

    var lastItemForLastGroup = array.reduce(function (previous, current) {
        // continue population of the latest group
        groups[groups.length - 1].push(previous);

        if (previous.dt !== current.dt) {
            // add a new group
            groups.push([]);
        }

        // return current so that it will `previous` in the next iteration
        return current;
    });

    groups[groups.length - 1].push(lastItemForLastGroup);

    return groups;
}

For someone that understands the reduce method, it's straightforward and makes sense. Otherwise it may appear unreadable (then we would say it abuses Array.prototype.reduce instead :) ).

Using the following as test data:

var harry = [
    // group 0
    {
        "dt": 1442275200,
        "main": {"temp": 285.66, "temp_min": 282.93, "temp_max": 285.66, "pressure": 899.08, "sea_level": 1029.64, "grnd_level": 899.08, "humidity": 84, "temp_kf": 2.73},
        "weather": [{"id": 800, "main": "Clear", "description": "sky is clear", "icon": "01n"}],
        "clouds": {"all": 0},
        "wind": {"speed": 1.18, "deg": 34.0052},
        "rain": {},
        "sys": {"pod": "n"},
        "dt_text": "2015-09-15 00:00:00"
    },
    {
        "dt": 1442275200,
        "main": {"temp": 285.66, "temp_min": 282.93, "temp_max": 285.66, "pressure": 899.08, "sea_level": 1029.64, "grnd_level": 899.08, "humidity": 84, "temp_kf": 2.73},
        "weather": [{"id": 800, "main": "Clear", "description": "sky is clear", "icon": "01n"}],
        "clouds": {"all": 0},
        "wind": {"speed": 1.18, "deg": 34.0052},
        "rain": {},
        "sys": {"pod": "n"},
        "dt_text": "2015-09-15 00:00:00"
    },
    // group 1
    {
        "dt": 1442228400,
        "main": {"temp": 285.66, "temp_min": 282.93, "temp_max": 285.66, "pressure": 899.08, "sea_level": 1029.64, "grnd_level": 899.08, "humidity": 84, "temp_kf": 2.73},
        "weather": [{"id": 800, "main": "Clear", "description": "sky is clear", "icon": "01n"}],
        "clouds": {"all": 0},
        "wind": {"speed": 1.18, "deg": 34.0052},
        "rain": {},
        "sys": {"pod": "n"},
        "dt_text": "2015-09-14 00:00:00"
    },
    {
        "dt": 1442228400,
        "main": {"temp": 285.66, "temp_min": 282.93, "temp_max": 285.66, "pressure": 899.08, "sea_level": 1029.64, "grnd_level": 899.08, "humidity": 84, "temp_kf": 2.73},
        "weather": [{"id": 800, "main": "Clear", "description": "sky is clear", "icon": "01n"}],
        "clouds": {"all": 0},
        "wind": {"speed": 1.18, "deg": 34.0052},
        "rain": {},
        "sys": {"pod": "n"},
        "dt_text": "2015-09-14 00:00:00"
    },
    {
        "dt": 1442228400,
        "main": {"temp": 285.66, "temp_min": 282.93, "temp_max": 285.66, "pressure": 899.08, "sea_level": 1029.64, "grnd_level": 899.08, "humidity": 84, "temp_kf": 2.73},
        "weather": [{"id": 800, "main": "Clear", "description": "sky is clear", "icon": "01n"}],
        "clouds": {"all": 0},
        "wind": {"speed": 1.18, "deg": 34.0052},
        "rain": {},
        "sys": {"pod": "n"},
        "dt_text": "2015-09-14 00:00:00"
    },
    // group 2
    {
        "dt": 1442181600,
        "main": {"temp": 285.66, "temp_min": 282.93, "temp_max": 285.66, "pressure": 899.08, "sea_level": 1029.64, "grnd_level": 899.08, "humidity": 84, "temp_kf": 2.73},
        "weather": [{"id": 800, "main": "Clear", "description": "sky is clear", "icon": "01n"}],
        "clouds": {"all": 0},
        "wind": {"speed": 1.18, "deg": 34.0052},
        "rain": {},
        "sys": {"pod": "n"},
        "dt_text": "2015-09-13 00:00:00"
    },
    {
        "dt": 1442181600,
        "main": {"temp": 285.66, "temp_min": 282.93, "temp_max": 285.66, "pressure": 899.08, "sea_level": 1029.64, "grnd_level": 899.08, "humidity": 84, "temp_kf": 2.73},
        "weather": [{"id": 800, "main": "Clear", "description": "sky is clear", "icon": "01n"}],
        "clouds": {"all": 0},
        "wind": {"speed": 1.18, "deg": 34.0052},
        "rain": {},
        "sys": {"pod": "n"},
        "dt_text": "2015-09-13 00:00:00"
    }
];

transform(harry); returns 3 groups, where the first group contains two items, second group contains 3 items and third group contains two items as expected.

Upvotes: 0

Blindman67
Blindman67

Reputation: 54079

Here are two ways of doing it. Don't know why you have the i or why you bind this as you don't use eithe. Nor did you code push the last row of items onto rows.

Version one trying to keep to your syntax.

var transform = function (array) {
    var rows = []; // row array
    var parts;     // undefined parts array
    var lastDay;   // undefine last day
    array.forEach(function (item) {
        var currentDay = new Date(item.dt * 1000).getDay(); // get day. safari returns NaN for new Date(item.dt_txt).getDay(), where dt_txt is in format in the array of question. chrome is ok. so better use timestamp to getDay().
        if (currentDay !== lastDay) { // if not the same as last
            // sorry this test is wrong have removed
            // if (parts === undefined) { // check if there is a parts array.
                parts = [];            // create it and
                rows.push(parts);      // push it onto the rows array.
            //}
            parts.push(item);          // push the new item onto the parts array
            lastDay = currentDay;      // remeber the last day.
        } else {
            parts.push(item);          // same day so just push it onto the parts array;
        }
    });
    return rows;
}

Version 2 the most efficient way I can think of.

    var transform = function (array) {
        var rows = []; // row array
        var row;   // current row
        var lastDay;   // undefine last day
        array.forEach(function (item) {
            var currentDay = new Date(item.dt * 1000).getDay(); // get day.
// safari returns NaN for new Date(item.dt_txt).getDay(), where dt_txt is in format in the array of question. chrome is ok. so better use timestamp to getDay().
            if (currentDay !== lastDay) { // if not the same as last
                row = rows.push([item])-1; // push new array onto the rows array and get new row index.
                lastDay = currentDay;      // remeber the last day.
            } else {
                rows[row].push(item);          // same day so just push it onto the parts array;
            }
        });
        return rows;
    }

Upvotes: 1

Yoann
Yoann

Reputation: 3060

Your logic isn't bad.

But you forgot to push your row if all your data is on the same day.

So you'd have to add a rows.push(parts) after your array.forEach() loop.

Also, few tips.

  • no need to bind array.forEach().bind(this).
  • check on the length of the array instead of its value parts.length instead of parts != []

Here is a jsFiddle with your stuff fixed.

Upvotes: 0

Related Questions