Wednesday, May 14, 2008

JavaScript Challenge: Reverse a Linked List

posted by Jonah Dempcy

This article is a follow-up on my previous post on writing a linked list class in MooTools. In that post, I offered an implementation of a singly-linked list in JavaScript and gave a challenge: To reverse a linked list in place, starting with only a reference to the head. The challenge also forbade the use of storing the data externally -- that is to say, you can't just iterate over the linked list and stuff the items in a new array, then reverse the array.

As promised, here is my solution to the challenge. If you are planning on trying the challenge yourself, read no further.

I've included the code for defining the LinkedList and LinkedListItem classes as well. It's separate from my solution, so if you want to give it a try yourself, go ahead and copy the code below.

var LinkedList = new Class({

    // Input: Array collection
    // An array of items to turn into a singly linked list
    
    initialize: function(collection) {

        this.collection = new Array();
                   
        // Construct the linked list
        collection.each(function(item, i) {
            var item = new LinkedListItem(item);
            this.collection.push(item);
            
            if (i > 0) {
                this.collection[i - 1].setNext(item);
            }
        }.bind(this));
        
        this.setFirst(this.collection[0]);
    },
    
    // Input: LinkedListItem item
    // Sets the head to be the given item
        
    setFirst: function(item) {
        this.head = item;
    },
        
    // Returns: LinkedListItem head
    
    getFirst: function() {
        return this.head;
    },
    
    // Returns: Array result
    // An array with all items' values, in the order they are linked
    
    getAll: function() {
       var result = new Array();
       
       result.push(this.getFirst().getValue());
       
       var item = this.getFirst().getNext();
       while (item) {
           result.push(item.getValue());
           item = item.getNext();
       }
       
       return result;
    }
});

// This class defines an individual item in the linked list.
// The value it is instantiated with is stored in the value property,
// and is accessible via getter/setter methods.

var LinkedListItem = new Class({
    initialize: function(item) {
        this.value = item;
    },
    
    getNext: function() {
        return this.next;
    },
    
    setNext: function(item) {
        this.next = item;
    },
    
    getValue: function() {
        return this.value;
    }
});

For an in-depth discussion of this code including step-by-step commentary, check out the previous article, LinkedList Class in MooTools.

Reverse a Linked List: Solution

This is what I came up with:

window.addEvent('domready', function() {

    var list = new LinkedList(['a', 'b', 'c', 'd', 'e', 
                               'f', 'g', '1', '2', '3', 
                               '4', '5']);

    // Challenge: Reverse the linked list in place (list.collection) 
    //            without using another array

    // Input: LinkedList list

    function reverseLinkedList(list) {

        // Inputs: LinkedListItem item, LinkedListItem next
        //
        // Returns true if next item exists
        // If not, sets the head to be the existing item
        
        function checkForNextItem(item, next) {
            if (!$defined(next)) {
                list.setFirst(item);
                return false;
            } else {
                return true;
            }
        }
        
        // Our new tail is the old head
        var tail = list.getFirst(); // 0

        var itemOne = tail.getNext(); // 1

        // If there is only one element in the list, return it
        if (!$defined(itemOne)) {
            return list;
        }
        
        // Remove the old link from the tail
        tail.setNext(null);                      

       // Prepare third item variable for while() loop
        var itemThree = tail;
        
        // The while() loop will stop when the checkForNextItem() 
        // function fails to find a next item and calls 
        // list.setFirst() on the final existing item.
        
        while (list.getFirst() == tail) {
            
            itemTwo = itemOne.getNext(); // 2
            itemOne.setNext(itemThree); // link 1 to 0            
            if (!checkForNextItem(itemOne, itemTwo)) break;
                              
            itemThree = itemTwo.getNext(); // 3            
            itemTwo.setNext(itemOne); // link 2 to 1
            if (!checkForNextItem(itemTwo, itemThree)) break;
            
            itemOne = itemThree.getNext(); // 4
            itemThree.setNext(itemTwo); // link 3 to 2
            checkForNextItem(itemThree, itemOne);                       
            
        }
        
        return list.getAll();  
    }
    
    console.log('Forward: ' + list.getAll()); 
    console.log('Backward: ' + reverseLinkedList(list));
    
    // RESULT:
    //
    // Forward: a,b,c,d,e,f,g,1,2,3,4,5
    // Backward: 5,4,3,2,1,g,f,e,d,c,b,a    
});

The main part of the function that does the work of reversing the linked list is the while() loop. I use a helper method, checkForNextItem(), which is declared at the top.

You'll notice I declared the function inside the reverseLinkedList() function. The reason I did this is that checkForNextItem() is a private method that shouldn't be accessible to code outside of the scope of the reverseLinkedList() function. Just like declaring local variables when you don't need a global, you can declare private methods inside the scope of the function block. It's perfect for helper methods like this and it doesn't clutter the global namespace.

To explain what the checkForNextItem() actually does, let's examine the while() loop where it's used.

        while (list.getFirst() == tail) {
            itemTwo = itemOne.getNext();
            itemOne.setNext(itemThree);         
            if (!checkForNextItem(itemOne, itemTwo)) break;
                              
            itemThree = itemTwo.getNext();           
            itemTwo.setNext(itemOne);
            if (!checkForNextItem(itemTwo, itemThree)) break;
            
            itemOne = itemThree.getNext();
            itemThree.setNext(itemTwo);
            checkForNextItem(itemThree, itemOne);                              
        }

The while() loop continues until list.getFirst() no longer returns the tail (the old head). At that point, the loop knows to finish because the checkForNextItem() method, unable to find a next item, will set the head to be the final remaining item in the list. Thus, the old tail becomes the new head and the linked list is reversed.

The only other area of the code which warrants discussion is the check if there is only one item in the list, and calling setNext(null) on the new tail (the former head):

        // Our new tail is the old head
        var tail = list.getFirst(); // 0

        var itemOne = tail.getNext(); // 1

        // If there is only one element in the list, return it
        if (!$defined(itemOne)) {
            return list;
        }
        
        // Remove the old link from the tail
        tail.setNext(null);        

The check to make sure itemOne is defined is simply a fail-safe for linked lists with only one item. In that case, tail.getNext() will be undefined so we just return the list as it is, with only one item. Originally I wrote the check using the JavaScript typeof keyword (i.e. if (typeof itemOne == 'undefined')), but then I remembered to use MooTools' nifty $defined() method which is both more succinct and easier to read (in my opinion).

Besides the check for single-item lists, I also had to remove the old link from the tail (formerly the head). If you don't remove the link, the while() loop will never end, since the tail will link to the next-to-last item which links back to the tail, forever and ever. So that's why I call tail.setNext(null) there.

Refactoring the Code

After writing this, something just didn't feel right about the while() loop. It was messy and seemed to be doing the same thing 3 times. I remembered back to the first time I solved this challenge and recalled a more elegant solution. After changing the code, I ended up with this:

window.addEvent('domready', function() {
    var list = new LinkedList(['a', 'b', 'c', 'd', 'e', 
                               'f', 'g', '1', '2', '3', 
                               '4', '5']);


    // Input: LinkedList list
    // Returns: LinkedList list (reversed)

    function reverseLinkedList(list) {

        // Our new tail is the old head
        var current = list.getFirst(); // 0

        // If there is only one element in the list, return it
        if (!$defined(current.getNext())) {
            return list;
        }
                
        var previous = null;
        var next;       
        
        while (current != null) {
            next = current.getNext();
            current.setNext(previous);
            previous = current;
            current = next;        
        }
        list.setFirst(previous);
        
        return list;  
    }
    console.log('Forward: ' + list.getAll()); 
    console.log('Backward: ' + reverseLinkedList(list).getAll());
    
    // RESULT:
    //
    // Forward: a,b,c,d,e,f,g,1,2,3,4,5
    // Backward: 5,4,3,2,1,g,f,e,d,c,b,a    
});

This code is more optimized and nicer to read. It has the same functionality but the while() loop has been significantly simplified and the checkForNextItem() method has been eliminated altogether. It's a good thing when you can reduce complexity like that.

One other minor edit I made during refactoring was to change the function from returning list.getAll() to just returning the list itself. After thinking about it, I prefer for the input and outputs to both be of type LinkedList and then you can just call getAll() on the result. It's a minor nuance but worth pointing out.

Leave comments if you'd like a deeper explanation of a specific area of the code or if anything is unclear, and of course your own implementations are always welcome.

Labels: , ,

Monday, May 12, 2008

JavaScript Challenge: Dispense Change

posted by Jonah Dempcy

This challenge is to write a function that takes an amount of change and returns a String Array with the coins to dispense.

Examples:

  • .25 returns ['quarter']
  • .26 returns ['quarter', 'penny']
  • .30 returns ['quarter', 'nickel']
  • 1.01 returns ['dollar', 'penny']
  • 1.16 returns ['dollar', 'dime', 'nickel', 'penny']

One of the requirements is that this returns the least amount of units of currency as possible. In other words, you can't return 100 pennies or even 4 quarters when 1 dollar would suffice. This is modeled after how real-life vending machines work.

Land of the Vending Machines Land of the Vending Machines By gullevek

If you want to attempt to solve this challenge yourself, read no further! The rest of the article will go on to describe my solution to this problem and some of the issues I encountered along the way.

Using MooTools' round() Method

I originally wrote my solution to this challenge without the use of JavaScript libraries. However, I ran into a common problem when dealing with dollar values, which is that I needed to round them to the 2nd decimal place. Without rounding to the 2nd place, you end up with subtle flaws in the data.

For instance, I was using an if() statement to check if the change remaining to dispense is greater than or equal to 0.1 (one cent). Before I added the code to round to the 2nd decimal place, the if() statement wouldn't evaluate to true when there should be a penny left to dispense. The change remaining should have been .1, but it wasn't entering the if(changeToDispense >= 0.1) statement. How could this be? Well, I was subtracting the change, but upon closer inspection, some of the values were not what I expected. When I subtract .25 from .48, I ended up with 0.22999999999999998 for instance.

Of course, I should have ended up with 0.23, not .022999 (repeating). But, without rounding the number, it ended up slightly less than the correct amount, introducing subtle bugs into the code. Since the full amount wasn't being subtracted, the last penny would never be dispensed because there wasn't a whole penny left (it was something like 0.09999999999999998). Thus, I rounded to the second decimal place and it solved the problem.

Using core JavaScript methods, it's possible to work around this problem. It involves multiplying by 100, rounding and then dividing by 100. But, since most of the projects I work on use JavaScript libraries and nearly all of them have round() functions in some form or another, I decided to get a little help from a library. Specifically, I use the MooTools round() method for solving this problem.

The round() method takes how many places after the decimal to round to, so by calling round(2), a number like 0.0999 (repeating) is converted to 0.1.

Vending Machine - Japan Vending Machine - Japan By rytc

Note that as the method is added to the prototype of the Number object (i.e. base class), it is called as an instance method of the number you want to round, not as a method of the Math object as with core JS methods. Compare:

  • Core JS: Math.round(1.8);
  • MooTools: 1.8.round();

Designing a Solution

My idea for the solution is to store a hash of the coin values keyed by their name, and then iterate over them checking if each value is higher than the amount of remaining change to dispense. For this to work, I need to iterate over them in order from highest to lowest. Otherwise, if I start in random order, it will break one of the requirements: This must return the least units of currency as possible. If I start at the penny, then it would keep dispensing pennies until there was no change left, and never get on to the other units of currency.

Therefore, the loop needs to iterate over the currency values in order of highest to lowest: dollar, quarter, dime, nickel, penny. Here is the hash object that I created to represent the currency values, and then we'll discuss how to iterate over them in order:

 var money =
     'dollar': 1.00,
     'quarter': 0.25,
     'dime': 0.10,
     'nickel': 0.05,
     'penny': 0.01                                 
 };

You might think it's as easy as using a for/in loop, but unfortunately, browser compatibility issues prevent it from being this simple. Using a for/in loop will iterate over the items but there is no guarantee of the order. Internet Explorer and Firefox use the correct order (the order of declaration) as far as I know, but Safari in particular does not follow this. So, it's not as simple as:

function dispenseChange(changeToDispense) {
 var result = new Array();
 for (var unit in money) {
     if (money[unit] > changeToDispense) result.push(unit);
 }
 return result;
}

It would be nice if it were this simple, and indeed in most browsers it is. But due to obscure quirks with the order of iteration over objects of properties, we need to store the order in its own array.

Asahi Beer Vending Machine: Komatsu Asahi Beer Vending Machine: Komatsu By jpellgen

A Working Solution

I've changed the money object to now contain two properties, a values hash and an order array.

 var money = {
     values: {
         'dollar': 1.00,
         'quarter': 0.25,
         'dime': 0.10,
         'nickel': 0.05,
         'penny': 0.01                                 
     },
     order: ['dollar', 'quarter', 'dime', 'nickel', 'penny']
 };

Now I can iterate over the order array and use that to key the values hash. It's one extra step in the process but it doesn't add too much bloat to the code, and it actually works (at least, in the browsers I've tested):

function dispenseChange(changeToDispense) {         
 var result = new Array();
 while (changeToDispense.round(2) >= 0.01) {
     for (var i = 0; i < money.order.length; i++) {
         var value = money.values[money.order[i]]; // Number, e.g. 1.00
         if (changeToDispense.round(2) >= value) {
             result.push(money.order[i]); // String, e.g. "dollar"
             changeToDispense -= value;
             break;                      
         }
     }
 }
 return result;
}   

You can test the function with logging statements:

window.addEvent('domready', function() {
 var tests = [.01, .05, .11, .27, .74, 1.08, 1.99, 2.07];
 tests.each(function(testValue) {
     console.log(testValue.toString() +
                 ': ' +
                 dispenseChange(testValue)
     );
 });
});

Improving the Implementation

One area for improvement is the nested for() loop inside the while() loop. Right now it isn't ideal, since it will waste iterations checking for currency amounts higher than the current currency. To explain what I mean, let's look at the loop again:

 while (changeToDispense.round(2) >= 0.01) {
     for (var i = 0; i < money.order.length; i++) {
         // ...
         if (changeToDispense.round(2) >=
             money.values[money.order[i]) {
             // dispense unit of currency
             break;                      
         }
     }
 }

I commented out some of the implementation details so we can focus on the structure. The reason that this is sub-optimal is that after each unit of currency is dispensed, the for() loop breaks and the while() loop starts it over again. Say there is less than a dollar remaining to dispense-- for each unit of currency dispensed, the for() loop will still check for a dollar.

To optimize this code, we'd have to only iterate over the order array once. This means the for() loop would be outside the while() loop, and for each unit of currency, the while() loop would continue to dispense change of that unit until the change remaining is less than the unit's value. It would be optimized to n (see Big O notation for details) at that point.

Optimized Code

Here is the new and improved code with the optimized loops:

var money = {
 values: {
     'dollar': 1.00,
     'quarter': 0.25,
     'dime': 0.10,
     'nickel': 0.05,
     'penny': 0.01                                 
 },
 order: ['dollar', 'quarter', 'dime', 'nickel', 'penny']
};
function dispenseChange(changeToDispense) {
  
 var result = new Array();
 money.order.each(function(unit, i) {
     var value = money.values[unit]; // Number, e.g. 1.00
     while (changeToDispense.round(2) >= value) {
         result.push(unit); // String, e.g. "dollar"
         changeToDispense -= value;
     }
 }); 
 return result;
}
  
window.addEvent('domready', function() {
 var tests = [.01, .05, .11, .27, .74, 1.08, 1.99, 2.07];
 tests.each(function(testValue) {
     console.log(testValue.toString() +
                 ': ' +
                 dispenseChange(testValue)
     );
 });
});

An observant reader will notice that I changed the for() loop to use MooTools' each() method. The reason I did this is that we no longer need to break out of the for() loop, and I prefer the each() notation. (Thanks for recommending it, Lowell!)

As always, feel free to post your own solutions to this challenge in the comments.

The latest in Japanese vending machines
The latest in Japanese vending machines By El Fotopakismo

Labels: , ,

Wednesday, April 2, 2008

JavaScript Challenge: Cardinal Numbers

posted by Jonah Dempcy


This challenge is to write functions to add and subtract numbers using the names of numbers as strings. More precisely, the challenge is to write add() and subtract() functions that take two strings as input, and return one string. For example:
add("one", "ten"); // should return "eleven"
subtract("twenty", "fifteen"); // should return "five"
For the purposes of this exercise, we will limit the maximum numbers handled to 99. So, you don't have to handle, say, add("three-thousand-five-hundred-and-fifty-two", "ten-million-and-seven"). Though, you certainly get bonus points if you handle this case as well.

Here is my solution below, which is by no means optimal. The following is my first attempt at solving this issue, coded in roughly 30 minutes, so go easy if you find any SNAFUs.

Note: I prefer 4-space indentation but I set it to 2-space for this article so the code example will fit on the page without horizontal scrolling.
var cardinalNumbers = {
    "one": 1,
    "two": 2,
    "three": 3,
    "four": 4,
    "five": 5,
    "six": 6,
    "seven": 7,
    "eight": 8,
    "nine": 9,
    "ten": 10,
    "eleven": 11,
    "twelve": 12,
    "thirteen": 13,
    "fourteen": 14,
    "fifteen": 15,
    "sixteen": 16,
    "seventeen": 17,
    "eighteen": 18,
    "nineteen": 19
}

var multiplesOfTen = {
    "twenty": 20,
    "thirty": 30,
    "forty": 40,
    "fifty": 50,
    "sixty": 60,
    "seventy": 70,
    "eighty": 80,
    "ninety": 90
}
                        
function convertWordToNumber(word) {
  var number = new Number(); // this is where we will store the result              
  
  if (word.indexOf("-") == -1) { // If it is less than 20
    number = cardinalNumbers[word];
  } else {
    var multipleOfTen = word.split("-")[0];   // e.g. "seventy"
    var cardinalNumber = word.split("-")[1];   // e.g. "six"
    number = multiplesOfTen[multipleOfTen] + cardinalNumbers[cardinalNumber]; 
  } 
  
  return number;
}

function convertNumberToWord(number) {
  var word = new String(); // this is where we will store the result
    
  if (number < 20) {
    for (var cardinalNumber in cardinalNumbers) {
      if (number == cardinalNumbers[cardinalNumber]) {
        word = cardinalNumber;
        break;
      }
    }
  } else if (number < 100) {
    if (number % 10 == 0) { // If the number is a multiple of ten
      for (var multipleOfTen in multiplesOfTen) {
        if (number == multiplesOfTen[multipleOfTen]) {
          word = cardinalNumber;
          break;
        }
      }
    } else { // not a multiple of ten
      for (var multipleOfTen in multiplesOfTen) {
        for (var i = 9; i > 0; i--) {
          if (number == multiplesOfTen[multipleOfTen] + i) {
            word = multipleOfTen + "-" + convertNumberToWord(i);
            break;
          }
        }
      }
    }
  } else {
    alert("We don't handle numbers greater than 99 yet.");
  }
    
  return word;
}

// These functions takes words as inputs, e.g. "one" and "two"

function add(x, y) {
  return convertNumberToWord(convertWordToNumber(x) + convertWordToNumber(y));
}

function subtract(x, y) {
  return convertNumberToWord(convertWordToNumber(x) - convertWordToNumber(y));
}


// Test convertWordToNumber()
alert(convertWordToNumber("three"));               // should alert 3
alert(convertWordToNumber("seventy-three"));       // should alert 73   


// Test convertNumberToWord()
alert(convertNumberToWord(8));                     // should alert "eight"
alert(convertNumberToWord(46));                    // should alert "forty-six"


// Test add() 
alert(add("one", "two"));                          // should alert "three"

// Test subtract() 
alert(subtract("fifteen", "eleven"));              // should alert "four"
Feel free to post your solutions in the comments. Particularly good solutions will get an entire article devoted to them.

Labels: ,