Problem
The built-in "sum" function in Python is great for adding up a bunch of values.
sum([ 1, 2, 3 ])
# output: 6
But if you give it an iterable that has values of different types, it simply adds them as usual. This means if you had just one float (fractional number) in a list full of integers (whole numbers,) the result of the "sum" would be a fractional number.
sum([ 1, 2, 3.5 ])
# output: 6.5
This is probably the expected behaviour since it will give an accurate answer.
However there may be times where you need the result to match the most common data type in the inputs, such that the values before being added are first converted into that type.
Solution
To address this issue I first iterated over the inputs to get the common type, then converted the values to that common type before adding them.
def sum2(t, start=None):
def get_common_type(t):
d = (lambda types: {ty: types.count(ty) for ty in set(types)})(tuple(map(type, t)))
best_count = -1
best_ty = None
for ty, count in d.items():
if count > best_count:
best_count = count
best_ty = ty
return best_ty
common_type = get_common_type(t)
start = common_type() if start is None else common_type(start)
return sum(map(common_type, t), start)
Or even shorter:
sum2 = lambda t, start=None: (lambda common_type: sum(map(common_type, t), common_type() if start is None else common_type(start)))((lambda d: tuple(d.keys())[tuple(d.values()).index(max(d.values()))])((lambda s: {_: s.count(_) for _ in set(s)})(tuple(map(type, t)))))
Demonstration
sum2([ 1, 2, 3, 4.5 ])
# output: 10
The output this time kept the most common data type (integer) and converted the non-integer 4.5 to 4 before adding it to yield the result of 10.
Pros
- Preserve the most common type for passing to other functions
- Avoid accidental mistakes by typing ".0" after just one number
- Works with any in-place-add-able objects
Cons
- Result is less accurate (fractional parts may get truncated)
- Doesn't work for infinite generators
- Worse execution speed and memory usage
- Common type must be default constructible and constructible from all other data types in the inputs
Note that if you give "sum2" a starting value, that value is also converted to the most common type of the inputs, but the start itself is not considered in the realisation of the common type.
If the starting value is supplied, you may want to:
- Assume the type of the starting value as the common type. This would be best if a specific data type was obtained from a previously called function to be the starting value. It would save the calculation of the common type and enable the solution to work
- Consider the starting value when finding the common type
- Assume the caller wants to use the starting value as-is, but convert all other values to their common type
To tackle some of the cons, you could also calculate the most common type from the first N values, for instance the first five values. This wouldn't add as much execution time and memory usage as evaluating the entire input, and would work with infinite generators, but the common type may be biased towards only the first few values.
You will typically find that the standard library writers have already given thought to these usual use cases and picked the better solution. Meanwhile you only thought about your specific use case. Therefore another option would be to adapt your workflow around the standard functions. For instance you could "map" your iterable to the type you want before computing the "sum":
sum_as = lambda ty, it, start=None: sum(map(ty, it), start)
sum_as(int, [ 1, 2, 3, 4.5 ])
# output: 10
Top comments (0)