Woodstock Blog

a tech blog for general algorithmic interview questions

[Design] Functional Programming

Overview

A functional language allows you to write a mathematical function, i.e. a function that takes n arguments and returns a value. If the program is executed, this function is evaluated.

A purely functional program always yields the same value for an input, and the order of evaluation is not well-defined; which means that uncertain values like user input or random values are hard to model in purely functional languages.

One Sentence

Functional programming is like describing your problem to a mathematician. Imperative programming is like giving instructions to an idiot.

Pros and Cons

Functional Programming allows coding with fewer potentials for bugs because each component is completely isolated. Also, using recursion and first-class functions allows for simple proofs of correctness which typically mirror the structure of the code.

Functional programming languages are typically less efficient in their use of CPU and memory than imperative languages such as C and Pascal.

[Java OOP] Thread Pool Pattern

Overview

Thread pool is a collection of managed threads usually organized in a queue, which execute the tasks in the task queue.

Thread pool is a group of pre-instantiated, idle threads which stand ready to be given work.

A sample thread pool (green boxes) with waiting tasks (blue) and completed tasks (yellow)

Why Thread Pool?

Thread Pools are useful when you need to limit the number of threads running in your application at the same time.

Thread pools are often used in multi threaded servers. Each connection arriving at the server via the network is wrapped as a task and passed on to a thread pool. The threads in the thread pool will process the requests on the connections concurrently.

Many server applications, such as Web servers (eg. Tomcat), database servers, file servers, or mail servers must process a large number of tasks received from a network protocol. Often, the task is short-lived and the number of requests is large.

Instead of starting a new thread for every task to execute concurrently, the task can be passed to a thread pool. As soon as the pool has any idle threads the task is assigned to one of them and executed.

Implementation

This is a simple thread pool implementation. The pool is implemented using a list of tasks, and a list of threads. Theads will fetch tasks and run it. The list is implemented with a BlockingQueue - however, it’s fine to use just a normal queue.

Main Class:

public class Main {

    public static void main(String[] args) {

        // In this example, we create 3 thread to run 10 tasks
        // set the BlockingQ size to be 5.

        ThreadPool tp = new ThreadPool(3, 5);

        List<MyTask> todoList = new ArrayList<MyTask>();
        for (int i = 1; i <= 10; i++) {
            todoList.add(new MyTask("T" + i));
        }

        for (MyTask todo : todoList) {
            tp.execute(todo);
        }

        while (!tp.noMoreTask()) {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        tp.stop();
    }

}

Pool Class:

public class ThreadPool {

    // a list of tasks (BlockingQueue)
    // a list of thread
    private MyBlockingQueue taskQueue = null;
    private List<MyThread> threads = new ArrayList<MyThread>();
    private boolean isStopped = false;

    public ThreadPool(int numThreads, int maxNumTasks) {
        taskQueue = new MyBlockingQueue(maxNumTasks);

        for (int i = 0; i < numThreads; i++) {
            threads.add(new MyThread(taskQueue));
        }
        System.out.println("Thread pool initiated. ");
        for (MyThread thread : threads) {
            thread.start();
        }
    }

    public synchronized void execute(Runnable task) {
        if (this.isStopped) {
            throw new IllegalStateException("ThreadPool is stopped");
        }

        try {
            this.taskQueue.enqueue(task);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public boolean noMoreTask() {
        return this.taskQueue.isEmpty();
    }

    public synchronized void stop() {
        this.isStopped = true;
        for (MyThread thread : threads) {
//          thread.stop();
            thread.interrupt();
        }
        System.out.println("Thread pool stopped. ");
    }
}

Thread Class:

public class MyThread extends Thread {

    private MyBlockingQueue taskQueue = null;
    private boolean isStopped = false;

    public MyThread(MyBlockingQueue queue) {
        taskQueue = queue;
    }

    public void run() {
        while (!isStopped()) {
            try {
                Runnable runnable = (Runnable) taskQueue.dequeue();
                runnable.run();
            } catch (Exception e) {
                // log or otherwise report exception,
                // but keep pool thread alive.
            }
        }
    }

    // public synchronized void stop() {
    // isStopped = true;
    // this.interrupt(); // break pool thread out of dequeue() call.
    // }

    public synchronized boolean isStopped() {
        return isStopped;
    }
}

Task Class:

public class MyTask implements Runnable {

    String message;

    public MyTask(String s) {
        message = s;
    }

    @Override
    public void run() {
        // do some task here...
        // finish the task ...
        System.out.println("Task finished: " + message);
    }
}

Output:

Thread pool initiated. 
Task finished: T1
Task finished: T3
Task finished: T4
Task finished: T5
Task finished: T6
Task finished: T2
Task finished: T7
Task finished: T10
Task finished: T9
Task finished: T8
Thread pool stopped. 

[Design] Producer Consumer Problem

Overview

Producer-consumer problem illustrates the need for synchronization in systems where many processes share a resource.

In the problem, two processes share a fixed-size buffer. One process produces information and puts it in the buffer, while the other process consumes information from the buffer. These processes do not take turns accessing the buffer, they both work concurrently.

Inadequate Solution

The code might look like this:

BufferSize = 3;
count = 0;

Producer()
{
    int widget;
    WHILE (true) {                     // loop forever
       make_new(widget);               // create a new widget to put in the buffer
       IF(count==BufferSize)
           Sleep();                    // if the buffer is full, sleep
       put_item(widget);               // put the item in the buffer
       count = count + 1;              // increment count of items
       IF (count==1)
           Wakeup(Consumer);           // if the buffer was previously empty, wake
    }                                  //  the consumer
}

Consumer()
{
    int widget;
    WHILE(true) {                    // loop forever
      IF(count==0)
           Sleep();                  // if the buffer is empty, sleep
      remove_item(widget);           // take an item from the buffer
      count = count + 1;             // decrement count of items
      IF(count==N-1)
            Wakeup(Producer);        // if buffer was previously full, wake
                                     //  the producer
      Consume_item(widget);          // consume the item
    }
}

This will cause problems, because it contains a race condition that can lead to a deadlock. Or in simply words, it has the potential of losing wakeups.

An alternative analysis is that if the programming language does not define the semantics of concurrent accesses to shared variables (in this case itemCount) without use of synchronization, then the solution is unsatisfactory for that reason, without needing to explicitly demonstrate a race condition.

Solutions are: semaphores or monitors.

Semaphore

semaphore mutex = 1;
semaphore fillCount = 0;
semaphore emptyCount = BUFFER_SIZE;

procedure producer() {
    while (true) {
        item = produceItem();
        down(emptyCount);
            down(mutex);
                putItemIntoBuffer(item);
            up(mutex);
        up(fillCount);
    }
}

procedure consumer() {
    while (true) {
        down(fillCount);
            down(mutex);
                item = removeItemFromBuffer();
            up(mutex);
        up(emptyCount);
        consumeItem(item);
    }
}

Monitor

monitor ProducerConsumer {
    int itemCount;
    condition full;
    condition empty;

    procedure add(item) {
        while (itemCount == BUFFER_SIZE) {
            wait(full);
        }

        putItemIntoBuffer(item);
        itemCount = itemCount + 1;

        if (itemCount == BUFFER_SIZE -1) {
            notify(full);
        }
    }
    procedure remove() {
        while (itemCount == 0) {
            wait(empty);
        }

        item = removeItemFromBuffer();
        itemCount = itemCount - 1;

        if (itemCount == 1) {
            notify(empty);
        }


        return item;
    }
}

procedure producer() {
    while (true) {
        item = produceItem();
        ProducerConsumer.add(item);
    }
}

procedure consumer() {
    while (true) {
        item = ProducerConsumer.remove();
        consumeItem(item);
    }
}

[Question] Max Sum of Non-Consecutive Elements

Question

link

There is an integer array consisting positive numbers only.

Find maximum possible sum of elements such that there are no 2 consecutive elements present in the sum.

Solution

It’s a little tricky to write the equation. Always remember the basic principle of DP is to assume that solution is found for (i -1), and then we calculate solution for input (i).

Don’t miss the (i-1) part.

Code

written by me

public int maxSumNonConsec(int[] input) {
    int len = input.length;
    int[] dp = new int[len];
    dp[0] = input[0];
    dp[1] = Math.max(input[0], input[1]);
    for (int i = 2; i < len; i++) {
        dp[i] = Math.max(dp[i - 1], input[i] + dp[i - 2]);
    }
    return dp[len - 1];
}

[Question] Decimal to Hexadecimal

Question

link

Decimal to Hexadecimal conversion.

Solution

Convert binary to hex as a group of 4 bits.

Read code.

Code

written by me

private final char[] hexDigits = { '0', '1', '2', '3', '4', '5', '6', '7',
        '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
private final int flag = 0x0F;

public String decToHex(int dec) {
    char[] hex = new char[8];
    for (int i = 7; i >= 0; i--) {
        int oneDigit = flag & dec;
        dec >>= 4;
        hex[i] = hexDigits[oneDigit];
    }
    return String.valueOf(hex);
}

[Design] Composition Over Inheritance

Overview

Composition over inheritance in OOP is a technique by which classes achieve polymorphic behavior and code reuse by containing other classes instead of through inheritance.

Benefits

It gives the design higher flexibility, giving business-domain classes and more stable business domain in the long term. In other words, HAS-A can be better than an IS-A relationship.

And inheritance breaks encapsulation! This post also points out these (which is hard to understand):

  1. You can’t change the implementation inherited from super classes at runtime (obviously because inheritance is defined at compile time).

  2. Inheritance exposes a subclass to details of its parent’s class implementation, that’s why it’s often said that inheritance breaks encapsulation (in a sense that you really need to focus on interfaces only not implementation, so reusing by sub classing is not always preferred).

  3. The tight coupling provided by inheritance makes the implementation of a subclass very bound up with the implementation of a super class that any change in the parent implementation will force the sub class to change.

  4. Excessive reusing by sub-classing can make the inheritance stack very deep and very confusing too.

[Question] Add Integers Without +/++

Question

link

Add two numbers (integers) without using + or plus arithmetic operator.

Solution

Bit operations.

We could not do this in 1 pass, because multiple rounding issues.

So we do it in while-loop then! 2 solutions available: iteratively and recursively.

Code

written by me

public int add(int x, int y) {
    // add y into x (and y results to 0)
    while (y != 0) {
        int carry = x & y;
        int sum = x ^ y;
        x = sum;
        y = carry << 1;
    }
    return x;
}

recursive

public int add2(int x, int y) {
    if (y == 0) {
        return x;
    }
    int carry = (x & y) << 1;
    return add2(x ^ y, carry);
}

updated on Sep 10th, 2014: this question appears on cc150v4 20.1. I wrote the following code:

public static int add_no_arithm(int a, int b) {
    if (b == 0)
        return a;
    int addition = a ^ b;
    int carry = (a & b) << 1;
    return add_no_arithm(addition, carry);
}

[CC150v4] 9.4 Sort Large Files

Question

If you have a 2 GB file with one string per line, which sorting algorithm would you use to sort the file and why?

Solution

External Sorting and N-way merge.

  1. Divide the file into K chunks, where X * K = 2 GB Bring each chunk into memory and sort the lines as usual using any O(n log n) algorithm Save the lines back to the file
  2. Now bring the next chunk into memory and sort
  3. Once we’re done, merge them one by one

Details

Wiki example of sorting 900 megabytes of data using only 100 megabytes of RAM:

  1. Read 100 MB of the data in main memory and sort by some conventional method, like quicksort.
  2. Write the sorted data to disk.
  3. Repeat steps 1 and 2 until all of the data is in sorted 100 MB chunks (there are 900MB / 100MB = 9 chunks), which now need to be merged into one single output file.
  4. Read the first 10 MB (= 100MB / (9 chunks + 1)) of each sorted chunk into input buffers in main memory and allocate the remaining 10 MB for an output buffer. (In practice, it might provide better performance to make the output buffer larger and the input buffers slightly smaller.)
  5. Perform a 9-way merge and store the result in the output buffer. Whenever the output buffer fills, write it to the final sorted file and empty it. Whenever any of the 9 input buffers empties, fill it with the next 10 MB of its associated 100 MB sorted chunk until no more data from the chunk is available. This is the key step that makes external merge sort work externally – because the merge algorithm only makes one pass sequentially through each of the chunks, each chunk does not have to be loaded completely; rather, sequential parts of the chunk can be loaded as needed.

Similar Question

link

[Google] Sort a million 32 bit integers using only 2MB of RAM.

1 million integers = 4MB which is > 2MB RAM.

Solution: external sort - divide and conquer

  1. read half the list into 2MB ram and sort using quicksort (quicksort uses logn space - however 0.5m integers is less than 2MB (2000kb v 2048kb) so this should be okay).
  2. write sorted data to disk
  3. repeat for next chunk
  4. merging results: we need an output buffer. lets say this is 1MB. then we read 512KB from each of your chunks into input buffers. we then perform a 2-way merge of the data. when an input buffer becomes empty we can pull in the remainder of the chunk.
  5. when the output buffer is full we write this to disk.
  6. when the process completes we are left with 2x 1MB files sorted in the correct order.

[Testing] Random Error Debugging 1

Question

link

You are given the source to an application which is crashing during run time. After running it 10 times in a debugger, you find it never crashes in the same place.

What programming errors could be causing this crash? How would you test each one?

Analysis

  1. code depends on system time or RNG

  2. disk full, i.e. other processes may delete a different file causing more space to be available

  3. memory issue, i.e. other processes allocate and/or free memory

  4. a pointer points to a random location in memory that is changed by another process causing some values be “valid” (very rare though)

In general a situation with other process is likely.

[Google] Million Phone Numbers

Question 1

link

How would you store 1 million phone numbers?

Solution

BitMap.

The key here is that 1 million phone numbers will be within some range, likely 10 million or so. 10 million bits = 107 bits ~ 0.12 GB. Just have a bit array where the first bit being set implies that the first phone number is set (e.g., 10 000 000) and the last bit indicates the last phone number (10 999 999). If you find a number in the list, just set the appropriate bit. You now have a bit vector of size 1million bits, sorted, and to check for a particular number, you just check whether the corresponding bit is set.

Question 2

link

How would you sort 1 million phone numbers?

Solution

BitMap or radix sort, both are O(n) complexity.

Read more here.