2694. Event Emitter
Problem Description
The problem requires designing an EventEmitter
class that mimics the basic behavior of event emitter systems found in many programming environments such as Node.js or the web's Event Target interface.
An EventEmitter
is essentially a mechanism that allows different parts of an application to communicate with each other by emitting events to which other parts have subscribed.
Two core functionalities are required:
- Subscribing to events: The
subscribe
method should allow clients to register a callback function that is to be executed when a particular event is emitted. Each event can have multiple subscribers, and upon the event being emitted, the callbacks should be executed in the order they were added. - Emitting events: The
emit
method should trigger all the callbacks associated with the event name it receives as an argument. If the event has associated arguments, they should be passed to the callback functions.
Additionally, when a client subscribes to an event, a subscription object with an unsubscribe
method should be returned. This method, when called, will remove the callback from the event's list of subscribers.
Intuition
To achieve the functionality described, the EventEmitter
class must maintain a data structure that can keep track of all the callbacks associated with each event. To do this in an efficient and orderly way, a Map
can be used where the key is the event name, and the value is a Set
of callback functions. A Set
is chosen because it inherently avoids duplicate entries, which is desirable as it's stated that no callbacks passed to subscribe
are referentially identical.
For the subscribe
method, we implement the following steps:
- Retrieve the set of callbacks for the event from the map, or create a new set if it doesn't exist.
- Add the callback to the set.
- Return an object with an
unsubscribe
method that, when invoked, will delete the callback from the set of callbacks associated with the event.
For the emit
method, we need to:
- Retrieve the set of callbacks for the given event name.
- If the event has no callbacks, we return an empty array as specified.
- Iterate over the set of callbacks, executing each with the provided arguments, and collect the results.
- Return the array with the results from all the executed callbacks.
This implementation ensures that all the requirements for the EventEmitter
class are fulfilled, allowing for flexible event-driven programming patterns.
Solution Approach
The EventEmitter
class is implemented using TypeScript which provides type-safety over JavaScript. Here's an approach to how the solution is crafted:
Data Structures
JavaScript's Map
and Set
are used to implement the subscription mechanism.
- A
Map
is used to associate each event with a set of callbacks (Set<Callback>
). This allows efficient retrieval and management of callbacks per event. Set
is used to store callbacks for each event to ensure each callback is unique.
Implementation Details
subscribe Method
- When a subscriber calls
subscribe
, it passes theeventName
and thecallback
. - The method retrieves the callbacks set associated with
eventName
usingthis.d.get(eventName)
, or initializes a newSet
if none exists. - It adds the
callback
to the set with.add(callback)
. - The method returns an object containing an
unsubscribe
method. Inside this method, it removes thecallback
from the callbacks set when called.
The use of a Set
here ensures that the order of callbacks will be preserved, and duplicates are prevented without extra logic.
emit Method
- This method takes
eventName
and optionally an array ofargs
. - It fetches the set of callbacks associated with
eventName
. If none are found, it returns an empty array. - It uses the spread syntax
[...callbacks]
to convert the set into an array that can be iterated over. map
is used to invoke each callback with the providedargs
, collecting the results in an array that is returned.
The spread and map operations ensure that callbacks are called in the order they were added.
To summarize, the algorithm for this solution is straightforward: use Map
for event-to-callback(s) association, and Set
for maintaining callbacks. Subscribing adds the callback to the set, and emitting iterates over and invokes these callbacks, while unsubscribing removes a callback from the set.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's demonstrate the solution approach with a small example. Consider we want our EventEmitter
to handle 'data' and 'error' events.
First, we create an instance of EventEmitter
.
let emitter = new EventEmitter();
Now, let's subscribe to the 'data' event with two different callbacks.
let dataSubscription1 = emitter.subscribe('data', (data) => `Received ${data}`);
let dataSubscription2 = emitter.subscribe('data', (data) => `Processed ${data}`);
According to the solution approach, the subscribe
method will add each callback to a set associated with the 'data' event in the emitter's internal map. Each subscription returns an object with an unsubscribe
method.
Next, we will emit the 'data' event with a piece of data, let's say 'example data'
.
let result = emitter.emit('data', 'example data');
When the emit
method is called, it will look up the associated set of callbacks for the 'data' event and invoke each callback in the order they were added, passing 'example data'
to them. The result will be an array of callback outputs:
console.log(result); // Output: ["Received example data", "Processed example data"]
Finally, let's demonstrate unsubscribing from an event. We decide to unsubscribe dataSubscription1
.
dataSubscription1.unsubscribe();
Now, if we emit the 'data' event again:
result = emitter.emit('data', 'new example data');
This time, the output will only reflect the remaining subscription:
console.log(result); // Output: ["Processed new example data"]
The unsubscribe
method removed dataSubscription1
from the emitter's callback set, so invoking the 'data' event no longer triggers the first callback.
This example has shown how the EventEmitter class can subscribe to events with callbacks, emit events which trigger those callbacks, and unsubscribe from events to remove callbacks—all following the explained solution approach using JavaScript's Map
and Set
.
Solution Implementation
1# Global mapping from event names to a set of callbacks
2event_callbacks_map = {}
3
4# Type Alias for callback function
5Callback = callable
6
7# Subscription object returned by subscribe function
8class Subscription:
9 def __init__(self, event_name, callback):
10 self.event_name = event_name
11 self.callback = callback
12
13 def unsubscribe(self):
14 """Unsubscribe the callback from the event"""
15 if self.event_name in event_callbacks_map:
16 event_callbacks_map[self.event_name].discard(self.callback)
17
18def subscribe(event_name: str, callback: Callback) -> Subscription:
19 """
20 Subscribes to a given event with a callback and returns a subscription object.
21
22 :param event_name: The name of the event to subscribe to.
23 :param callback: The callback to invoke when the event is emitted.
24 :return: A Subscription object with an unsubscribe method to cancel the subscription.
25 """
26 # Use setdefault to initialize a set if the event name is not already present
27 event_callbacks_map.setdefault(event_name, set()).add(callback)
28
29 # Return a new Subscription object
30 return Subscription(event_name, callback)
31
32def emit(event_name: str, args: list = None) -> list:
33 """
34 Emits a given event by invoking all subscribed callbacks with the provided arguments.
35
36 :param event_name: The name of the event to emit.
37 :param args: A list of arguments to pass to the callbacks.
38 :return: A list containing the results of each callback invocation.
39 """
40 # Default args to an empty list if not provided
41 if args is None:
42 args = []
43
44 # Get the set of callbacks for the event name
45 callbacks = event_callbacks_map.get(event_name, [])
46
47 # Invoke all callbacks and collect the results
48 return [callback(*args) for callback in callbacks]
49
50# Example usage:
51
52# Subscribes to the 'onClick' event with on_click_callback
53def on_click_callback():
54 """Example callback function"""
55 return 99
56
57# Subscribe to the 'onClick' event
58subscription = subscribe('onClick', on_click_callback)
59
60# Emit the 'onClick' event and print the result
61print(emit('onClick')) # [99]
62
63# Unsubscribe from the 'onClick' event
64subscription.unsubscribe()
65
66# Attempt to emit the 'onClick' event again and print the result
67print(emit('onClick')) # []
68
1import java.util.*;
2
3// Functional interface to represent a generic Callback
4@FunctionalInterface
5interface Callback {
6 Object call(Object... args);
7}
8
9// Subscription interface to represent the contract for an unsubscribe action
10interface Subscription {
11 void unsubscribe();
12}
13
14// EventManager class to handle subscribing and emitting events
15public class EventManager {
16 // Global mapping from event names to a set of Callbacks
17 private static Map<String, Set<Callback>> eventCallbacksMap = new HashMap<>();
18
19 // Subscribes to a given event with a callback and returns a subscription object
20 public static Subscription subscribe(String eventName, Callback callback) {
21 Set<Callback> callbacks = eventCallbacksMap.computeIfAbsent(eventName, k -> new HashSet<>());
22 callbacks.add(callback);
23
24 return () -> callbacks.remove(callback);
25 }
26
27 // Emits a given event by invoking all subscribed callbacks with the provided arguments
28 public static List<Object> emit(String eventName, Object... args) {
29 Set<Callback> callbacks = eventCallbacksMap.get(eventName);
30 if (callbacks == null) {
31 return Collections.emptyList();
32 }
33 List<Object> results = new ArrayList<>();
34 for (Callback callback : callbacks) {
35 results.add(callback.call(args));
36 }
37 return results;
38 }
39}
40
41// Example usage
42
43public class Example {
44 public static void main(String[] args) {
45 // Subscribes to the 'onClick' event with onClickCallback
46 Callback onClickCallback = () -> 99;
47 Subscription subscription = EventManager.subscribe("onClick", onClickCallback);
48
49 // Emits the 'onClick' event and logs the result
50 System.out.println(EventManager.emit("onClick")); // Output: [99]
51
52 // Unsubscribes from the 'onClick' event
53 subscription.unsubscribe();
54
55 // Attempts to emit the 'onClick' event again and logs the result
56 System.out.println(EventManager.emit("onClick")); // Output: []
57 }
58}
59
1#include <iostream>
2#include <string>
3#include <unordered_map>
4#include <unordered_set>
5#include <functional>
6#include <vector>
7
8// Alias for a function that takes no arguments and returns an int.
9using Callback = std::function<int()>;
10
11// Struct representing a subscription with an unsubscribe method.
12struct Subscription {
13 std::string eventName;
14 Callback callback;
15
16 void unsubscribe(std::unordered_map<std::string, std::unordered_set<Callback>>& eventCallbacksMap) {
17 // Deletes the callback from the set of callbacks for the given event.
18 if(eventCallbacksMap.count(eventName) > 0) {
19 eventCallbacksMap[eventName].erase(callback);
20 }
21 }
22};
23
24// Global mapping from event names to a set of Callbacks.
25std::unordered_map<std::string, std::unordered_set<Callback>> eventCallbacksMap;
26
27/**
28 * @brief Subscribes to a given event with a callback and returns a subscription object.
29 *
30 * @param eventName The name of the event to subscribe to.
31 * @param callback The callback to invoke when the event is emitted.
32 * @return Subscription An object with an unsubscribe method to cancel the subscription.
33 */
34Subscription subscribe(const std::string& eventName, const Callback& callback) {
35 // Adds the callback to the set for the given event.
36 eventCallbacksMap[eventName].emplace(callback);
37
38 // Creates a subscription object for the caller to retain and use for unsubscribing.
39 Subscription subscription{eventName, callback};
40 return subscription;
41}
42
43/**
44 * @brief Emits a given event by invoking all subscribed callbacks.
45 *
46 * @param eventName The name of the event to emit.
47 * @return std::vector<int> A vector containing the results of each callback invocation.
48 */
49std::vector<int> emit(const std::string& eventName) {
50 std::vector<int> results;
51 auto callbacksIt = eventCallbacksMap.find(eventName);
52
53 // If no callbacks are registered for this event, return an empty vector.
54 if (callbacksIt == eventCallbacksMap.end()) {
55 return results;
56 }
57
58 // Invokes each callback associated with the event and stores the result in the vector.
59 for (const auto& callback : callbacksIt->second) {
60 results.push_back(callback());
61 }
62
63 return results;
64}
65
66// Example usage:
67int main() {
68 // Subscribes to the 'onClick' event with onClickCallback
69 auto onClickCallback = []() -> int {
70 return 99;
71 };
72 Subscription subscription = subscribe("onClick", onClickCallback);
73
74 // Emits the 'onClick' event and prints the result.
75 for (int result : emit("onClick")) {
76 std::cout << result << std::endl; // Should print: 99
77 }
78
79 // Unsubscribes from the 'onClick' event.
80 subscription.unsubscribe(eventCallbacksMap);
81
82 // Attempts to emit the 'onClick' event again and prints the result.
83 for (int result : emit("onClick")) {
84 std::cout << result << std::endl; // Should not print anything.
85 }
86
87 return 0;
88}
89
1type Callback = (...args: any[]) => any;
2type Subscription = {
3 unsubscribe: () => void;
4};
5
6// Global mapping from event names to a set of Callbacks
7const eventCallbacksMap: Map<string, Set<Callback>> = new Map();
8
9/**
10 * Subscribes to a given event with a callback and returns a subscription object.
11 * @param eventName - The name of the event to subscribe to.
12 * @param callback - The callback to invoke when the event is emitted.
13 * @returns An object with an unsubscribe method to cancel the subscription.
14 */
15function subscribe(eventName: string, callback: Callback): Subscription {
16 const callbacks = eventCallbacksMap.get(eventName) || new Set<Callback>();
17 callbacks.add(callback);
18 eventCallbacksMap.set(eventName, callbacks);
19
20 return {
21 unsubscribe: () => {
22 eventCallbacksMap.get(eventName)?.delete(callback);
23 }
24 };
25}
26
27/**
28 * Emits a given event by invoking all subscribed callbacks with the provided arguments.
29 * @param eventName - The name of the event to emit.
30 * @param args - An array of arguments to pass to the callbacks.
31 * @returns An array containing the results of each callback invocation.
32 */
33function emit(eventName: string, args: any[] = []): any[] {
34 const callbacks = eventCallbacksMap.get(eventName);
35 if (!callbacks) {
36 return [];
37 }
38 return Array.from(callbacks).map(callback => callback(...args));
39}
40
41// Example usage:
42
43// Subscribes to the 'onClick' event with onClickCallback
44function onClickCallback() {
45 return 99;
46}
47const subscription = subscribe('onClick', onClickCallback);
48
49// Emits the 'onClick' event and logs the result
50console.log(emit('onClick')); // [99]
51
52// Unsubscribes from the 'onClick' event
53subscription.unsubscribe();
54
55// Attempts to emit the 'onClick' event again and logs the result
56console.log(emit('onClick')); // []
57
Time and Space Complexity
Time Complexity
-
subscribe(eventName: string, callback: Callback): Subscription
- The time complexity for subscribing is
O(1)
since adding an event to aMap
and adding a callback to aSet
are both constant time operations.
- The time complexity for subscribing is
-
unsubscribe()
- The time complexity for unsubscribing is
O(1)
because it involves a single call todelete
on aSet
, which is a constant time operation.
- The time complexity for unsubscribing is
-
emit(eventName: string, args: any[] = []): any
- The time complexity for emitting an event depends on the number of callbacks subscribed to the event. If
n
is the number of callbacks, the complexity isO(n)
because it maps each callback and applies it to the arguments.
- The time complexity for emitting an event depends on the number of callbacks subscribed to the event. If
Space Complexity
- Overall space complexity for the
EventEmitter
class would beO(m * n)
wherem
is the number of unique events andn
is the average number of subscribers per event since aSet
of callbacks is stored for each event.
Additionally, the space complexity for emitting an event (emit
) is also worth noting:
emit(eventName: string, args: any[] = []): any
- The space complexity for emitting is
O(n)
because it creates an array of the return values from each callback, wheren
is the number of callbacks.
- The space complexity for emitting is
Do note that this analysis assumes the size of the event names and the footprint of each callback function are not significant compared to the numbers of events and subscribers, which is a common assumption in complexity analysis.
What's the relationship between a tree and a graph?
Recommended Readings
LeetCode Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way we
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https algomonster s3 us east 2 amazonaws com recursion jpg You first
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!