Top N Tickers by Traded Volume
Published:
Problem
There is a stream of trades. Each trade contains:
tickervolume
At any point, we need to print the top n tickers by total traded volume.
Implement:
execute_trade(ticker, volume)print_top_n_trades(n)
Idea
We need two operations:
- update one ticker’s total volume when a trade arrives
- query the current top
ntickers quickly
Use:
unordered_map<string, long long>to store the current total volume per tickerset<TradeVolume>to keep tickers sorted by volume
The main detail is updates:
- when a new trade for ticker
Tarrives, first remove its oldTradeVolumeentry from the set - update the total in the hash map
- insert the new
TradeVolumeentry into the set
To get highest volume first, use a custom comparator:
- larger total volume comes first
- if two tickers have the same volume, sort by ticker name
Complexity
execute_trade:O(log m)wheremis number of distinct tickersprint_top_n_trades(n):O(n)- space:
O(m)
Code
#include <iostream>
#include <set>
#include <string>
#include <unordered_map>
struct TradeVolume {
std::string ticker;
long long totalVolume;
};
struct TradeVolumeCompare {
bool operator()(const TradeVolume& left, const TradeVolume& right) const {
if (left.totalVolume != right.totalVolume) {
return left.totalVolume > right.totalVolume;
}
return left.ticker < right.ticker;
}
};
class TradeTracker {
public:
void executeTrade(const std::string& ticker, long long volume) {
auto volumeIt = totalVolumeByTicker.find(ticker);
if (volumeIt != totalVolumeByTicker.end()) {
orderedTrades.erase(TradeVolume{ticker, volumeIt->second});
volumeIt->second += volume;
orderedTrades.insert(TradeVolume{ticker, volumeIt->second});
return;
}
totalVolumeByTicker[ticker] = volume;
orderedTrades.insert(TradeVolume{ticker, volume});
}
void printTopNTrades(int n) const {
int printed = 0;
for (const auto& tradeVolume : orderedTrades) {
if (printed == n) {
break;
}
std::cout << tradeVolume.ticker << " " << tradeVolume.totalVolume << "\n";
++printed;
}
}
private:
std::unordered_map<std::string, long long> totalVolumeByTicker;
std::set<TradeVolume, TradeVolumeCompare> orderedTrades;
};
int main() {
TradeTracker tracker;
tracker.executeTrade("AAPL", 100);
tracker.executeTrade("MSFT", 200);
tracker.executeTrade("GOOG", 150);
tracker.executeTrade("AAPL", 75);
tracker.executeTrade("TSLA", 300);
tracker.printTopNTrades(3);
return 0;
}Dry Run
After these trades:
AAPL 100MSFT 200GOOG 150AAPL 75TSLA 300
The total traded volumes become:
TSLA = 300AAPL = 175MSFT = 200GOOG = 150
So print_top_n_trades(3) prints:
TSLA 300
MSFT 200
AAPL 175Why Not Just Use a Heap?
A heap is good for retrieving the maximum, but updates are awkward when the same ticker appears many times.
If we use only a heap:
- every new trade pushes a new value
- old values become stale
- top queries need lazy deletion logic
That works, but for this problem a hash map + set is cleaner because each ticker appears only once in the ordered structure.
Also, using a comparator is usually clearer than storing negative volume because the ordering rule is explicit in the code.
Edge Cases
- If
nis larger than the number of tickers, print all available tickers. - If the same ticker appears repeatedly, keep adding to its total volume.
- Use
long longfor total volume to avoid overflow when the stream is large.
Heap + Hash Map Approach
This is another good solution, and it is often easier to explain across languages like Python, Java, and TypeScript.
Use:
unordered_map<string, long long>for the latest total volume of each tickerpriority_queue<TradeVolume>as a max-heap
Idea:
- every
executeTradeupdates the hash map - then push the new
TradeVolumeinto the heap - the heap may now contain stale entries for the same ticker
- while printing top
n, discard stale heap entries until the top matches the latest value in the hash map
This is called lazy deletion.
Code
#include <iostream>
#include <functional>
#include <queue>
#include <string>
#include <unordered_map>
#include <vector>
struct TradeVolume {
std::string ticker;
long long totalVolume;
};
struct MaxHeapTradeVolumeCompare {
bool operator()(const TradeVolume& left, const TradeVolume& right) const {
if (left.totalVolume != right.totalVolume) {
return left.totalVolume < right.totalVolume;
}
return left.ticker > right.ticker;
}
};
class TradeTrackerHeap {
public:
void executeTrade(const std::string& ticker, long long volume) {
totalVolumeByTicker[ticker] += volume;
maxHeap.push(TradeVolume{ticker, totalVolumeByTicker[ticker]});
}
void printTopNTrades(int n) {
std::vector<TradeVolume> validTradesToRestore;
int printed = 0;
while (!maxHeap.empty() && printed < n) {
TradeVolume currentTrade = maxHeap.top();
maxHeap.pop();
auto volumeIt = totalVolumeByTicker.find(currentTrade.ticker);
// The heap can contain older snapshots for the same ticker.
// Only the entry matching the latest total in the hash map is valid.
if (volumeIt == totalVolumeByTicker.end() ||
volumeIt->second != currentTrade.totalVolume) {
continue;
}
std::cout << currentTrade.ticker << " " << currentTrade.totalVolume << "\n";
validTradesToRestore.push_back(currentTrade);
++printed;
}
for (const auto& tradeVolume : validTradesToRestore) {
maxHeap.push(tradeVolume);
}
}
private:
std::unordered_map<std::string, long long> totalVolumeByTicker;
std::priority_queue<TradeVolume,
std::vector<TradeVolume>,
MaxHeapTradeVolumeCompare>
maxHeap;
};Why Stale Entries Happen
Suppose:
AAPL 100AAPL 50
After these trades:
- hash map says
AAPL = 150 - heap contains both
TradeVolume{"AAPL", 100}andTradeVolume{"AAPL", 150}
When we query:
(150, AAPL)is valid(100, AAPL)is stale and must be ignored
That is what this code is checking:
auto volumeIt = totalVolumeByTicker.find(currentTrade.ticker);
if (volumeIt == totalVolumeByTicker.end() ||
volumeIt->second != currentTrade.totalVolume) {
continue;
}How to read it:
- look up the latest total volume for
currentTrade.tickerin the hash map - compare that latest value with the volume stored in the heap entry
- if they are different, the heap entry is an older snapshot, so skip it
Example:
- after
executeTrade("AAPL", 100), heap hasTradeVolume{"AAPL", 100} - after
executeTrade("AAPL", 50), hash map saysAAPL = 150, and heap also getsTradeVolume{"AAPL", 150} - if the heap returns
TradeVolume{"AAPL", 100}, the map says the latest total is150 - since
100 != 150, this entry is stale and should be ignored - if the heap returns
TradeVolume{"AAPL", 150}, it matches the map and is valid
So the rule is simple:
- heap entry = candidate
- hash map = source of truth
- only print the heap entry if both match
Complexity
execute_trade:O(log k)wherekis the number of heap entriesprint_top_n_trades(n): not strictlyO(n)because stale entries may need to be popped- space: larger than the
setapproach because the heap keeps old entries
Set vs Heap
hash map + setkeeps only one ordered entry per ticker and gives cleaner top-nquerieshash map + heapis more portable across languages, but needs lazy deletion- for C++, the
setapproach is cleaner - for interview discussion across multiple languages, the heap version is often easier to generalize
Leave a Comment