Sales Tax problem form thoughtworks for Interview

I decided to apply for Thoughtworks as soon as I heard that they are expanding in Gurgaon. After few days of application submission , I received a mail stating to solve one the two problem using Object Oriented philosophy. This was screening test to qualify for Thougthworks interview process. So, I decided to pick the first problem i.e. sales tax problem and worked on code a bit and submitted it within two days with UML design . here is the code that I have submitted. after few Hours of submission I received a call where I have been told to come and attend face to face interview in Gurgaon office.

here is the problem description with expected output and test programs:

PROBLEM ONE : SALES TAXES
Basic sales tax is applicable at a rate of 10% on all goods, except books, food,
and medical products that are exempt. Import duty is an additional sales tax
applicable on all imported goods at a rate of 5%, with no exemptions.

When I purchase items I receive a receipt which lists the name of all the items
and their price (including tax), finishing with the total cost of the items,
and the total amounts of sales taxes paid.  The rounding rules for sales tax are
that for a tax rate of n%, a shelf price of p contains (np/100 rounded up to
the nearest 0.05) amount of sales tax.

Write an application that prints out the receipt details for these shopping baskets...
INPUT:
Input 1:
1 book at 12.49
1 music CD at 14.99
1 chocolate bar at 0.85

Input 2:
1 imported box of chocolates at 10.00
1 imported bottle of perfume at 47.50

Input 3:
1 imported bottle of perfume at 27.99
1 bottle of perfume at 18.99
1 packet of headache pills at 9.75
1 box of imported chocolates at 11.25

OUTPUT
Output 1:
1 book: 12.49
1 music CD: 16.49
1 chocolate bar: 0.85
Sales Taxes: 1.50
Total: 29.83

Output 2:
1 imported box of chocolates: 10.50
1 imported bottle of perfume: 54.65
Sales Taxes: 7.65
Total: 65.15

Output 3:
1 imported bottle of perfume: 32.19
1 bottle of perfume: 20.89
1 packet of headache pills: 9.75
1 imported box of chocolates: 11.85
Sales Taxes: 6.70
Total: 74.68

Solution:

I decided to design the solution using “Design by Interface” approach . basically decoupled the implementation . I also used Google Guice for dependency Injection . Parameterized Junit has been used for Unit test.

Here is the package structure for Sales Tax problem.

I had divided the problem into multiple modules like Item , Cart and Tax calculator and names the package based on similar thoughts.

Item package has Item’s concreate implemetnation and some helper methods to define Item properties and listing goes as follows.

Item Module:

Listing1 : ItemIntf.java

import item.ItemDefine.ItemType;

/**
 * Define Item related properties
 * @author vinod
 *
 */
public interface ItemIntf {

	String getItemDescription();
	void setItemDescription(String description);
	float getItemPrice();
	void setItemPrice(float price);
	void setItemType(ItemType itemType);

	boolean isItemImported();
	boolean isItemExempted();

	float getItemPriceWithTax();
	float getItemSaleTax();
}

Concreate Implementation for ItemInf where I have used Guice dependency Injection to Inject Tax Caclulator.

Listing 2 : Item.java

package item;
import com.google.inject.Inject;

import item.ItemDefine.ItemType;
import tax.CalculatorFactory;
import tax.TaxCalculatorIntf;

/**
 * Item Implementation
 * @author vinod
 *
 */
public class Item implements ItemIntf {
	protected float itemPrice;
	private String name;
	private ItemType itemType;

	@Inject
	private TaxCalculatorIntf CALCULATOR ;

	@Override
	public String getItemDescription() {
		return name;
	}

	@Override
	public void setItemDescription(String itemName){
		name = itemName;
	}

	@Override
	public float getItemPrice() {
		return itemPrice;
	}

	@Override
	public void setItemPrice(float price) {
		itemPrice = price;

	}

	@Override
	public void setItemType(ItemType type) {
		itemType = type;
	}

	@Override
	public boolean isItemImported() {
		return itemType.isImported();
	}

	@Override
	public boolean isItemExempted() {
		return itemType.isExempted();
	}

	@Override
	public float getItemPriceWithTax() {
		return getItemSaleTax()+getItemPrice();
	}

	@Override
	public float getItemSaleTax() {
		return (CALCULATOR.calculateTax(this));
	}

	@Override
	public String toString(){
		return 1+ " "+name+" :" +getItemPriceWithTax();
	}
}

ItemDefine has an enum that defines the Item type and some of its property

Listing 3 : ItemDefine.java

package item;

/**
 * Item related Define
 * @author vinod
 *
 */
public class ItemDefine {
	public enum ItemType{
		BOOK(true,false),
		MEDICAL(true,false),
		FOOD(true,false),
		OTHERS ( false , false),
		IMPORTED_BOOK(true,true),
		IMPORTED_MEDICAL(true,true),
		IMPORTED_FOOD(true,true),
		IMPORTED_OTHERS(false,true);
		private boolean isExempted;
		private boolean isImported;
		private ItemType(boolean exepmted , boolean imported){
			isExempted = exepmted;
			isImported = imported;
		}

		public boolean isImported(){
			return isImported;
		}
		public boolean isExempted(){
			return isExempted;
		}

	}
}

Tax Calculator module:
It defines Interface and Implementation for Tax Calculator.

Listing 4: TaxCalculatorIntf.java

package tax;

import item.ItemIntf;

public interface TaxCalculatorIntf{
	float calculateTax(ItemIntf item);
}

TaxDefine has enum to define the tax type.

Listing 5: TaxDefine.java

package tax;

/**
 * Tax related define
 * @author vinod
 *
 */
public class TaxDefine {
	public enum TaxType {
		NA(0),
		BASIC(10.0f / 100),
		IMPORTED((5.0f / 100)) ,
		BOTH(BASIC.getApplicableTax()+IMPORTED.getApplicableTax());
		private float applicableTax;

		private TaxType(float tax) {
			applicableTax = tax;
		}

		public float getApplicableTax() {
			return applicableTax;
		}
	}
}

Here is the implementation of TaxCalulator as per rules given.

Listing 6: ServiceTaxCalculator.java

package tax;

import tax.TaxDefine.TaxType;
import item.ItemIntf;

/**
 * Tax Calculator implementation
 * @author vinod
 *
 */
public class ServiceTaxCalculator implements TaxCalculatorIntf{
	private static final float ROUNDOFF=0.05f;
	public float calculateTax(ItemIntf item) {
		return roundOffTax(getItemTaxType(item).getApplicableTax() * item.getItemPrice());
	}

	private TaxType getItemTaxType(ItemIntf item) {
		if(item.isItemImported() && !item.isItemExempted()){
			return TaxType.BOTH;
		}else if( item.isItemImported() && item.isItemExempted()){
			return TaxType.IMPORTED;
		}else if( !item.isItemImported() && !item.isItemExempted()){
			return TaxType.BASIC;
		}
		return TaxType.NA;
	}
	private float roundOffTax(float tax){
		return (float) Math.ceil(tax/ROUNDOFF)*ROUNDOFF;
	}

}

Cart Module:

Cart module has implementation of cart where user buy product and help in calculating and printing tax.

Listing 7 : CarIntf.java

package cart;

import item.ItemIntf;

public interface CartIntf {
	public void addItemToCart(ItemIntf item);
	public void calculateAndPrintReceiptWithTax();
	public float getTotalCost() ;
	public float getSalesTax() ;
}

Cart implements CartIntf as given below.

Listing 8 : Cart.java

package cart;

import item.ItemIntf;

import java.text.DecimalFormat;
import java.text.Format;
import java.util.ArrayList;
import java.util.List;

public class Cart implements CartIntf{

	private List<ItemIntf> itemList;
	private float saleTax;
	private float totalCost;
	private static final Format FORMATTER = new DecimalFormat("0.00");

	public Cart() {
		itemList = new ArrayList<ItemIntf>();
	}

	@Override
	public void addItemToCart(ItemIntf item) {
		itemList.add(item);
	}

	@Override
	public void calculateAndPrintReceiptWithTax() {
		resetCart();
		StringBuilder buffer = new StringBuilder();
		for (ItemIntf item : itemList) {
			buffer.append("\n").append(item.toString());
			totalCost += item.getItemPriceWithTax();
			saleTax += item.getItemSaleTax();
		}
		buffer.append("\nSales Tax:" + FORMATTER.format(saleTax));
		buffer.append("\nTotal :" + totalCost);
		System.out.println(buffer.toString());
	}

	private void resetCart() {
		totalCost = 0.0f;
		saleTax = 0.0f;
	}

	@Override
	public float getTotalCost() {
		return totalCost;
	}

	@Override
	public float getSalesTax() {
		return Float.valueOf(FORMATTER.format(saleTax));
	}

	public String toString(){
		StringBuilder buffer = new StringBuilder();
		for (ItemIntf item : itemList) {
			buffer.append("\n").append(item.toString());
		}
		return buffer.toString();
	}

}

Guice Module :

Now last is module package which initialize Google Guice dependency module and help in Injecting defined dependencies.

Listing 10 :  ShoppingModules.java

package module;

import item.Item;
import item.ItemIntf;
import tax.ServiceTaxCalculator;
import tax.TaxCalculatorIntf;

import cart.Cart;
import cart.CartIntf;

import com.google.inject.AbstractModule;
import com.google.inject.Singleton;

class ShoppingModules extends AbstractModule{

	@Override
	protected void configure() {
		bind(TaxCalculatorIntf.class).to(ServiceTaxCalculator.class).in(Singleton.class);
		bind(ItemIntf.class).to(Item.class);
		bind(CartIntf.class).to(Cart.class);
	}

}

Helper class to Initialize Guice modules.

Listing 12 :  InjectorFactory.java

package module;

import com.google.inject.Guice;
import com.google.inject.Injector;

public class InjectorFactory {
	private InjectorFactory() {}

    // Singleton instance
    private static Injector injector;

    static {
        injector = Guice.createInjector(new ShoppingModules());
    }

    /**
     * Return the default injector.
     */
    public static Injector getInjector() {
        return injector;
    }

}

Testing :

Test Modules:

I had written Junit and main program to verify Input data.

ItemTestHelper has some helper methods and test program data as follows.

Listing 13 : ItemTestHelper.java

package cart;

import item.ItemDefine.ItemType;
import item.ItemIntf;
import module.InjectorFactory;

public class ItemTestHelper {
	/**
	 * Data set
	 */
	public static Object[][] ITEM_LIST = new Object[][] {
			   { "book", 12.49f , ItemType.BOOK , 12.49f },
			   { "Music CD", 14.99f , ItemType.OTHERS , 16.49f},
			   { "chocolate bar" , 0.85f , ItemType.FOOD , 0.85f} ,
			   { "imported box of chocolates", 10.00f, ItemType.IMPORTED_FOOD, 10.50f},
			   { "imported bottle of perfume", 47.50f, ItemType.IMPORTED_OTHERS, 54.65f},
			   { "imported bottle of perfume", 27.99f,ItemType.IMPORTED_OTHERS , 32.19f},
			   { "bottle of perfume", 18.99f, ItemType.OTHERS , 20.89f},
			   { "packet of headache pills", 9.75f , ItemType.MEDICAL, 9.75f},
			   { "box of imported chocolates",11.25f,ItemType.IMPORTED_FOOD, 11.85f}
			   };

	/**
	 * Helper method to create Item
	 * @param description
	 * @param price
	 * @param itemType
	 * @return
	 */
	public static ItemIntf createItem(String description , float price , ItemType itemType ){
		ItemIntf item = InjectorFactory.getInjector().getInstance(ItemIntf.class);
		item.setItemType(itemType);
		item.setItemPrice(price);
		item.setItemDescription(description);
		return item;
	}
}

Junit for testing Item

Listing 14 : ItemTest.java

package cart;

import item.ItemDefine.ItemType;
import item.ItemIntf;

import java.util.Arrays;
import java.util.Collection;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;

@RunWith(Parameterized.class)
public class ItemTest {
	private String description;
	private float price;
	private ItemType itemType;
	private float expectedPriceWithTax;

	public ItemTest(String desc , float aPrice , ItemType type , float costWithTax){
		description = desc;
		price = aPrice;
		itemType = type;
		expectedPriceWithTax = costWithTax;
	}

	@Parameterized.Parameters
	 public static Collection<Object[]> data() {
	   Object[][] data = ItemTestHelper.ITEM_LIST;
	   return Arrays.asList(data);
	 }

	 @Test
	 public void testItemPriceWithTax(){
		ItemIntf item = ItemTestHelper.createItem(description, price, itemType);
		Assert.assertEquals("test failed for price with tax"+item.getItemDescription(), expectedPriceWithTax ,item.getItemPriceWithTax(), 0.0f );
	 }

}

Junit for testing Cart

Listing 15 : CartTest.java

package cart;

import static cart.ItemTestHelper.ITEM_LIST;
import item.ItemDefine.ItemType;
import item.ItemIntf;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

import module.InjectorFactory;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;

import com.google.inject.Inject;

@RunWith(Parameterized.class)
public class CartTest {
	private List<ItemIntf> itemList ;
	private float expectedTotalSalexTax;
	private float exptecdTotalCost;

	public CartTest(List<ItemIntf> list , float totalCost , float totalSalexTax){
		itemList = list;
		exptecdTotalCost = totalCost;
		expectedTotalSalexTax = totalSalexTax;
	}

	@Parameterized.Parameters
	 public static Collection<Object[]> data() {
	   Object[][] data = new Object[][] {
			   { createItemList(new Object[][]{ITEM_LIST[0], ITEM_LIST[1], ITEM_LIST[2]}), 29.83f , 1.50f},
			   { createItemList(new Object[][]{ITEM_LIST[3], ITEM_LIST[4]}), 65.15f , 7.65f},
			   { createItemList(new Object[][]{ITEM_LIST[5], ITEM_LIST[6], ITEM_LIST[7], ITEM_LIST[8]}), 74.68f , 6.70f} };
	   return Arrays.asList(data);
	 }

	 private static List<ItemIntf> createItemList(Object[][] data){
		 List<ItemIntf> list = new ArrayList<ItemIntf>();
		 for(Object[] item: data){
			 list.add(ItemTestHelper.createItem((String)item[0],(Float)item[1], (ItemType)item[2]));
		 }
		 return list;
	 }

	 @Test
	 public void testCartCostAndTax(){

		 CartIntf cart = InjectorFactory.getInjector().getInstance(CartIntf.class);
		 for(ItemIntf item : itemList){
			 cart.addItemToCart(item);
		 }
		 cart.calculateAndPrintReceiptWithTax();
		 Assert.assertEquals("test failed for sales tax" , expectedTotalSalexTax ,cart.getSalesTax(), 0.0f );
		 Assert.assertEquals("test failed for total cost" , exptecdTotalCost ,cart.getTotalCost(), 0.0f );
	 }	

}

Junit test suit to run both Test Program.

Listing 16 : ShoppingTestSuit.java

package cart;

import org.junit.runner.RunWith;
import org.junit.runners.Suite;
@RunWith(Suite.class)
@Suite.SuiteClasses({ItemTest.class, CartTest.class})
public class ShoopingTestSuit {

}

Now I had written a main program that can take command line input and return the output as per rules given in the problem above.

Listing 17 : Main.java

import item.Item;
import item.ItemIntf;
import item.ItemDefine.ItemType;

import java.util.Scanner;

import module.InjectorFactory;

import cart.Cart;
import cart.CartIntf;

/**
 * command line Test programme
 * @author vinod
 *
 */
public class Main {

	enum ItemTypeList{
		BOOK("Book"),
		MUSIC_CD("Music CD"),
		CHOCOLATE("Chocolate"),
		PERFUME("Perfume"),
		PILLS("Headache Pills");
		private String itemName;
		private ItemTypeList( String name){
			itemName = name;
		}

		public String getItemName(){
			return itemName;
		}
	}

	public static void main(String[] args) {
		Scanner input = new Scanner(System.in);
		ItemTypeList list[] = ItemTypeList.values();
		StringBuilder buffer  = new StringBuilder();
		int count=1;
		for(ItemTypeList item : list){
			buffer.append(count++).append(" :").append(item.getItemName()+"\n");
		}
		String productList = "Select Item : \n"+buffer.toString();
        ItemType[] itemTypes = ItemType.values();

        buffer.delete(0, buffer.length());
		for(ItemType itemType : itemTypes){
			buffer.append(itemType.ordinal()).append(" :").append(itemType.name()+"\n");
		}
		String itemTypeList ="Item type: "+buffer;

		CartIntf cart = InjectorFactory.getInjector().getInstance(CartIntf.class);
		while(true){
			System.out.println(productList);
			int product = input.nextInt();
			if(product == 0 ){
				break;
			}
			ItemIntf item = InjectorFactory.getInjector().getInstance(ItemIntf.class);
			item.setItemDescription(list[product-1].getItemName());
			System.out.println("Price: ");
            item.setItemPrice(input.nextFloat());
            System.out.println(itemTypeList);
            item.setItemType(itemTypes[input.nextInt()]);

			cart.addItemToCart(item);

		}

		cart.calculateAndPrintReceiptWithTax();
	}
}

Build and Deployment

and Lastly ant build script for compiling and running the test program and Junit.

<project name="twtest" default="junit" basedir=".">

	<property name="src" value="src" />
	<property name="testsrc" value="test" />
	<property name="junitsrc" value="junit" />
	<property name="output" value="classes" />
	<property name="jar.dir" value="jar" />
	<property name="jar" value="${jar.dir}/twtest.jar" />
	<property name="lib.dir" value="lib" />

	<path id="classpath">
		<fileset dir="${lib.dir}" includes="**/*.jar" />
	</path>

	<target name="init">
		<tstamp />
		<mkdir dir="${output}" />
		<mkdir dir="${jar.dir}" />
	</target>

	<target name="compile" depends="init">
		<javac destdir="${output}">
			<classpath refid="classpath" />
			<src path="${src}" />
			<src path="${testsrc}" />
			<src path="${junitsrc}" />
		</javac>
	</target>

	<target name="jar" depends="compile">
		<jar destfile="${jar}">
			<fileset dir="${output}" />
		</jar>
	</target>

	<target name="run" depends="jar">
		<java fork="true" classname="Main">
			<classpath>
				<path refid="classpath" />
				<path location="${jar}" />
			</classpath>
		</java>
	</target>

	<target name="junit" depends="jar">
		<junit printsummary="yes" fork="yes" haltonfailure="yes">
			<classpath>
				<path refid="classpath" />
				<path location="${jar}" />
			</classpath>
			<formatter type="plain" />
			<test name="cart.ShoopingTestSuit" outfile="ShoopingTestSuitResult"/>
		</junit>
	</target>

	<target name="clean">
		<delete dir="${output}" />
		<delete dir="${jar.dir}" />
	</target>
</project>

Please leave me a message if any comments.

About these ads

About techruminations

bear
This entry was posted in Uncategorized and tagged , , , , , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s