2724. Sort By
Problem Description
You are given an array arr
and a function fn
. Your task is to return a new sorted array sortedArr
where the elements are sorted based on the numeric values returned by applying fn
to each element.
The sorting rules are:
- Apply the function
fn
to each element in the array to get a numeric value - Sort the array elements in ascending order based on these numeric values
- If
fn(a)
returns a smaller number thanfn(b)
, thena
should come beforeb
in the sorted array
Key constraints:
- The function
fn
only returns numbers - The function
fn
will never return duplicate numbers for different elements in the same array (each element maps to a unique number)
For example, if you have an array [5, 4, 1, 2, 3]
and a function fn = (x) => x
, the sorted array would be [1, 2, 3, 4, 5]
. If the function was fn = (x) => -x
, the sorted array would be [5, 4, 3, 2, 1]
because the negative values would reverse the order.
The solution uses JavaScript's built-in sort()
method with a custom comparator function (a, b) => fn(a) - fn(b)
to achieve the ascending order sorting based on the function's output values.
Intuition
The key insight is that we need to sort elements not by their own values, but by the values that the function fn
produces when applied to them. This is a classic use case for custom sorting.
When we think about sorting, we typically compare two elements directly. But here, we need an intermediate step: first transform each element using fn
, then compare those transformed values.
JavaScript's sort()
method accepts a comparator function that determines the order of elements. The comparator takes two elements a
and b
and returns:
- A negative number if
a
should come beforeb
- A positive number if
b
should come beforea
- Zero if they're equal
Since we want ascending order based on fn
's output, we can directly use fn(a) - fn(b)
as our comparator:
- If
fn(a) < fn(b)
, the result is negative, placinga
beforeb
- If
fn(a) > fn(b)
, the result is positive, placingb
beforea
This elegantly handles the transformation and comparison in a single expression. The guarantee that fn
never produces duplicate values for a given array means we don't need to worry about handling ties or maintaining stable sort order for equal values.
The beauty of this approach is its simplicity - we let the built-in sort method handle all the complexity of the sorting algorithm itself, while we just provide the logic for comparing elements based on their transformed values.
Solution Approach
The implementation uses JavaScript's built-in Array.sort()
method with a custom comparator function. Let's walk through how this works:
Step 1: Understanding the Sort Method
The sort()
method modifies the array in-place and accepts a comparator function that defines the sorting logic. The comparator receives two elements at a time and determines their relative order.
Step 2: Creating the Comparator Function
The comparator (a, b) => fn(a) - fn(b)
works as follows:
- Takes two elements
a
andb
from the array - Applies the transformation function
fn
to both elements - Returns the difference
fn(a) - fn(b)
Step 3: How the Sorting Works
When sort()
executes with our comparator:
- For any two elements being compared, it calls
fn(a)
andfn(b)
to get their numeric values - The subtraction
fn(a) - fn(b)
produces:- A negative value when
fn(a) < fn(b)
→a
is placed beforeb
- A positive value when
fn(a) > fn(b)
→b
is placed beforea
- Zero when
fn(a) === fn(b)
→ order remains unchanged (though this won't happen per problem constraints)
- A negative value when
Example Walkthrough:
If arr = [3, 1, 2]
and fn = (x) => x * x
:
- Comparing 3 and 1:
fn(3) - fn(1) = 9 - 1 = 8
(positive, so 1 comes before 3) - Comparing 1 and 2:
fn(1) - fn(2) = 1 - 4 = -3
(negative, so 1 comes before 2) - Comparing 3 and 2:
fn(3) - fn(2) = 9 - 4 = 5
(positive, so 2 comes before 3) - Result:
[1, 2, 3]
(sorted by square values: 1, 4, 9)
The solution is efficient and concise, leveraging the language's built-in sorting capabilities while providing custom comparison logic through the function composition.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to understand how the solution works.
Given:
arr = [3, -2, 1, -4]
fn = (x) => x * x
(squares each number)
Step 1: Understand what fn does to each element
fn(3) = 9
fn(-2) = 4
fn(1) = 1
fn(-4) = 16
Step 2: Apply the sort with comparator (a, b) => fn(a) - fn(b)
The sort method will compare pairs of elements. Let's trace through some comparisons:
First comparison: 3 and -2
fn(3) - fn(-2) = 9 - 4 = 5
(positive)- Since the result is positive, -2 should come before 3
Second comparison: -2 and 1
fn(-2) - fn(1) = 4 - 1 = 3
(positive)- Since the result is positive, 1 should come before -2
Third comparison: 1 and -4
fn(1) - fn(-4) = 1 - 16 = -15
(negative)- Since the result is negative, 1 should come before -4
Step 3: Build the final sorted order
Based on the squared values:
1
has the smallest squared value (1)-2
has the next smallest squared value (4)3
has the third smallest squared value (9)-4
has the largest squared value (16)
Final Result: [1, -2, 3, -4]
This demonstrates how elements are sorted not by their original values, but by the values produced when fn
is applied to them. The comparator fn(a) - fn(b)
ensures ascending order based on the transformed values.
Solution Implementation
1def sortBy(arr, fn):
2 """
3 Sorts an array of elements based on a numeric value returned by the provided function
4
5 Args:
6 arr: The array to be sorted
7 fn: A function that takes an element and returns a numeric value for comparison
8
9 Returns:
10 A new sorted array in ascending order based on the function's return values
11 """
12 # Create a copy of the array to avoid modifying the original
13 # Use the key parameter in sorted() to specify the comparison function
14 # The sorted() function automatically sorts in ascending order based on fn's return values
15 return sorted(arr, key=fn)
16
1import java.util.Arrays;
2import java.util.Comparator;
3import java.util.function.Function;
4
5/**
6 * Sorts an array of elements based on a numeric value returned by the provided function
7 * @param <T> The type of elements in the array
8 * @param arr The array to be sorted
9 * @param fn A function that takes an element and returns a numeric value for comparison
10 * @return A new sorted array in ascending order based on the function's return values
11 */
12public static <T> T[] sortBy(T[] arr, Function<T, Double> fn) {
13 // Create a copy of the array to avoid modifying the original
14 T[] sortedArray = Arrays.copyOf(arr, arr.length);
15
16 // Sort the copied array using a custom comparator
17 // The comparator compares elements by comparing the numeric values returned by fn
18 // Elements are sorted in ascending order based on these values
19 Arrays.sort(sortedArray, new Comparator<T>() {
20 @Override
21 public int compare(T a, T b) {
22 // Get the numeric values for both elements
23 Double valueA = fn.apply(a);
24 Double valueB = fn.apply(b);
25 // Compare the values (ascending order)
26 return valueA.compareTo(valueB);
27 }
28 });
29
30 return sortedArray;
31}
32
1#include <vector>
2#include <algorithm>
3#include <functional>
4
5/**
6 * Sorts an array of elements based on a numeric value returned by the provided function
7 * @param arr - The array to be sorted
8 * @param fn - A function that takes an element and returns a numeric value for comparison
9 * @returns A new sorted array in ascending order based on the function's return values
10 */
11template<typename T>
12std::vector<T> sortBy(const std::vector<T>& arr, std::function<double(const T&)> fn) {
13 // Create a copy of the input array to avoid modifying the original
14 std::vector<T> sorted_arr = arr;
15
16 // Sort the copied array using a custom comparator
17 // The comparator compares elements by evaluating fn on each element
18 // Returns true if fn(a) < fn(b), which places a before b in ascending order
19 std::sort(sorted_arr.begin(), sorted_arr.end(),
20 [&fn](const T& a, const T& b) {
21 return fn(a) < fn(b);
22 });
23
24 // Return the sorted array
25 return sorted_arr;
26}
27
1/**
2 * Sorts an array of elements based on a numeric value returned by the provided function
3 * @param arr - The array to be sorted
4 * @param fn - A function that takes an element and returns a numeric value for comparison
5 * @returns A new sorted array in ascending order based on the function's return values
6 */
7function sortBy<T>(arr: T[], fn: (item: T) => number): T[] {
8 // Create a copy of the array and sort it using the comparison function
9 // The sort compares elements by subtracting fn(b) from fn(a)
10 // Negative result means a comes before b, positive means b comes before a
11 return arr.sort((a: T, b: T) => fn(a) - fn(b));
12}
13
Time and Space Complexity
Time Complexity: O(n log n)
The sortBy
function uses JavaScript's built-in sort()
method with a custom comparator function. The time complexity of the sort operation depends on the underlying sorting algorithm implementation, which in most JavaScript engines (like V8) is typically Timsort or a variation of quicksort. These algorithms have an average and worst-case time complexity of O(n log n)
, where n
is the number of elements in the array.
Additionally, the comparator function fn
is called O(n log n)
times during the sorting process. If we assume fn
has a time complexity of O(f)
, the overall time complexity becomes O(n log n × f)
. When fn
is O(1)
(constant time), the total complexity remains O(n log n)
.
Space Complexity: O(n)
The space complexity depends on the sorting algorithm implementation:
- The
sort()
method in JavaScript typically usesO(log n)
auxiliary space for the recursive call stack in algorithms like quicksort or Timsort - However, since
sort()
may create temporary arrays during the sorting process (especially in Timsort for merging), the worst-case space complexity can beO(n)
- The function returns the sorted array in-place (modifying the original array), so no additional
O(n)
space is needed for the output
Therefore, the overall space complexity is O(n)
in the worst case, though it could be O(log n)
in practice depending on the specific implementation and input characteristics.
Common Pitfalls
1. Modifying the Original Array Instead of Creating a New One
A frequent mistake is using JavaScript's arr.sort()
directly, which modifies the original array in-place rather than creating a new sorted array as required by the problem.
Incorrect approach:
function sortBy(arr, fn) {
return arr.sort((a, b) => fn(a) - fn(b)); // Modifies original array!
}
Correct approach:
function sortBy(arr, fn) {
return [...arr].sort((a, b) => fn(a) - fn(b)); // Creates a copy first
}
// or
function sortBy(arr, fn) {
return arr.slice().sort((a, b) => fn(a) - fn(b)); // Also creates a copy
}
2. Caching Function Results for Performance
When fn
is computationally expensive, the sort comparator calls it multiple times for the same elements during sorting (approximately O(n log n) times per element). This can lead to performance issues.
Inefficient approach:
function sortBy(arr, fn) {
return [...arr].sort((a, b) => fn(a) - fn(b)); // fn called multiple times per element
}
Optimized approach using Schwartzian Transform:
function sortBy(arr, fn) {
return arr
.map(item => [item, fn(item)]) // Cache fn results
.sort((a, b) => a[1] - b[1]) // Sort by cached values
.map(pair => pair[0]); // Extract original elements
}
3. Handling Non-Numeric Return Values
While the problem states fn
only returns numbers, defensive programming suggests validating this assumption to prevent unexpected behavior.
Vulnerable approach:
function sortBy(arr, fn) {
return [...arr].sort((a, b) => fn(a) - fn(b)); // Assumes fn always returns numbers
}
Defensive approach:
function sortBy(arr, fn) {
return [...arr].sort((a, b) => {
const valA = fn(a);
const valB = fn(b);
if (typeof valA !== 'number' || typeof valB !== 'number') {
throw new Error('Function must return numeric values');
}
return valA - valB;
});
}
4. Language-Specific Sort Stability Concerns
Different programming languages handle stable sorting differently. While modern JavaScript (ES2019+) guarantees stable sorting, older versions don't. Python's sorted()
is always stable, but this distinction matters when migrating code between languages or dealing with legacy environments.
Cross-language consideration:
- Python's
sorted(arr, key=fn)
maintains relative order for equal elements - Pre-ES2019 JavaScript might not preserve original order for equal
fn
values - Though the problem states
fn
returns unique values, this becomes important when requirements change
What's the output of running the following function using the following tree as input?
1def serialize(root):
2 res = []
3 def dfs(root):
4 if not root:
5 res.append('x')
6 return
7 res.append(root.val)
8 dfs(root.left)
9 dfs(root.right)
10 dfs(root)
11 return ' '.join(res)
12
1import java.util.StringJoiner;
2
3public static String serialize(Node root) {
4 StringJoiner res = new StringJoiner(" ");
5 serializeDFS(root, res);
6 return res.toString();
7}
8
9private static void serializeDFS(Node root, StringJoiner result) {
10 if (root == null) {
11 result.add("x");
12 return;
13 }
14 result.add(Integer.toString(root.val));
15 serializeDFS(root.left, result);
16 serializeDFS(root.right, result);
17}
18
1function serialize(root) {
2 let res = [];
3 serialize_dfs(root, res);
4 return res.join(" ");
5}
6
7function serialize_dfs(root, res) {
8 if (!root) {
9 res.push("x");
10 return;
11 }
12 res.push(root.val);
13 serialize_dfs(root.left, res);
14 serialize_dfs(root.right, res);
15}
16
Recommended Readings
Coding Interview 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
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 assets algo monster recursion jpg You first call Ben and ask
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!