2
\$\begingroup\$

I have JSON data from a Google sheet in a 3D array like this:

const values = [
  [
    // useless first array
  ],
  [
    'timestamp_dont_want',
    'bird',
    '',
    '',
    'easy',
    'foot'
  ],
  [
    'timestamp_dont_want',
    'bee',
    'celery'
  ],
  // lots of other arrays
  [
    'timestamp_dont_want',
    'bat',
    'chocolate',
    'droid',
    '',
    'finger',
    '',
    'hella cool',
    'indiana'
  ]
];

The maximum length of any given array is 12. Other than the entire first array and index 0 of every following array, each value (that is present and not empty) in an array corresponds to a desired object key label I have stored as 11 strings in another array:

const labels = [
  'label_b',
  'label_c',
  'label_d',
  'label_e',
  'label_f',
  'label_g',
  'label_h',
  'label_i',
  'label_j',
  'label_k',
  'label_l'
];

Some arrays have all the values I need and many have empty values or stop at a certain index. I would like to create a new array of objects, containing only the useful and present data, that would be organized like this:

const organized = [
  {
    label_b: 'bird',
    label_e: 'easy',
    label_f: 'foot'
  },
  {
    label_b: 'bee',
    label_e: 'celery'
  },
  // lots of other objects
  {
    label_b: 'bat',
    label_c: 'chocolate',
    label_d: 'droid',
    label_f: 'finger',
    label_h: 'hella cool',
    label_i: 'indigo'
  }
];

I wrote a function, but it doesn't feel very elegant:

function sort (arr3d, arrKeys) { // Passing values and labels constants from above
  let organized = []; // Initiate array
  for (var i = 1; i < arr3d.length; i++) { // For each array of values, starts at 1 to ignore first useless array
    organized.push({}); // Create empty objects
  }
  for (var j = 0; j < arrKeys.length; j++) { // Outer loop once for each unique object key
    for (var k = 1; k < arr3d.length; k++) { // Inner loop for each array of values, starts at 1 to ignore first useless array
      if (arr3d[k][j + 1]) { // Testing presence of value before writing in next step, ignoring index 0 of each array
        organized[k - 1][arrKeys[j]] = arr3d[k][j + 1].trim(); // Craete new key in object; trim value from associated array and assign to key
      }
    }
  }
  console.log(organized); // Prove it worked
}

Any ideas about how to refactor or optimize? Maybe you can teach me about using a new JS method.

\$\endgroup\$

migrated from stackoverflow.com Aug 11 '17 at 6:02

This question came from our site for professional and enthusiast programmers.

1
\$\begingroup\$

There's two things that might help performance, and could also make your code easier to understand:

  1. only extract the length of the arrays once per loop, instead of per iteration

  2. deference deeply nested structures earlier, and as infrequently as possible

For #1, I normally use:

for (var i = 0, n = array.length; i < n; ++i) { ... }

For #2, look at your innermost nested line:

organized[k - 1][arrKeys[j]] = arr3d[k][j + 1].trim();

Consider reversing the order in which you enumerate over the data. There's more scope for optimising values from the inner loop to the outer loop if you iterate over the "leftmost" components of your 2D structures first:

for (var k = 1, kn = arr3d.length; k < kn; k++) {
    var o = organized[k - 1] = {};
    var a = arr3d[k];
    for (var j = 0, jn = arrKeys.length; j < jn; j++) {
        var t = a[j + 1];
        if (t) {
            o[arrKeys[j]] = t.trim();
        }
    }
}

Note also that now that the loops are the other way around there's no need to pre-create the objects that make up the organized array - it's initialised with an empty object in the first line of the outer loop.

\$\endgroup\$
  • \$\begingroup\$ Thanks for responding. I get no. 1: don't repeat a static operation when it can be assigned to memory. I am going to have to come back to no. 2 a bit later because my brain is kind of fried at the moment. \$\endgroup\$ – jsejcksn Aug 10 '17 at 17:04
  • \$\begingroup\$ the main point of #2 is that in your original code you had seven property accesses per inner loop pass. My revised version has three (albeit there's now also two in the outer loop, but those happen fewer times than they did before, too). For a 10x10 loop you've gone from 700 operations down to 320. \$\endgroup\$ – Alnitak Aug 11 '17 at 8:40
1
\$\begingroup\$

You should split your function into two:

function convertObject(arr, labels) {
    const obj = {};
    for (let i = 1; i < arr.length; i++) {
        if (arr[i] !== '') {   // changed to type-safe comparison operator
            obj[labels[i]] = arr[i].trim();
        }
    }
    return obj;
}

function convertArray(arr, labels) {
    const result = [];
    for (let i = 1; i < arr.length; i++) {
        result.push(convertObject(arr[i], labels));
    }
    return result;
}

I changed the function name to convert since this has nothing to do with sorting.

Also, your arr3d was not 3-dimensional at all, therefore I changed its name, too.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.