2675. Array of Objects to Matrix
Problem Description
The task is to write a function that converts an array of objects arr
into a two-dimensional array, or matrix, m
. The input array, arr
, can contain objects that are nested, meaning that an object can have properties which are objects or arrays themselves. Additionally, these objects can have properties of different types including numbers, strings, booleans, and null values.
The first row of the resulting matrix m
should contain column names. For non-nested objects, these names are simply the keys of the properties in the objects. For nested objects, the column names should represent the path to the property, with each level of nesting separated by a period "."
.
Each subsequent row in the matrix corresponds to an object from the input array arr
. The values are placed in the matrix in alignment with the column names. If an object does not have a value for a specific column, that matrix cell should be an empty string ""
.
Finally, the columns are to be sorted in lexicographically ascending order, which means dictionary order, considering the column names as strings.
Intuition
The proposed solution involves two main steps: First, it flattens the structure of the input objects to easily map them into rows of the matrix. Second, it constructs the matrix using column names in lexicographically ascending order.
For the flattening part, a depth-first search (DFS) algorithm is employed. DFS traverses nested objects and arrays and builds a flat key-value representation of the underlying data where the keys are the paths leading to each value. The paths reflect the structure of the original object with nested keys joined by a period "."
.
Once flattened, the complete list of column names is derived from the keys of flattened objects. These keys are then deduplicated and sorted to form the first row of the matrix - the column header row.
The next part involves creating each row of the matrix corresponding to the input objects. For each object's flattened version, the values are filled in the appropriate columns, considering the sorted column names. If a value for a specific column is not found in the object, an empty string is inserted in that place.
By iterating through the entire array of objects and creating each row according to the gathered column names, the function builds the complete matrix, adhering to the requirements of the problem.
Solution Approach
The implementation of the solution can be broken down into several steps that reflect the thought process for handling the problem.
-
Depth-First Search to Flatten Objects: To navigate through nested objects, a DFS algorithm is used. DFS is ideal for exploring all the paths through nested data as it goes as deep as possible down one path before backing up and trying another. Here, the
dfs
function is recursively called with akey
(representing the current path) andobj
, the object or value currently being explored.-
It starts by checking if the
obj
is a leaf node, which in this context is a value that is not an object or an array – i.e., numbers, strings, booleans, or null. If it is, a single key-value pair is returned with the currentkey
and theobj
itself. -
If the
obj
is another object or array, it iterates over the object entries and recursively callsdfs
on each key-value pair, appending the current key to the new key for nested properties bynewKey = key ?
{k}:
${k};
, wherek
is the current object's key. The recursion ensures that all levels of nesting are accounted for in the final keys. -
It accumulates and flattens these results to produce a single key-value pair for each property, regardless of how deeply nested it is in the original object.
-
-
Creating a List of Unique Keys: After flattening each object into a collection of key-value pairs, all the keys are gathered into one array and then reduced to a unique set to eliminate duplicates. The unique keys are then sorted lexicographically using the
.sort()
method to define the column names for the matrix. -
Building the Matrix: With the list of keys prepared, the actual matrix construction begins. The sorted list of keys forms the first row of the matrix.
-
For each object (after flattening), the algorithm iterates over the list of sorted keys and checks if the object contains a value for each key. This is performed by searching the flattened objects for a property with that key. If the key is present, its value is inserted into the row; if not, an empty string
""
is used as a placeholder. -
The
find
function:row.find(r => r.hasOwnProperty(key))?.[key]
is used to retrieve the value for a given key from the set of key-value pairs obtained after flattening. -
The
push
method appends either the found value or an empty string to thenewRow
array, preparing a complete row of the matrix. -
Each
newRow
is then added to theans
array, which accumulatively builds up the final matrix.
-
-
Returning the Result: At the end of the iteration, the
ans
array contains the first row with the sorted column names and the subsequent rows representing objects' properties. The completed matrix is returned.
This approach efficiently handles the conversion of an array of potentially nested objects into a cleanly structured matrix, taking care to accommodate missing keys and ensuring the final columns are in the required lexicographic order.
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 walk through a small example to illustrate the solution approach. Suppose we have the following array of objects:
const arr = [ { name: 'Alice', age: 30, address: { city: 'Wonderland', zip: '12345' } }, { name: 'Bob', age: 22, contact: { email: '[email protected]', phone: '555-4132' } } ];
Following our solution approach, we'd perform these steps:
-
Depth-First Search to Flatten Objects:
We use DFS to flatten each object in
arr
. For Alice, we would obtain:{ 'name': 'Alice', 'age': 30, 'address.city': 'Wonderland', 'address.zip': '12345' }
For Bob, similarly:
{ 'name': 'Bob', 'age': 22, 'contact.email': '[email protected]', 'contact.phone': '555-4132' }
-
Creating a List of Unique Keys:
Collect all keys from the flattened objects:
['name', 'age', 'address.city', 'address.zip', 'contact.email', 'contact.phone']
Then, we remove duplicates and sort them lexicographically to get the column names for our matrix:
['address.city', 'address.zip', 'age', 'contact.email', 'contact.phone', 'name']
-
Building the Matrix:
With our sorted keys, we start building the matrix. The first row will be the column names:
[['address.city', 'address.zip', 'age', 'contact.email', 'contact.phone', 'name']]
Then, we add rows for Alice and Bob. Since Alice does not have contact info, and Bob does not have an address, we put empty strings for those values:
Alice's row:
['Wonderland', '12345', 30, '', '', 'Alice']
Bob's row:
['', '', 22, '[email protected]', '555-4132', 'Bob']
Adding these rows to our matrix results in:
[ ['address.city', 'address.zip', 'age', 'contact.email', 'contact.phone', 'name'], ['Wonderland', '12345', 30, '', '', 'Alice'], ['', '', 22, '[email protected]', '555-4132', 'Bob'] ]
-
Returning the Result:
Our final output is the completed matrix:
[ ['address.city', 'address.zip', 'age', 'contact.email', 'contact.phone', 'name'], ['Wonderland', '12345', 30, '', '', 'Alice'], ['', '', 22, '[email protected]', '555-4132', 'Bob'] ]
And this is how the solution approach breaks down a complex data structure into a structured matrix with sorted columns, dealing properly with nested objects and missing values.
Solution Implementation
1def json_to_matrix(json_array):
2 """
3 Converts JSON objects to a matrix representation. Each object's keys and values
4 are flattened into rows.
5 :param json_array: List of JSON objects
6 :return: Matrix (list of lists) with each row representing an object with flattened key-value pairs.
7 """
8
9 def flatten_object(prefix, value):
10 """
11 Helper function to flatten an object's keys and values.
12 Prefixes keys with their parent's keys for nesting.
13 :param prefix: String prefix for nested keys
14 :param value: The value to be processed, can be a dict or a primitive type
15 :return: Flattened representation of the object
16 """
17 # Directly return the value if it is primitive or null
18 if isinstance(value, (int, str, bool)) or value is None:
19 return {prefix: value}
20
21 # Initialize an accumulator for flattened entries
22 result = []
23 # Iterate over the object properties if it is a dictionary
24 if isinstance(value, dict):
25 for key, val in value.items():
26 # Create a new key with the parent key prefix (if available)
27 new_key = f'{prefix}.{key}' if prefix else key
28 # Recursively flatten the object and accumulate results
29 result.extend(flatten_object(new_key, val))
30 # Flatten the list of results before returning and handle non-dict iterables
31 elif hasattr(value, '__iter__') and not isinstance(value, str):
32 for item in value:
33 result.extend(flatten_object(prefix, item))
34 else:
35 result.append({prefix: value})
36
37 return result
38
39 # Map each JSON object in the input array to its flattened representation
40 flattened_entries = [flatten_object('', obj) for obj in json_array]
41
42 # Extract the unique keys across all flattened objects and sort them
43 unique_keys = sorted(set(
44 key for entry in flattened_entries for obj in entry for key in obj
45 ))
46
47 # Prepare the result matrix with the first row being the sorted keys
48 matrix = [unique_keys]
49
50 # Iterate over each flattened JSON object to create remaining matrix rows
51 for entry in flattened_entries:
52 matrix_row = []
53 # For each unique key, find the corresponding value in the current entry
54 for key in unique_keys:
55 # If a value for a key exists, push the value, otherwise push an empty string
56 found_entry = next((e for e in entry if key in e), None)
57 matrix_row.append(found_entry[key] if found_entry else '')
58 # Add the populated row to the matrix
59 matrix.append(matrix_row)
60
61 # Return the fully constructed matrix
62 return matrix
63
1import java.util.*;
2import java.util.stream.Collectors;
3
4public class JsonToMatrixConverter {
5
6 // Convert a JSON array to a matrix representation
7 public static List<List<Object>> jsonToMatrix(List<Map<String, Object>> jsonArray) {
8 // Map each JSON object to its flattened representation
9 List<Map<String, Object>> flattenedEntries = jsonArray.stream()
10 .map(obj -> flattenObject("", obj))
11 .collect(Collectors.toList());
12
13 // Extract the unique keys across all flattened objects and sort them
14 Set<String> uniqueKeysSet = new TreeSet<>();
15 for (Map<String, Object> entry : flattenedEntries) {
16 uniqueKeysSet.addAll(entry.keySet());
17 }
18 List<String> uniqueKeys = new ArrayList<>(uniqueKeysSet);
19
20 // Prepare the result matrix with the first row being the sorted keys
21 List<List<Object>> matrix = new ArrayList<>();
22 matrix.add(new ArrayList<>(uniqueKeys)); // Add the keys as the first row
23
24 // Iterate over each flattened JSON object to create the remaining matrix rows
25 for (Map<String, Object> entry : flattenedEntries) {
26 List<Object> matrixRow = new ArrayList<>();
27 // For each unique key, find the corresponding value in the current entry
28 for (String key : uniqueKeys) {
29 // If a value for a key exists, add the value, otherwise add an empty string
30 matrixRow.add(entry.getOrDefault(key, ""));
31 }
32 // Add the populated row to the matrix
33 matrix.add(matrixRow);
34 }
35
36 // Return the fully constructed matrix
37 return matrix;
38 }
39
40 // Helper function to flatten an object's keys and values with nested keys prefixed by their parent keys
41 private static Map<String, Object> flattenObject(String prefix, Object value) {
42 Map<String, Object> result = new HashMap<>(); // Initialize a map to hold the flattened results
43
44 // If the value is primitive or null, return it directly
45 if (value instanceof Number || value instanceof String || value instanceof Boolean || value == null) {
46 result.put(prefix, value);
47 return result;
48 }
49
50 // If the value is a Map, iterate over its entries
51 if (value instanceof Map) {
52 @SuppressWarnings("unchecked")
53 Map<String, Object> valueMap = (Map<String, Object>) value;
54 for (Map.Entry<String, Object> entry : valueMap.entrySet()) {
55 // For nested objects, create a new key with the parent key prefix
56 String newKey = prefix.isEmpty() ? entry.getKey() : prefix + "." + entry.getKey();
57 // Recursively flatten the object and merge the results into the 'result' map
58 result.putAll(flattenObject(newKey, entry.getValue()));
59 }
60 }
61
62 return result;
63 }
64
65 // Main method for testing
66 public static void main(String[] args) {
67 // Example JSON objects represented as maps
68 List<Map<String, Object>> exampleJsonArray = new ArrayList<>();
69 Map<String, Object> obj1 = new HashMap<>();
70 obj1.put("id", 1);
71 obj1.put("name", "John");
72 Map<String, Object> obj2 = new HashMap<>();
73 obj2.put("id", 2);
74 obj2.put("name", "Jane");
75 obj2.put("location", "NY");
76 exampleJsonArray.add(obj1);
77 exampleJsonArray.add(obj2);
78
79 // Convert the JSON array to a matrix
80 List<List<Object>> matrix = jsonToMatrix(exampleJsonArray);
81
82 // Print the matrix
83 for (List<Object> row : matrix) {
84 System.out.println(row);
85 }
86 }
87}
88
1#include <vector>
2#include <string>
3#include <map>
4#include <set>
5#include <nlohmann/json.hpp> // Assuming use of nlohmann::json library for JSON operations
6
7// Function to convert JSON objects to a matrix representation. Each object's keys and values are flattened into rows.
8std::vector<std::vector<std::variant<std::string, double, bool, std::nullptr_t>>> JsonToMatrix(const std::vector<nlohmann::json>& jsonArray) {
9 using json = nlohmann::json;
10 using Variant = std::variant<std::string, double, bool, std::nullptr_t>;
11
12 // Helper function to flatten an object's keys and values. Prefixes keys with their parent's keys for nesting.
13 std::function<std::map<std::string, Variant>(const std::string&, const json&)> FlattenObject = [&](const std::string& prefix, const json& value) -> std::map<std::string, Variant> {
14 std::map<std::string, Variant> resultMap;
15
16 if (value.is_primitive()) {
17 // Directly return the value if it is primitive or null.
18 return {{prefix, value}};
19 }
20 // Check if the value is an object and iterate over properties.
21 else if (value.is_object()) {
22 for (auto it = value.begin(); it != value.end(); ++it) {
23 // Create a new key with the parent key prefix (if available).
24 const std::string newKey = prefix.empty() ? it.key() : prefix + "." + it.key();
25 // Recursively flatten the object and insert results into the map.
26 auto nestedMap = FlattenObject(newKey, it.value());
27 resultMap.insert(nestedMap.begin(), nestedMap.end());
28 }
29 }
30
31 return resultMap;
32 };
33
34 // Create a set to store unique keys and a vector of maps for flattened JSON objects.
35 std::set<std::string> uniqueKeys;
36 std::vector<std::map<std::string, Variant>> flattenedEntries;
37
38 // Map each JSON object in the input array to its flattened representation.
39 for (const auto& obj : jsonArray) {
40 auto flattenedEntry = FlattenObject("", obj);
41 flattenedEntries.push_back(std::move(flattenedEntry));
42 for (const auto& pair : flattenedEntries.back()) {
43 uniqueKeys.insert(pair.first);
44 }
45 }
46
47 // Prepare the result matrix with the first row being the sorted keys.
48 std::vector<std::vector<Variant>> matrix;
49 std::vector<Variant> headerRow;
50 for (const auto& key : uniqueKeys) {
51 headerRow.push_back(key);
52 }
53 matrix.push_back(headerRow);
54
55 // Iterate over each flattened JSON object to create remaining matrix rows.
56 for (const auto& entry : flattenedEntries) {
57 std::vector<Variant> matrixRow;
58
59 // For each unique key, find the corresponding value in the current entry.
60 for (const auto& key : uniqueKeys) {
61 auto foundEntry = entry.find(key);
62 if (foundEntry != entry.end()) {
63 matrixRow.push_back(foundEntry->second);
64 } else {
65 matrixRow.push_back(""); // Use an empty string when the key is not found.
66 }
67 }
68 // Add the populated row to the matrix.
69 matrix.push_back(matrixRow);
70 }
71
72 // Return the fully constructed matrix.
73 return matrix;
74}
75// NOTE: To use std::variant and nlohmann::json library, C++17 and the JSON library need to be enabled and included respectively.
76
1// Function to convert JSON objects to a matrix representation. Each object's keys and values are flattened into rows.
2function jsonToMatrix(jsonArray: any[]): (string | number | boolean | null)[][] {
3 // Helper function to flatten an object's keys and values. Prefixes keys with their parent's keys for nesting.
4 const flattenObject = (prefix: string, value: any): { [key: string]: any } => {
5 // Directly return the value if it is primitive or null.
6 if (
7 typeof value === 'number' ||
8 typeof value === 'string' ||
9 typeof value === 'boolean' ||
10 value === null
11 ) {
12 return { [prefix]: value };
13 }
14 // Initialize an array to accumulate flattened entries.
15 const result: any[] = [];
16 // Iterate over the object properties.
17 for (const [key, val] of Object.entries(value)) {
18 // Create a new key with the parent key prefix (if available).
19 const newKey = prefix ? `${prefix}.${key}` : `${key}`;
20 // Recursively flatten the object and push results into the array.
21 result.push(flattenObject(newKey, val));
22 }
23 // Flatten the array of results before returning.
24 return result.flat();
25 };
26
27 // Map each JSON object in the input array to its flattened representation.
28 const flattenedEntries = jsonArray.map(obj => flattenObject('', obj));
29
30 // Extract the unique keys across all flattened objects and sort them.
31 const uniqueKeys = [...new Set(
32 flattenedEntries
33 .flat()
34 .map(obj => Object.keys(obj))
35 .flat()
36 )].sort();
37
38 // Prepare the result matrix with the first row being the sorted keys.
39 const matrix: (string | number | boolean | null)[][] = [uniqueKeys];
40
41 // Iterate over each flattened JSON object to create remaining matrix rows.
42 for (const entry of flattenedEntries) {
43 const matrixRow: (string | number | boolean | null)[] = [];
44 // For each unique key, find the corresponding value in the current entry.
45 for (const key of uniqueKeys) {
46 // If a value for a key exists, push the value, otherwise push an empty string.
47 const foundEntry = entry.find(e => e.hasOwnProperty(key));
48 matrixRow.push(foundEntry ? foundEntry[key] : '');
49 }
50 // Add the populated row to the matrix.
51 matrix.push(matrixRow);
52 }
53
54 // Return the fully constructed matrix.
55 return matrix;
56}
57
Time and Space Complexity
Time Complexity
To analyze the time complexity of the jsonToMatrix
function, let's examine the steps involved:
-
dfs
function - Thedfs
(Depth-First Search) function is a recursive function which is called for each property in the object.- For each property that is a primitive value (
number
,string
,boolean
, ornull
), it performs a constant amount of work. - For nested objects, it iterates through all keys and calls itself recursively.
- Assuming that the average depth of the objects is
d
and the average number of keys at each level isk
, the time complexity of thedfs
function is approximatelyO(k^d)
.
- For each property that is a primitive value (
-
arr.map
call - Thedfs
function is called once for each item in the input arrayarr
, with the length of the array beingn
, resulting in a time complexity ofO(n * k^d)
for this step. -
Generation of
keys
array - This involves flattening the array of key-value pairs and extracting keys. The flattening operation has a complexity ofO(n * k^d)
since it involves concatenating arrays returned by thedfs
function. The creation of theSet
to filter out unique keys isO(u)
whereu
is the number of unique keys, which is less than or equal ton * k^d
. -
The
ans
array - The nested loop involves iterating over each row and then each key. Assuming there arem
unique keys, the complexity of this step isO(n * m)
.
Combining all these steps, the overall time complexity will be dominated by the arr.map
call and the operations inside it. Therefore, the overall time complexity of the jsonToMatrix
function is O(n * k^d + n * m)
where:
n
is the length of the input arrayarr
,k
is the average number of keys in an object,d
is the average depth of nested objects,m
is the number of unique keys across all objects.
Space Complexity
The space complexity considers the memory used throughout the execution of the function:
-
Recursive
dfs
calls - Each recursive call creates a new execution context, and the maximum depth of the recursive call stack will bed
, so the space complexity due to recursion isO(d)
. -
kv
andans
arrays - These arrays are used to store intermediate and final results. Sinceans
includes a row for each input object plus one row for the keys, its size isO(n * m)
. Thekv
array contains the results of thedfs
function calls and has a similar size. -
keys
array - Storing the unique keys requires a space ofO(u)
, whereu
is the number of unique keys, which can be as much asO(n * k^d)
in the worst case.
The main contributing factor to space complexity is the output array ans
and the set of unique keys. Therefore, the overall space complexity of the jsonToMatrix
function is O(n * m + n * k^d + d)
. Simplified, assuming m
is bounded by n * k^d
, the final space complexity is O(n * k^d + d)
where:
d
is the average depth of nested objects,n
is the length of the input arrayarr
,k
is the average number of keys in an object.
What's the output of running the following function using input 56
?
1KEYBOARD = {
2 '2': 'abc',
3 '3': 'def',
4 '4': 'ghi',
5 '5': 'jkl',
6 '6': 'mno',
7 '7': 'pqrs',
8 '8': 'tuv',
9 '9': 'wxyz',
10}
11
12def letter_combinations_of_phone_number(digits):
13 def dfs(path, res):
14 if len(path) == len(digits):
15 res.append(''.join(path))
16 return
17
18 next_number = digits[len(path)]
19 for letter in KEYBOARD[next_number]:
20 path.append(letter)
21 dfs(path, res)
22 path.pop()
23
24 res = []
25 dfs([], res)
26 return res
27
1private static final Map<Character, char[]> KEYBOARD = Map.of(
2 '2', "abc".toCharArray(),
3 '3', "def".toCharArray(),
4 '4', "ghi".toCharArray(),
5 '5', "jkl".toCharArray(),
6 '6', "mno".toCharArray(),
7 '7', "pqrs".toCharArray(),
8 '8', "tuv".toCharArray(),
9 '9', "wxyz".toCharArray()
10);
11
12public static List<String> letterCombinationsOfPhoneNumber(String digits) {
13 List<String> res = new ArrayList<>();
14 dfs(new StringBuilder(), res, digits.toCharArray());
15 return res;
16}
17
18private static void dfs(StringBuilder path, List<String> res, char[] digits) {
19 if (path.length() == digits.length) {
20 res.add(path.toString());
21 return;
22 }
23 char next_digit = digits[path.length()];
24 for (char letter : KEYBOARD.get(next_digit)) {
25 path.append(letter);
26 dfs(path, res, digits);
27 path.deleteCharAt(path.length() - 1);
28 }
29}
30
1const KEYBOARD = {
2 '2': 'abc',
3 '3': 'def',
4 '4': 'ghi',
5 '5': 'jkl',
6 '6': 'mno',
7 '7': 'pqrs',
8 '8': 'tuv',
9 '9': 'wxyz',
10}
11
12function letter_combinations_of_phone_number(digits) {
13 let res = [];
14 dfs(digits, [], res);
15 return res;
16}
17
18function dfs(digits, path, res) {
19 if (path.length === digits.length) {
20 res.push(path.join(''));
21 return;
22 }
23 let next_number = digits.charAt(path.length);
24 for (let letter of KEYBOARD[next_number]) {
25 path.push(letter);
26 dfs(digits, path, res);
27 path.pop();
28 }
29}
30
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!