Oleksiy's Blog

Swap variables in JavaScript

07/05/2015

A quite common interview question is how to swap two variables w/o creating a third one for that purpose. That's a good one for checking you understanding of core language concepts, rather than best-practice. I'd prefer using intermediate var, rather than violate comprehension.

Array approach

a = [b][b = a, 0];

or another equivalent one:

a = [b, b = a][0];

The claim of not using a var is right here, but technically we create an inline array object in between which lives while the context's active, which can contain more operational overhead than a simple var using.

Closure approach

a = (function (c) { b = a; return c; }(b));

Here we store the intermediate value in lexical scope instead. That's not the best performer either although.

Bitwise approach

If you're dealing with integers and want to make your colleaugues hate you a little bit more, use this:

x^=(y^(y=x));

Let's zoom in:

a = (a = a ^ b, b = a ^ b, a ^ b);

And more zoom:

a = a ^ b;
b = a ^ b;
a = a ^ b;

This approach is also known as XOR algorithm.

Arithmetic approach:

a = b + (b=a, 0);

This one is type sensitive. For strings it may look like this:

a = b + (b=a, '');

And less one:

b=a+(a=b)-b;

ES6 way

It's just making our live easier:

[a,b] = [b,a];

Working with tree objects in JavaScript

04/04/2015

During recent refactoring of the core of my job project, I became really sad when i found these kinds of piece of... code:

storedData.events[requestData.date] = {};
storedData.events[requestData.date][requestData.categoryId] = {};
storedData.events[requestData.date][requestData.categoryId].data = result;
storedData.events[requestData.date][requestData.categoryId].updated = currentTime;
_deferred.resolve(storedData.events[requestData.date][requestData.categoryId].data);

These kinds of blocks were dispersed throughout the application. The structure of nesting this caching tree was different for different cases, so I came up with idea to create a lean and flexible interface for accessing this object.

So by using TDD, I expected to have a method store() which takes dynamic set of arguments, the last of ones will contain the data. E.g.:

cache.store('events', 'today', 16, ['event1', 'event2']);

should store the data and set the timestamp:

{ events: { 
  today: { 
    16: {
      data: ['event1', 'event2'] ,
      timestamp: 1428089159822
    }
  }
} }

I broke down this task to several simpler ones and defined them as functions: pack() - should produces nested tree object of merge() - should recursively merge from source object to target object dive() - should recursively get the value by provided path

As utility I used UnderscoreJS.

Tests first

Alright, so I based on that wrote the tests:

it('should pack to hierarchical structure', function() {
    expect(priv.pack(['events', 'today', 16, ['event1', 'event2']])).
      toEqual({
        events: {
          today: {
            16: {
              data: ['event1', 'event2'],
              updated: mocks.timestamp
            }
          }
        }
      });
  });

  it('should store events data', function() {
    pub.store('events', 'today', 16, ['event1', 'event2']);
    pub.store('events', 'tomorrow', 150, ['event3', 'event4']);
    pub.store('event', 12345, { name: 'Event 5'});

    expect(pub.storedData.events).
      toEqual({
        today: {
          16: {
            data: ['event1', 'event2'],
            updated: mocks.timestamp
          },
        },
        tomorrow: {
          150: {
            data: ['event3', 'event4'],
            updated: mocks.timestamp
          },
        }
      });

    expect(pub.storedData.event).toEqual({
      12345: {
        data: { name: 'Event 5'},
        updated: mocks.timestamp
      }
    });

  });

Nested object from array

function store () {
  var args   = [].slice.apply(arguments), // to array
      source = pack(args);
  merge(storedData, source);
  return source;
}

// creates hierarchical structure and stores last array element as data
function pack (args) {
  var args = args.slice(0),
      obj = {}, arg = args.splice(0,1)[0];

  obj[arg] = args.length > 0 ? pack(args) : undefined;
  return obj[arg] ? obj : new Cached(arg);
}

Deep Merge

function merge (target, source) {
  _.each(_.keys(source), function (key) {
    target[key] = source[key] instanceof Cached ?
      source[key] : target[key] || source[key]; // change if endpoint otherwise choose an existing
    merge(target[key], source[key]);
  });
}

I also created a constructor of cached object to distinguish it in setting and getting:

function Cached (data) {
  _.extend(this, {
    data: data,
    updated: timeFactory.getCurrentTime()
  });
}

For accessing the database i needed a stored() method which recursively travels by object by path given in arguments. This function was slightly simpler to implement.

So I created dive() function and left the business stuff in facade stored() to make it syntactically sweeter.

function stored () {
  var args   = [].slice.apply(arguments),
      cached = dive(args, storedData);
  return cached && cached instanceof Cached ?
    cached.data : undefined;
}

function dive (args, obj) {
  var args = args.slice(0),
      key  = args.shift();
  return !obj ? undefined :
    args.length > 0 ?
      dive(args, obj[key]) : obj[key];
}

Then I added more tests:

  it('should set and get data from/to cache', function() {
    pub.store('events', 'today', 16, ['event1', 'event2']);
    pub.store('events', 'tomorrow', 52, ['event3', 'event4']);

    expect(pub.stored('events', 'today', 16)).
      toEqual(['event1', 'event2']);

    expect(pub.stored('events', 'tomorrow', 52)).
      toEqual(['event3', 'event4']);

    expect(pub.stored('events', 'live', 10)).
      toBeUndefined();

    expect(pub.stored('some', 'invalid', 'path')).
      toBeUndefined();
  });

And this is really helpful thinking in TDD. While hacking my module, I realized some cases that my algorithm didn't do because of silly mistakes. Like this one:

  it('should override existing data', function() {
    pub.store('event', 100, {name: 'old data'});
    pub.store('event', 100, {name: 'new data'});
    pub.store('event', 101, {name: 'other data'});
    expect(pub.stored('event', 100).name).toEqual('new data');
    expect(pub.stored('event', 101).name).toEqual('other data');
  });

In passion of syntactic perfection and optimization, I missed the basic thing: events had not been updating by store() function. Though UT helped me out, as always, I resolved that by adding instance checking merge() function.

Now after adding Cached() constructor it works as just fine.

Although, big refactoring is still in progress...