HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavascriptMinor

Python DefaultDict implementation in JavaScript

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
defaultdictimplementationjavascriptpython

Problem

I found Python's defaultdict and OrderedDict incredibly useful, hence I attempted an implementation in JavaScript. As JavaScript objects only support string as key, it is not as powerful as the Python version. Can anyone suggest a way of using an object as a key in JS, like some kind of hash function? Also, is there anything wrong with this implementation?

```
(function () {
'use strict';

function forEach(collection, func, thisArg) {
var i;
if (Array.isArray(collection)) {
if (thisArg) {
for (i = 0; i < collection.length; ++i) {
if (func.call(thisArg, collection[i], i) === false) {
break;
}
}
} else {
for (i = 0; i < collection.length; ++i) {
if (func.call(collection[i], collection[i], i) === false) {
break;
}
}
}
} else {
if (thisArg) {
for (i in collection) {
if (func.call(thisArg, collection[i], i) === false) {
break;
}
}
} else {
for (i in collection) {
if (func.call(collection[i], collection[i], i) === false) {
break;
}
}
}
}
return collection;
}

function deepCopy(obj) {
if (typeof obj != 'object') {
return obj;
}
var newObj = Array.isArray(obj) ? new Array(obj.length) : {};
forEach(obj, function (v, k) {
newObj[k] = v;
});
return newObj;
}

function assert(expression) {
if (!expression) {
throw 'Assertion faild';
}
}

function implicitEqual(val1, val2) {
return val1 == val2;
}

function explicitEqual(val1, val2) {
return val1 === val2;
}

function deleteFromArray(arr, value, flag) {
var ret = null;
var method = flag == 'i' ? implicitEqual : explicitEqual;
forEach(arr, function (val, index) {
if (method(val, value)) {
arr.splice(index, 1);
ret = val;
return false;
}
})
return

Solution

Readability

Method forEach has 4 nested if-statements, with each statement doing things a little differently. This is a good candidate to refactor for compactness. There are 2 distinct ways you are looping arg collection, so I suggest to make a method for each of them.

(1) Complex Object

One for looping a complex object:

function forEach(collection, func, thisArg) {
    var i;
    if (Array.isArray(collection)) {
       // .. 
    }
    else {
       // .. this part
    }
  }


New method propertyEach:

function propertyEach(source, iteratee) {
  for (const key in source) {
    if (iteratee(source[key], key, source) === false) {
      break;
    }
  }
  return source;
}


As an example:

const myComplexObject = {
  name: 'Doe',
  firstName: 'John'
};

propertyEach(myComplexObject, (item, i) => console.log(i + ': ' + item));


yielding

'name: Doe'  
'firstName: John'


(2) Array

And one for looping an array:

function forEach(collection, func, thisArg) {
    var i;
    if (Array.isArray(collection)) {
       // .. this part
    }
    else {
       // .. 
    }
  }


New method arrayEach:

// courtesy of lodash
function arrayEach(array, iteratee) {
  var index = -1,
      length = array == null ? 0 : array.length;

  while (++index < length) {
    if (iteratee(array[index], index, array) === false) {
      break;
    }
  }
  return array;
}


As an example:

const myArray = [ 1, 2, 3 ];
arrayEach(myArray, (item, i) => console.log(i + ': ' + item));


yielding

'0: 1'  
'1: 2'  
'2: 3'


Don't Repeat Yourself (DRY)

Then we can refactor your method to call either method depending on whether the source is an array and avoid redudancy when thisArg is undefined.

function forEach(source, func, thisArg) {
  if (thisArg === undefined) thisArg = source;
  if (Array.isArray(source)) {
    arrayEach(source, (item, i) => func.call(thisArg, item, i));
  } else {
    propertyEach(source, (item, i) => func.call(thisArg, item, i));
  }
}


As compared to the initial beast:

function forEach(collection, func, thisArg) {
    var i;
    if (Array.isArray(collection)) {
      if (thisArg) {
        for (i = 0; i < collection.length; ++i) {
          if (func.call(thisArg, collection[i], i) === false) {
            break;
          }
        }
      } else {
        for (i = 0; i < collection.length; ++i) {
          if (func.call(collection[i], collection[i], i) === false) {
            break;
          }
        }
      }
    } else {
      if (thisArg) {
        for (i in collection) {
          if (func.call(thisArg, collection[i], i) === false) {
            break;
          }
        }
      } else {
        for (i in collection) {
          if (func.call(collection[i], collection[i], i) === false) {
            break;
          }
        }
      }
    }
    return collection;
  }


The previous examples through your refactored method:

forEach(myArray, (item, i) => console.log(i + ': ' + item));
forEach(myComplexObject, (item, i) => console.log(i + ': ' + item));

Code Snippets

function forEach(collection, func, thisArg) {
    var i;
    if (Array.isArray(collection)) {
       // .. 
    }
    else {
       // .. this part
    }
  }
function propertyEach(source, iteratee) {
  for (const key in source) {
    if (iteratee(source[key], key, source) === false) {
      break;
    }
  }
  return source;
}
const myComplexObject = {
  name: 'Doe',
  firstName: 'John'
};

propertyEach(myComplexObject, (item, i) => console.log(i + ': ' + item));
'name: Doe'  
'firstName: John'
function forEach(collection, func, thisArg) {
    var i;
    if (Array.isArray(collection)) {
       // .. this part
    }
    else {
       // .. 
    }
  }

Context

StackExchange Code Review Q#79926, answer score: 3

Revisions (0)

No revisions yet.