2
\$\begingroup\$

Imagine you are a company that wants to assist some US-based companies to ship their goods to customers in Hamburg. To achieve this, lets assume that every order can be represented by a tuple (orderId, companyName, customerAddress, orderedItem).

Thus, some sample data might look like this:

001, SuperTrader, Steindamm 80, Macbook
002, Cheapskates, Reeperbahn 153, Macbook
003, MegaCorp, Steindamm 80, Book "Guide to Hamburg"
004, SuperTrader, Sternstrasse 125, Book "Cooking 101"
005, SuperTrader, Ottenser Hauptstrasse 24, Inline Skates
006, MegaCorp, Reeperbahn 153, Playstation
007, Cheapskates, Lagerstrasse 11, Flux compensator
008, SuperTrader, Reeperbahn 153, Inline Skates

Instructions:

Using node.js, please implement a working solution to perform the following kind of operations on the data:

  1. show all orders from a particular company

  2. show all orders to a particular address

  3. delete a particular order given an OrderId

  4. display how often each item has been ordered, in descending order (ie in the above example, 2x for Macbook and Inline skates, 1x for the rest)

Note:

Please optimize your code and do not convolute it with handling exceptions/edge cases – we are more interested in readability for this solution.

Solution:

const data = [
  // orderId, company, custAddr, product
  ['001', 'SuperTrader', 'Steindamm 80', 'Macbook'],
  ['002', 'Cheapskates', 'Reeperbahn 153', 'Macbook'],
  ['003', 'MegaCorp', 'Steindamm 80, Book', 'Guide to Hamburg'],
  ['004', 'SuperTrader', 'Sternstrasse 125', 'Book "Cooking 101"'],
  ['005', 'SuperTrader', 'Ottenser Hauptstrasse 24', 'Inline Skates'],
  ['006', 'MegaCorp', 'Reeperbahn 153', 'Playstation'],
  ['007', 'Cheapskates', 'Lagerstrasse 11', 'Flux compensator'],
  ['008', 'SuperTrader', 'Reeperbahn 153', 'Inline Skates'],
];

// show all orders from a particular company
const allCompanyOrders = (orders, company) => { // O(n)
  return orders.filter((o) => o[1] === company);
};

// all orders to an address
const allOrdersToAddress = (orders, address) => { //O(n)
  return orders.filter((o) => o[2] === address);
};

// delete an order
const removeOrder = (orders, id) => { // O(n)
  const result = [];
  orders.forEach((o) => {
    if (o[0] !== id) {
      result.push(o);
    }
  });
  return result;
};

const itemsCount = (orders) => {
  const map = Object.create(null);
  orders.forEach((o) => {
    const name = o[3];
    const count = map[name] || 0;
    map[name] = count + 1;
  });
  return map;
};

//show often ordered items in descending ordered
const showMostFrequent = (orders) => {
  const map = itemsCount(orders); 
  const result = [];

  const keys = Object.keys(map);
  for (let name of keys) {
    const count = map[name];
    result.push({name, count});
  }

  // O(nlogn) + O(n) memory
  return result.sort((a, b) => b.count - a.count)
    .map((o) => o.name);
};

console.log(allCompanyOrders(data, 'SuperTrader').length === 4);
console.log(allOrdersToAddress(data, 'Reeperbahn 153').length === 3);
console.log(removeOrder(data, '007').length === 7);
console.log(showMostFrequent(data));

Questions:

  1. Should I preprocess data for optimisation at cost of memory?
  2. Right now I have to use indexes everywhere which feels uncomfortable.
\$\endgroup\$
2
\$\begingroup\$

I'd definitely map the tuples to objects, to avoid all the index stuff later on. In this case, the question specifically says it's all about readability, so don't worry about memory usage (also because it's next to nothing).

const orders = data.map(order => {
  const [orderId, companyName, companyAddress, orderedItem] = order;
  return { orderId, companyName, companyAddress, orderedItem };
});

As for the rest of the code:

Your removeOrder could be implemented using filter, just like the all* functions; no need to manually push to a new array. And your showMostFrequent and itemsCount functions could both be implemented using map - again instead of manually pushing to a new array/object.

Overall though, I'd wrap everything in an Orders object/class, instead of passing the orders array on every call. Maybe something like this:

class Orders {
  constructor(data) {
    this.orders = data.map(order => {
      const [orderId, companyName, companyAddress, orderedItem] = order;
      return { orderId, companyName, companyAddress, orderedItem };
    });
  }

  // note: Assumes orderIds are unique
  remove(orderId) {
    const index = this.orders.findIndex(order => order.orderId === orderId);
    if (index === -1) {
      return null;
    } else {
      const order = this.orders[index];
      this.orders.splice(index, 1);
      return order;
    }
  }

  findBy(property, value) {
    return this.orders.filter(order => order[property] === value);
  }

  findByCompanyName(name) {
    return this.findBy('companyName', name);
  }

  findByCompanyAddress(address) {
    this.findBy('companyAddress', address);
  }

  itemCounts() {
    const grouped = this.orders.reduce((memo, order) => {
      memo[order.orderedItem] = memo[order.orderedItem] || 0;
      memo[order.orderedItem] += 1;
      return memo;
    }, {})

    return Object
      .keys(grouped)
      .map(key => { return { orderedItem: key, count: grouped[key] } })
      .sort((a, b) => a.count - b.count)
      .reverse();
  }
};

I've added a generic findBy method that's used by other two findBy* methods. But more importantly, the remove method now modifies the list of orders, and returns the removed order object (or null), rather than return a new array.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.