Leetcode 937. Reorder Data in Log Files

Problem Explanation

You have an array of logs where each log is a string separated by a space. The first word in each log is an alphanumeric identifier, and there are two types of logs:

  1. letter-logs: The words following the identifier contain only lowercase letters.
  2. digit-logs: The words following the identifier contain only digits.

Your task is to reorder the logs so that all letter-logs appear before any digit-log. The letter-logs should be ordered lexicographically (or in dictionary order), ignoring the identifier. In case of ties (i.e. same word after identifier), use the identifier to resolve them. The digit-logs should appear in the order they were in the original list.

For example, consider the following logs:

1
2
3logs = ["dig1 8 1 5 1","let1 art can","dig2 3 6","let2 own kit dig","let3 art zero"]

The sorted logs would be:

1
2
3sorted_logs = ["let1 art can","let3 art zero","let2 own kit dig","dig1 8 1 5 1","dig2 3 6"]

Approach

To solve this problem, you can utilize a combination of simple string manipulation, sorting routines, and arrays.

  1. You start by separating logs into two categories: letter-logs and digit-logs. When separating, for letter-logs you store each log's identifier and the remaining part separately.
  2. You then sort the letter-logs lexicographically based on the remaining part after identifier. If there's a tie, use the identifier to break it.
  3. After sorting, you combine letter-logs and digit-logs to form the final result, ensuring that all letter-logs appear before any digit-log.

The Algorithm in Steps

Let's take the example logs above and walk through the algorithm step by step:

  1. Start with logs: ["dig1 8 1 5 1","let1 art can","dig2 3 6","let2 own kit dig","let3 art zero"]
  2. Separate logs into letter-logs and digit-logs for ease of processing:
1
2
3digitLogs = ["dig1 8 1 5 1", "dig2 3 6"]
4letterLogs = [ ("let1", "art can"), ("let2", "own kit dig"), ("let3", "art zero") ]
  1. Sort the letter-logs lexicographically. In case of ties, use the identifier:
1
2
3letterLogs = [ ("let1", "art can"), ("let3", "art zero"), ("let2", "own kit dig") ]
  1. Now combine letter-logs and digit-logs to get the final result. Remember that all letter-logs should appear before any digit-log:
1
2
3final_logs = ["let1 art can","let3 art zero","let2 own kit dig","dig1 8 1 5 1","dig2 3 6"]

And that's how we reorder the logs according to the given rules.

The Solution Code

The code starts by separating all logs into letter-logs and digit-logs. Then it sorts the letter-logs lexicographically and finally combines the two types of logs to get the final results.

Here's the solution in Python:

1
2python
3class Solution:
4    def reorderLogFiles(self, logs):
5        digitLogs = []
6        letterLogs = []
7        
8        for log in logs:
9            if log.split()[1].isdigit():
10                digitLogs.append(log)
11            else:
12                letterLogs.append(log)
13        
14        letterLogs.sort(key=lambda log: (log.split()[1:], log.split()[0]))
15        
16        return letterLogs + digitLogs

In Java:

1
2java
3class Solution {
4    public String[] reorderLogFiles(String[] logs) {
5        Arrays.sort(logs, (log1, log2) -> {
6            String[] split1 = log1.split(" ", 2);
7            String[] split2 = log2.split(" ", 2);
8            boolean isDigit1 = Character.isDigit(split1[1].charAt(0));
9            boolean isDigit2 = Character.isDigit(split2[1].charAt(0));
10            if(!isDigit1 && !isDigit2) {
11                int cmp = split1[1].compareTo(split2[1]);
12                if(cmp != 0) { return cmp; }
13                return split1[0].compareTo(split2[0]);
14            }
15            return isDigit1 ? (isDigit2 ? 0 : 1) : -1;
16        });
17        return logs;
18    }
19}

C++:

1
2c++
3class Solution {
4public:
5    vector<string> reorderLogFiles(vector<string>& logs) {
6        vector<string> digitLogs, letterLogs;
7        for(string &log : logs) {
8            if(isalpha(log.back())) letterLogs.push_back(log);
9            else digitLogs.push_back(log);
10        }
11        sort(letterLogs.begin(), letterLogs.end(), [](const string &a, const string &b) {
12            return a.substr(a.find(" ")) < b.substr(b.find(" ")) || (a.substr(a.find(" ")) == b.substr(b.find(" ")) && a < b);
13        });
14        letterLogs.insert(letterLogs.end(), digitLogs.begin(), digitLogs.end());
15        return letterLogs;
16    }
17};

And in JavaScript:

1
2javascript
3var reorderLogFiles = function(logs) {
4    const body = s => s.slice(s.indexOf(' ') + 1);
5    const isNum = c => /\d/.test(c);
6
7    const compare = (a, b) => {
8        const n = body(a), m = body(b);
9        if (!isNum(n) && !isNum(m)) {
10            const compare = n.localeCompare(m);
11            if (compare !== 0) return compare;
12            return a.localeCompare(b);
13        }
14
15        return isNum(n) ? (isNum(m) ? 0 : 1) : -1;
16    };
17
18    return logs.sort(compare);
19};

In C#:

1
2csharp
3public class Solution {
4    public string[] ReorderLogFiles(string[] logs) {
5        int len = logs.Length;
6        List<string> letter_logs = new List<string>();
7        List<string> digit_logs = new List<string>();
8        for(int i = 0; i < len; i++)
9        {
10            if(logs[i].Split(' ')[1][0] >= '0' && logs[i].Split(' ')[1][0] <= '9')
11                digit_logs.Add(logs[i]);
12            else
13                letter_logs.Add(logs[i]);
14        }
15        letter_logs.Sort((a, b) => {
16            int flag = string.Compare(a.Substring(a.IndexOf(' ') + 1), b.Substring(b.IndexOf(' ') + 1));
17            if(flag == 0)
18                return string.Compare(a.Split(' ')[0], b.Split(' ')[0]);
19            else
20                return flag;
21        });
22        return letter_logs.Concat(digit_logs).ToArray();
23    }
24}

Remember to add reasoning comments in the actual implementation for clarity.

Understanding the Solution

The solution algorithms separate the letter and digit logs, sort the letter logs based on content and identifier, and then recombine the letter and digit logs.

The Python, Java, C++, JavaScript and C# solutions all follow this general approach, but each with syntax and functions particular to the respective language.

In all the solutions:

  • A separate list or array is maintained for both the letter and digit logs.
  • The letter logs are sorted lexicographically using a custom comparator.
    • In Python, it uses the built-in sort() function with a lambda function as the key parameter to sort based on log content and then the identifier if necessary.
    • In Java, it uses the Arrays.sort() method with a custom comparator defined as a lambda function. It splits the logs into identifier and content, checks if they are digits, and then compares letters when both logs being compared are letters.
    • In C++, the lambda function works similarly to the Java solution. It uses the sort() function with a custom comparator that checks the content and the identifier of the logs.
    • In JavaScript, it also uses a similar approach to the Java and C++ solutions, by defining a custom comparator function and using the built-in sort() function to perform the sort.
    • In C#, it performs a similar operation but using a lambda expression inside the Sort() function.

After the letter logs are sorted, the digit logs, preserved in the order they appeared in the original list, are appended or concatenated at the end to form the final result array or list.

Extra Considerations

These solutions achieve optimal performance. The time complexity of the algorithm is dominated by the sorting of the letter logs, which is O(n log n), where n is the number of letter-logs.

The solutions also handle edge cases well. If there are no letter-logs or digit-logs, the solutions will still return the correct result with no exceptions. Also, they can handle logs with the same contents fine; logs are sorted by identifier in this case, as required.

However, you can improve the code's readability by adding clarifying comments, especially for the comparator functions, which may be confusing for less experienced programmers. Also, if you find lambda functions confusing, you can replace them with traditional function definitions for ease of understanding.

In conclusion, these solutions demonstrate good use of string manipulation, sorting routines, and array operations inherent in Python, Java, C++, JavaScript, and C# to solve a moderately challenging problem.


Got a question? Ask the Teaching Assistant anything you don't understand.

Still not clear? Ask in the Forum,  Discord or Submit the part you don't understand to our editors.


TA 👨‍🏫