- Qwen3 32B is a free model from Alibaba's Qwen team that you can run on your own computer. In our tests it's decent, with trade-offs: 64 out of 100, #24 of 56.
- It solved 15 of 30 coding jobs and scored 79 on reading documents. On our hardest tasks it scored 50.
- Runs on a 24 GB graphics card or a Mac with 48 GB.
Coding
Our coding test is 30 programming jobs, from small ones like reading time durations or cleaning up messy data to harder ones like a config-file parser or a double-entry ledger. We run each answer against tests the model never sees, and a job only counts if everything passes. Qwen3 32B got 15 of 30 right. The best local coders solved 29 of 30.
Reading documents
The second test hands the model things like an expense claim thread, a pay stub or an insurance statement, and asks for specific numbers and dates. Many questions need a bit of math, or noticing a correction further down the email. Qwen3 32B scored 79; the best model scored 100.
| Test | Score | Public questions | Secret questions |
|---|---|---|---|
| Coding | 50 | 71 | 44 |
| Reading documents | 79 | 90 | 76 |
| Decisions | 96 | 100 | 95 |
On the 18 hardest tasks (included in the scores above) it scored 50. This number separates the top models.
This model did much better on our public questions than on the secret ones. Sometimes that's luck; it can also mean the model has seen the public questions before.
We tested the full-size version online. The smaller versions you download usually score a little lower.
Can your computer run it?
You can download Qwen3 32B in several sizes. Smaller versions fit on more computers and lose a little quality, so pick the biggest one that fits.
| Your computer | Runs it? | Version to download |
|---|---|---|
| Graphics card, 8 GB | No | – |
| Graphics card, 12 GB | No | – |
| Graphics card, 16 GB | No | – |
| Graphics card, 24 GB | Yes | Q4 |
| Graphics card, 32 GB | Yes | Q6 |
| Mac, 16 GB | No | – |
| Mac, 24 GB | No | – |
| Mac, 32 GB | No | – |
| Mac, 48 GB | Yes | Q6 |
| Mac, 64 GB | Yes | Q8 |
| Mac, 96 GB | Yes | Q8 |
| Mac, 128 GB | Yes | Q8 |
For normal-length chats. Long documents need more memory. Q4 or Q8? · How we worked this out
How to try it
The easiest way is Ollama. Install it, then run:
ollama run qwen3:32b Want the strongest all-rounder instead? Qwen3.6 27B scored 97.
Its actual answers
What Qwen3 32B wrote for our public questions, word for word.
Coding: INI parser (hard) Didn't work
Okay, this is getting complex. Let me try to outline the code steps with more detail.
Let's start by writing a helper function to process the line and remove inline comments. So:
def remove_inline_comment(line):
inside_quotes = False
main = []
i = 0
while i < len(line):
c = line[i]
if c == '"':
inside_quotes = not inside_quotes
main.append(c)
i += 1
elif inside_quotes:
main.append(c)
i += 1
else:
if i < len(line) - 1:
next_c = line[i+1]
if c.isspace() and next_c in ';#':
# found the start of a comment. return everything before this.
return ''.join(main), True
main.append(c)
i += 1
# if no comment found, return entire line, False.
return ''.join(main), False
Wait, but once we find the space followed by # or ;, we should return the main part processed so far and ignore the rest. So in the above code, once we reach that point, we return the current main (before the space?), but in the code, the space and the next character are not added. Wait, no. Let's see.
In the example line "key = value # comment", after processing the main part until the space before the '#', the code would return the main part (before the space) as ''.join(main) is the part before the comment. So the code is scanning the line, and whenever it finds a space followed by # or ; and the code is not inside quotes, then the rest of the line is considered a comment.
This code may not capture all cases. For example, the line could have multiple spaces followed by #, but we need to find the first one. Anyway, for now, let's assume this function works as intended. So, for a given line, after stripping leading and trailing whitespace (if the line is not a continuation line?), no. For a key line, after processing, we need to remove the comment and then split into key and value.
But during the main code, for a line that is a key line or continuation line, we first determine whether it's a key line or continuation line. But the line may have a comment in the middle of the key line.
But the helper function is supposed to process the comment part. Let's proceed.
So, in the main loop:
current_section: str or None
sections: dict[str, dict], mapping section names to their keys.
default_section: dict[str, str]
current_key: either None, or the key being processed.
current_value_parts: For current key, the value is built from multiple lines. Each non-continuation line may have just one part, but continuation line appends their processed parts.
But for a quoted value, the value is the inner part. So the code must check whether the value_part is quoted. So for example, the code for processing the key line could be:
for a line that's not a section and not a comment:
if current_key is not None and the line is a continuation line:
process the line (without key), i.e., remove inline comments, then take the entire line and stripped.
add to current_value_parts.
else:
if current_key is not None: then this is a new line not a continuation, so need to finalize the current_key.
process the current line:
processed_line, has_comment = remove_inline_comment(line)
if no has_comment, processed_line is the entire main part.
else, processed_line is the main part.
split into key_part and value_part → find first = or :
if no key separator found → ValueError.
key_part is everything before the first separator, then stripped and lowercased.
value_part is everything after the separator.
process the value_part:
if value_part starts and ends with double quotes:
value_processed = value_part[1:-1]
else:
value_processed = value_part.strip()
if current_key is not None:
# this is after a key line, but current_key is set. So, the line is not continuation line. That's an error. Wait, no. Because when current_key is set, it's the previous line's key that was being processed, but this line is a new key line. So previous key's value was not closed.
# So, the code must append all value parts until a new line.
# So, need to collect all the value parts.
# Wait, but current_key can be None only if not in continuation.
# So, in this case, since current_key is not None and the line is not a continuation line, we need to add the previous key to the section and process the new line.
if current_key and line is not continuation:
# finalize the previous key.
value = '\n'.join(value_parts)
if current_section is 'DEFAULT':
default_section[current_key] = value
else:
sections[current_section][current_key] = value
current_key = None
value_parts = []
# then process the current line.
line_main = processed_line.
split into key_part and value_part.
if no splitter → ValueError.
key = key_part.strip().lower()
current_key = key
value_parts = [value_processed]
So, this is getting a bit complicated.
Alternatively, the code can manage for each current_key, the current value parts that are being collected.
So, the main variables are:
current_section: the name of the current section (could be 'DEFAULT')
current_key: the current key being collected (None if not in collection).
current_value_lines: a list of processed strings that make up the value for current_key, which are to be joined with newlines.
So, for each line that is a key line or continuation:
If the line is a continuation (starts with whitespace) and current_key is not None:
process it as a continuation, add to current_value_lines.
But wait, the line is a continuation line, so it is not a key line. How to get the value part for the continuation?
For a continuation line, the entire line is part of the value. So after processing the inline comments and stripping the line's leading and trailing whitespace (as per rule 6), the line's stripped text is added to current_value_lines.
Wait, rule 6 says the line's stripped text (when it's a continuation) is appended. So for example, the line is " more value here # comment in continuation" → after processing inline comment (using the helper), we get " more value here", then we strip to "more value here".
So the code for the continuation line would be:
processed_line, has_comment = remove_inline_comment(line)
But since the line is not a comment line (as earlier check), the helper returns the main part. Then, we take that line, strip leading and trailing whitespace, and add to current_value_lines.
So, for the continuation line, we strip leading and trailing whitespace and add to the current_key's value_parts, then join with '\n'.
But for the quoted value, the line's value is the quote part. But if the line's value is not in quotes, the code strips the entire value_part (after the split) and adds any continuation lines.
Wait, here's an example:
Section [Test]
Line1: key = "a" ; Now, a continuation line would be processed but since it's not a quoted value, then the code in the current_key is set and the value_parts is [value_part (a, after stripping quotes)]? Or not.
Wait, the value_part is the part after the split. For the line key = "a", the value_part is "a", which starts and ends with quotes. So process_value_part: take the inner part. So the inner part is a. So, current_value_lines for key would be ['a'].
If the next line is ' more value', it's a continuation line. Stripped becomes 'more value' and added to current_value_line. The total value is 'a' + '\n' + 'more value' → but that's not correct. Because the original value_part was quoted. So the rule is that the value in quotes is kept as is. So the initial value_part's inner part is the entire "a". The continuation line is processed as a new part. But the code is appending new parts to the initial. So the code is combining a quoted value with continuation lines, which might be wrong. But according to the rules, how should this be handled?
The problem says that for continuation lines, their stripped text is appended to the previous value. But how does the previous value look? For the initial key line, the processed value_part is either the inner part (if quoted) or the stripped part (if unquoted). So in the case of quoted value_part, the code appends to it. But according to rule 5, the quoted value is kept as-is, including newlines. So in the case of the quoted value_part, the code must not process continuation lines. But according to rule 6, the code does.
This suggests that there is an inconsistency in the rules. How to resolve this? The problem statement doesn't mention conflicting rules, so perhaps the code is to process the continuation regardless of whether the initial value was quoted. But perhaps that is an error.
But according to the rules as given, I must implement rule 6 regardless. So in the case of a quoted value and followed by a continuation line, the code would add the continuation's stripped text as a new line. But this is not what the user intended, but perhaps according to the rules, it's allowed. For example, if a line's value_part is in quotes and there's a continuation line, it is part of the same value, and the code appends the stripped text of the continuation line. Thus, the final value is the original quoted content plus the continuation lines. For example, if the quoted value is "foo" and the continuation line is "bar", the overall value is 'foo\nbar'.
But according to rule 5, the quoted value is kept exactly (so if the initial line was the quoted value, then the code would have stored that and added the continuation as a new part.
But why? The initial value_part is in quotes, meaning it's a complete value. So the code should not allow continuation lines. But the problem statement says nothing about this, so perhaps the code just proceeds as per the rules. For example, the code that splits the line into key and value, and then for the value, adds the continuation.
Thus, the code would allow this, which may not be what the user intended. However, since the problem states these rules, the code must follow them.
So, the code proceeds as per the following steps:
When processing a line (after checking for comments and sections):
if it is a continuation line:
process the line's content.
add the stripped content to the current_value_lines.
else:
if current_key was active:
finalize the current_key (join value_parts with newlines), add to the section.
reset current_key.
split the line into key_part and value_part.
key is key_part.strip().lower()
process the value_part into a value_processed (quoted or unquoted).
set current_key to key, current_value_lines = [value_processed]
Now, after processing all lines, if there's a current_key active, add it to the current section.
Now, the code must also handle the interpolation, which is the third part of the problem.
But first, the parsing is done. Once parsed, the default_section is available, and each other section must have the default_section's keys merged in, with same keys being overridden.
Then, interpolation: values can contain ${key} or ${section:key} references. The interpolation is recursive, and references to missing keys or cycles raise errors.
So, during parsing, the code must parse the value into a string, and then during the interpolation phase, substitute the references.
But the problem says that the parse_ini function must process interpolation as part of the parsing.
So, after parsing the INI file into the sections and default, then, for each key-value pair, process the value string for interpolation and replace the ${...} references.
Therefore, after parsing, the code must go through all the key values and resolve them.
But the order of processing is important. For example, when processing a section, its keys could refer to keys in default, but those keys may have been processed in default, but may also have their own interpolation.
But according to the problem statement, the default keys are inherited. So during the interpolation, if a section has a key, it uses its own value, else the default's value. So, the code must first process the default section, then process other sections.
But the interpolation is recursive. So the code needs to process the values with a resolver function that can handle the interpolation.
Now, the plan is:
After parsing the ini into sections and the default_section, we need to resolve the values for each key, applying interpolation.
But how to handle this.
Each value may contain variables like ${key} or ${section: key}. During the parsing phase, the resolver must look for these and replace them. The resolver knows which section the key is in, and uses the current section's values plus the default_section.
But the interpolation is recursive, so the code must handle possible loops.
So, perhaps after the parsing is done, perform a post-processing step for interpolation. For each section, and each key, run the interpolation.
But during the parsing, the code may have to collect the key lines (with interpolation in the value) and resolve them. However, since the interpolation could refer to keys that are not parsed yet, perhaps the code needs to resolve all the keys after parsing, and during the resolution, check for variables, then recursively resolve them.
But for the parse_ini function, the code must return a dict with interpolated values.
Now, the problem says that the same key appearing twice in a section makes the later value wins. So during parsing, the code must store the last value for the key.
But during interpolation, the code must process the value string based on the section's own keys and the default_section.
But how to implement this.
The parse_ini function will:
First, parse the ini into sections and default_section as per rules 1-8 and 10.
Then, perform interpolation on all values in all sections.
But to do that, each section's keys can be merged with the default_section's keys, but without overwriting the existing keys in the section.
So for each section in sections, the merged dict is {**default_section, **section}, but the order is important. But in Python 3.11+, you can do {**a, **b} (b overwrites a), but since interpolation is to inherit from default, for a section, the code will first check the section's own keys, and if not present, check the default section.
So, for a section S, when interpolating a key's value, if the key is not present in S, the code checks the default_section.
Thus, for interpolation, we need a resolver function that, given a section name, and a key, can find its value. But during interpolation, a value may refer to other keys that may or may not be in the same section.
Now, how to handle the interpolation.
For each value in the ini, during parsing, once the value is stored in the sections, we must process it. For example:
value = "abc ${key} xyz"
The code must replace ${key} with the value of key in the same section (including default).
But the problem says that interpolation is recursive. So if key refers to another key, which may refer to another, etc.
But processing this in a way that handles cycles and missing keys.
So, for parsing, after the sections are parsed (without interpolation), then process each key's value.
For the interpolation step:
For each section and each key in that section, we process the value, replacing any ${...} as per the rules. However, for the interpolation to be able to refer to other sections and their keys.
The resolver function for interpolation would be a function that for a given section and a key, returns its value. For example, a function that, for a key in section S, returns the value of the key in S, falling back to the default section.
So, the resolver could be something like:
def resolver(section_name, key):
if key in sections[section_name]:
value = sections[section_name][key]
else:
value = default_section.get(key, None)
return value
But the resolver needs to return the raw value (before interpolation) so that interpolations are resolved correctly. However, during interpolation, the value being processed may refer to another value that also needs to be interpolated. This suggests that the interpolation must be done after all values are parsed, but during the step of processing interpolation.
Thus, the resolver for interpolation is not the final value, but the original value before interpolation. Because the interpolation step must be done in a way that allows recursive replacement.
So, the plan for interpolation is:
Create a merged structure where each section's keys are processed, and the interpolation is done on the value strings.
But how to build that.
Another approach is:
For each section, when processing its keys, do not process the value yet, but store the original string. Then, during the interpolation phase, for each key, perform substitutions.
But the parsing rules (steps 1-8) must first parse the values, so the parse_ini function will need to parse the INI file into sections where each value is the raw value string. Then, process interpolation.
Wait, but in the parsing step, the value is stored as a string. So, during the parsing, the code collects the key and value as a raw string, and then later during interpolation replaces variables.
So, for example, the code first parses all key values as raw strings. Then, for each key value in each section and for the default section, process the string to interpolate variables.
But the default section's keys may also contain variables, which refer to other keys in the default section or other sections.
So, after parsing (without interpolation), the code must process each stored value into their interpolated forms.
But how to do that.
Let's think of an example.
default_section:
key1 = "value1"
section1:
key2 = "value2 ${key1}"
The resolver for section1's key2 knows that key1 is in the default_section.
So, during the interpolation stage, when processing section1's key2, the code will substitute ${key1} with default_section's key1.
But during substitution, the code must be able to find the key in the current context (the same section or the default).
So, perhaps during the parsing step, the code collects all the values as raw strings. Then, for each key in each section and the default_section, interpolate the value.
But during the interpolation, it must have access to all resolved values (with the current section's keys and the default_section).
Therefore, during the interpolation phase, we can process each section and each key in that section.
First, process the default_section's keys, then process the other sections. However, a default_section can refer to other sections? No, per the interpolation rules. Because the interpolation in default_section can refer to other keys in the same default section (since for the current section (default), same-section keys are allowed.
But the default section is not a section in the output. So, when the code reads a section [A], and a key in [A] is ${key} (without section), then the resolver would look for 'key' in A, then in default.
So, the resolver for section 'A' when interpolating a key would first check the section, then check the default.
Therefore, the code must, for each section, have a way to resolve keys in the section and in the default_section.
But how to handle the fact Coding: TTL LRU (hard) Worked
```python
import collections
class TTLCache:
def __init__(self, capacity: int, ttl: float, clock):
self.capacity = capacity
self.ttl = ttl
self.clock = clock
self._cache = collections.OrderedDict()
self.hits = 0
self.misses = 0
self.evictions = 0
self.expirations = 0
def _remove_expired(self):
current_time = self.clock()
expired_keys = [
key for key, (value, expiry) in self._cache.items()
if expiry <= current_time
]
for key in expired_keys:
del self._cache[key]
self.expirations += 1
def _evict(self):
if self._cache:
self._cache.popitem(last=False)
self.evictions += 1
def put(self, key, value):
if self.capacity <= 0:
return
self._remove_expired()
if key in self._cache:
self._cache.move_to_end(key, last=True)
else:
while len(self._cache) >= self.capacity:
self._evict()
current_time = self.clock()
expiry = current_time + self.ttl
self._cache[key] = (value, expiry)
def get(self, key, default=None):
if self.capacity <= 0:
self.misses += 1
return default
current_time = self.clock()
if key not in self._cache:
self.misses += 1
return default
value, expiry = self._cache[key]
if current_time >= expiry:
del self._cache[key]
self.expirations += 1
self.misses += 1
return default
else:
self._cache.move_to_end(key, last=True)
self.hits += 1
return value
def __len__(self):
current_time = self.clock()
return sum(1 for _, expiry in self._cache.values() if expiry > current_time)
def stats(self):
return {
'hits': self.hits,
'misses': self.misses,
'evictions': self.evictions,
'expirations': self.expirations
}
``` Coding: Parse duration Didn't work
```python
import re
def parse_duration(s: str) -> int:
s_stripped = s.strip()
if not s_stripped:
raise ValueError
parts = s_stripped.split()
units_order = {'h': 0, 'm': 1, 's': 2}
units_seen = set()
prev_priority = -1
total_seconds = 0
for part in parts:
if not re.fullmatch(r'\d+[hmsHMS]', part):
raise ValueError
unit_char = part[-1].lower()
num_str = part[:-1]
num = int(num_str)
if unit_char in units_seen:
raise ValueError
units_seen.add(unit_char)
current_priority = units_order[unit_char]
if current_priority < prev_priority:
raise ValueError
prev_priority = current_priority
if unit_char == 'h':
total_seconds += num * 3600
elif unit_char == 'm':
total_seconds += num * 60
else:
total_seconds += num
return total_seconds
``` Coding: Summarize ranges Worked
```python
def summarize_ranges(nums: list[int]) -> str:
if not nums:
return ""
sorted_unique = sorted(set(nums))
groups = []
current_group = [sorted_unique[0]]
for num in sorted_unique[1:]:
if num == current_group[-1] + 1:
current_group.append(num)
else:
groups.append(current_group)
current_group = [num]
groups.append(current_group)
result_parts = []
for group in groups:
if len(group) >= 3:
result_parts.append(f"{group[0]}..{group[-1]}")
else:
result_parts.append(','.join(map(str, group)))
return ','.join(result_parts)
``` Coding: Top customers Worked
name, 1000.0), but in Python, float(1000.0) is 1000.0, but when printed, Python shows one decimal place. However, the problem requires to return the total as a float rounded to two decimals, which is achieved via round(..., 2). However, in cases where the value after rounding has less than two decimals, like 1000.0, the float will be 1000.0. But when converting to a string, it may show as 1000.0, which is allowed. The problem says to return as float. So perhaps that is acceptable.
So the code is correct.
Now, what about if a row has customer name, but after trimming, it's a valid name. So, for example, customer with leading/trailing spaces. The code trims and uses that as the original name.
Yes.
Now, the code should handle all that.
Now, considering that, I think this code should work.
Now, code written as:
import re?
No, the code does not use any regex. The code is using string replace and try to convert. So no import needed.
Now, write the code in Python 3.12, using standard library.
Thus, the code is as above.
Wait, but in Python, when you have multiple commas, like "$1,23,456"—the code would replace all commas and get "123456"—so 123456.0, but if the intended amount was 1,23,456 (which is invalid in the standard decimal format, but in this case it would be processed as 123456, which is correct.
But again, the problem says that the amount is in given examples, which are correct when you remove the commas and $, but maybe not all the cases. But the problem says to skip those rows that cannot be parsed. So the code skips only those that after cleaning can't be parsed. That's what the problem says.
Now, to code this function.
Now, the code as written earlier.
Another possible case: row where customer name is already in the dictionary, but amount is a refund (negative), which should subtract. For example, first row is 'Alice' with $100. Then another row is 'alice' with '-$50.00' → the total is 50.0. Which is correct.
Now, code should handle that.
Now, the code for the function is as written above. Let me check if there are any bugs.
Another possible issue: if the first occurrence of a customer has a name that is, say, "Alice", and then later there is a refund for a customer "Alice ", which becomes "Alice" lower. So it's same customer, and the amount is added (as a negative) to the total.
Yes, correct.
Another thing: the problem says to return the sum rounded to two decimals, but during accumulation, we added precise amounts. For example, if the sum is 123.456, then round to two decimals gives 123.46. But if we had added rounded amounts each time, but the problem says to sum and then round at the end. The code as written is summing the precise amounts and then rounds the final sum. So that's correct.
Now, code.
Now, what data types to return? The function returns a list of tuples, where each tuple has (str, float). But for example, in the first example of 'Alice' with total 100.0 + 200.5 = 300.5 → round to two decimals is 300.5. So the tuple is ('Alice', 300.5). But in Python, when printed, it would be 300.5, but the problem says to round to two decimals, so maybe the correct value is 300.5, but perhaps the problem expects 300.50. But as a float, 300.5 is same as 300.50. So when converting to float, it's 300.5, but when printed as two decimal points, perhaps the code expects 300.50. But since the problem requires returning a float, perhaps it's up to the function to return the rounded float. However, in Python, when you round 300.5 to two decimals, you get 300.5. But when you format it, you can make it 300.50, but the function is to return a float. For example, round(300.5, 2) is 300.5, which as a float is stored as exactly that value. However, due to floating point imprecision, perhaps for some numbers, but that's a general issue. The problem says to round to two decimals. So the code uses round(..., 2).
Now, code written.
Now, what about when n is larger than the number of customers? Then the return is the sorted list. What if n is zero? Probably, the function returns empty list. The problem says n is an int. But the function can return an empty list if n is zero. However, the problem may assume that n is positive. But the function is supposed to return top n, so if n is zero, then return empty list. Since Python's list slicing handles it.
Now, code is written.
Now, in the code:
What if a customer has multiple invalid amount rows? But that's handled by the code. Those rows are skipped.
Now, the code should be correct. So the code should be:
def top_customers(rows: list[dict], n: int) -> list[tuple[str, float]]:
customer_data = {}
for row in rows:
customer = row['customer'].strip()
if not customer:
continue
amount_str = row['amount']
cleaned = amount_str.replace('$', '').replace(',', '').strip()
if not cleaned:
continue
try:
amount = float(cleaned)
except ValueError:
continue
lower_customer = customer.lower()
if lower_customer in customer_data:
customer_data[lower_customer]['total'] += amount
else:
customer_data[lower_customer] = {
'original_name': customer,
'total': amount
}
# create list and round
unsorted = [ (data['original_name'], data['total']) for data in customer_data.values() ]
# round the totals
unsorted_rounded = [ (name, round(total, 2)) for name, total in unsorted ]
# sort
sorted_list = sorted(unsorted_rounded, key=lambda x: (-x[1], x[0]))
return sorted_list[:n]
Wait, but in this code, the first unsorted is (original_name, sum), then unsorted_rounded is rounding each. But perhaps it's more efficient to compute the rounded value during the initial loop. But no, the code can collect the unrounded values first, then round once when building the unsorted_rounded list.
But the code as written:
Inside the loop, for the customer_data, the 'total' is stored as the sum of the parsed amounts. Then, after that, build the unsorted list, and for each element, round the total.
Yes.
Another example: customer 'A' with sum 123.456. Rounded is 123.46. Another customer 'B' with sum 123.46. So sorted by total descending, A and B would be in order based on name if they have same total.
But in the code, the unsorted_rounded is correctly handling that.
So this code should work.
Now, but in the code above, the first unsorted list is (original name, total), but then the code rounds. But during the code, the original name is the first occurrence's trimmed name. So that's okay.
But perhaps I can combine into one step. Like:
unsorted_rounded = [ (data['original_name'], round(data['total'], 2)) for data in customer_data.values() ]
Which is what I wrote earlier. So the code is:
Once that is done.
Now, the code in the previous example.
But perhaps the code should be:
After processing all the rows, for each customer in customer_data.values(), take original name and total sum, then round.
Yes.
Now, in code, the code for the list can be written as:
result = [ (data['original_name'], round(data['total'], 2)) for data in customer_data.values() ]
sorted_result = sorted(result, key=lambda x: (-x[1], x[0]))
return sorted_result[:n]
So this is better, because no need to create unsorted and then unsorted_rounded. So the code for the function becomes:
def top_customers(rows: list[dict], n: int) -> list[tuple[str, float]]:
customer_data = {}
for row in rows:
customer = row['customer'].strip()
if not customer:
continue
amount_str = row['amount']
cleaned = amount_str.replace('$', '').replace(',', '').strip()
if not cleaned:
continue
try:
amount = float(cleaned)
except ValueError:
continue
lower_customer = customer.lower()
if lower_customer not in customer_data:
customer_data[lower_customer] = {'original_name': customer, 'total': amount}
else:
customer_data[lower_customer]['total'] += amount
# generate the list
result = [ (data['original_name'], round(data['total'], 2)) for data in customer_data.values() ]
# sort
result.sort(key=lambda x: (-x[1], x[0]))
return result[:n]
Yes. This is the correct code.
Now, the code for the function is written.
Now, test another example: what if there are multiple customers with same total? For example, two customers "Alice" and "Bob" with same total. The code would sort them by name ascending.
Yes.
Now, code written.
Now, I think this should be correct.
```python
def top_customers(rows: list[dict], n: int) -> list[tuple[str, float]]:
customer_data = {}
for row in rows:
customer = row['customer'].strip()
if not customer:
continue
amount_str = row['amount']
cleaned = amount_str.replace('$', '').replace(',', '').strip()
if not cleaned:
continue
try:
amount = float(cleaned)
except ValueError:
continue
lower_customer = customer.lower()
if lower_customer not in customer_data:
customer_data[lower_customer] = {'original_name': customer, 'total': amount}
else:
customer_data[lower_customer]['total'] += amount
result = [(data['original_name'], round(data['total'], 2)) for data in customer_data.values()]
result.sort(key=lambda x: (-x[1], x[0]))
return result[:n]
``` Coding: Slugify Worked
```python
import unicodedata
import re
def slugify(title: str, max_len: int = 50) -> str:
slug = unicodedata.normalize('NFKD', title).encode('ascii', 'ignore').decode('ascii').lower()
slug = re.sub(r'[^a-z0-9]+', '-', slug).strip('-')
if len(slug) <= max_len:
return slug
parts = slug.split('-')
new_parts = []
current_len = 0
for part in parts:
if not new_parts:
if len(part) > max_len:
return part[:max_len]
new_parts.append(part)
current_len = len(part)
else:
add_len = current_len + 1 + len(part)
if add_len > max_len:
break
new_parts.append(part)
current_len = add_len
return '-'.join(new_parts)
``` Coding: Token bucket Worked
```python
class TokenBucket:
def __init__(self, capacity: float, refill_per_sec: float, clock):
self.capacity = capacity
self.refill_per_sec = refill_per_sec
self.clock = clock
self._tokens = capacity
self._last_refill_time = clock()
def _refill(self):
now = self.clock()
if now > self._last_refill_time:
delta = now - self._last_refill_time
added_tokens = delta * self.refill_per_sec
self._tokens = min(self.capacity, self._tokens + added_tokens)
self._last_refill_time = now
def allow(self, cost: float = 1) -> bool:
self._refill()
if self._tokens >= cost:
self._tokens -= cost
return True
return False
@property
def tokens(self) -> float:
self._refill()
return self._tokens
``` Decisions: Refund window (hard) 100% right
{
"outcome": {
"store_credit": 1.0
},
"defective": {
"false": 1.0
}
} Decisions: Incident matrix (hard) 100% right
{"severity": {"0": 0.0, "1": 1.0, "2": 0.0, "3": 0.0}, "page": {"true": 0.0, "false": 1.0}} Decisions: Tool followup (hard) 100% right
{
"tool": {
"calendar": 1.0
},
"confirm": {
"true": 0.95,
"false": 0.05
}
} Decisions: Legit security alert (hard) 100% right
{
"phishing": {
"false": 0.9,
"true": 0.1
},
"action_needed": {
"false": 0.85,
"true": 0.15
}
} Decisions: Meeting slot (hard) 100% right
{"slot": {"A": 0.0, "B": 1.0, "C": 0.0, "D": 0.0}, "raj_last": {"true": 1.0, "false": 0.0}} Decisions: Review mixed (hard) 100% right
{"hardware":{"true":0.9,"false":0.1},"support":{"true":0.95,"false":0.05}} Decisions: Support checkout down 100% right
{"department": {"technical": 0.85, "billing": 0.10, "sales": 0.05}, "urgency": {"3": 0.95, "2": 0.03, "1": 0.02}, "outage": {"true": 0.99, "false": 0.01}} Decisions: Refund wrong plan 100% right
{"department": {"billing": 0.95, "technical": 0.03, "account": 0.01, "shipping": 0.01, "sales": 0}, "refund": {"true": 1, "false": 0}, "tone": {"frustrated": 0.05, "calm": 0.95}} Decisions: Moderation doxxing 100% right
{"policy": {"none": 0.0, "harassment": 0.9, "hate": 0.03, "spam": 0.0, "self_harm": 0.07}, "personal_info": {"true": 0.99, "false": 0.01}} Decisions: Route calendar 100% right
{"tool": {"calendar": 1.0}, "confirm": {"true": 1.0}} Decisions: Doc invoice missing due 100% right
{
"doc_type": {
"invoice": 0.99,
"other": 0.01
},
"missing_due_date": {
"true": 0.95,
"false": 0.05
}
} Decisions: Phishing paypal 100% right
{"phishing": {"true": 0.98, "false": 0.02}, "risk": {"3": 0.95, "2": 0.03, "1": 0.01, "0": 0.01}} Decisions: Pii ssn email 100% right
{"data_kind": {"government_id": 1.0}, "sensitive": {"true": 1.0}} Decisions: Review mixed 100% right
{
"sentiment": {
"positive": 0.1,
"neutral": 0.0,
"negative": 0.9
},
"defect": {
"true": 0.95,
"false": 0.05
},
"recommend": {
"true": 0.05,
"false": 0.95
}
} Documents: Saas escalator (hard) 100% right
{
"year2_price_per_seat_month": 47.25,
"year3_price_per_seat_month": 47.25,
"year1_invoice": 58320.00,
"year2_invoice": 61236.00,
"addon_months_billed": 6,
"addon_invoice": 38556.00,
"year3_invoice": 134946.00,
"year3_discount_percent": 15,
"total_contract_value": 293058.00,
"contract_end_date": "2027-02-28"
} Documents: Expense thread 100% right
{"employee_id": "EMP-20417", "destination_city": "Lisbon", "trip_start": "2025-02-24", "trip_end": "2025-02-27", "approved_items": [{"date": "2025-02-24", "category": "airfare", "amount_usd": 1184.6}, {"date": "2025-02-24", "category": "ground_transport", "amount_usd": 38.88}, {"date": "2025-02-25", "category": "meals", "amount_usd": 229.39}, {"date": "2025-02-26", "category": "lodging", "amount_usd": 466.56}, {"date": "2025-02-27", "category": "ground_transport", "amount_usd": 44.82}], "rejected_item_count": 1, "per_diem_days": 3, "per_diem_usd": 195, "total_reimbursable_usd": 2159.25, "approver_email": "priya.raman@corvane.com"} Documents: Lease amendment 75% right
{"tenants": ["Marcus Lin", "Sofia Lin"], "landlord": "Ridgeline Property Group LLC", "zip": "97205", "lease_end": "2025-11-30", "original_monthly_rent": 2150.0, "monthly_rent_from_2025_06_01": 2199.6, "late_fee_from_2025_06_01": 109.98, "security_deposit": 2150.0, "total_pet_deposits": 800.0, "total_monthly_payment_july_2025": 2269.6, "move_in_payment": 4700.0} Documents: Ticket SLA 75% right
{
"ticket_id": "48213",
"account_id": "ACC-7731",
"open_issue": "inventory_sync",
"resolved_issues": ["billing_address", "invoice_pdf"],
"affected_orders": ["SO-99812", "SO-99820", "SO-99827"],
"priority": "P2",
"sla_due_local": "2025-09-15T14:30",
"sla_due_utc": "2025-09-15T19:30:00Z",
"reissued_invoice": "INV-2025-0812"
} Documents: Sales footnotes 100% right
{
"q3_total_usd": 15346000,
"q2_total_usd": 14464000,
"q2_central_originally_reported_usd": 3047000,
"q2_to_q3_change_pct": 6.1,
"top_region_q3": "East",
"fastest_growing_region_q1_to_q3": "International",
"regions_declining_q2_to_q3": ["East"],
"international_q3_organic_usd": 1731000,
"west_excluding_mountain_q3_usd": 4201000
} Size: 33B parameters. First tested OCT 10.
Comments
Sign in with GitHub to comment. Spam and abuse are hidden automatically.