HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

Ordering an un-ambiguous scrambled date

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
ambiguousscrambledorderingdate

Problem

A date is un-ambiguous when each number may be
decided to be day or month or year given the range
limitation of these numbers.

For example

  • \$14\$ cannot be a month number



  • \$2008\$ cannot be a day number



@param un_ambiguous_date: an un-ambiguous scrambled date,
of the format X/Y/Z where X, Y, Z correspond to
day, month, year but not necessarily in this order.

@return The date in the order 'DAY/MONTH/YEAR'.

I am particularly interested in full input validation and nice error producing, so tell me any lack of clarity/corner case missed.

For example usage see the doctests included with the function:

```
def order_date(un_ambiguous_date):
"""
@param un_ambiguous_date: an un-ambiguous scrambled date,
of the format X/Y/Z where X, Y, Z correspond to
day, month, year but not necessarily in this order.

@return: The date in the order 'DAY/MONTH/YEAR'.

A date is un-ambiguous when each number may be
decided to be day or month or year given the range
limitation of this numbers.
(For example 14 cannot be a month number, and
2008 cannot be a day number)

>>> order_date('3/2015/13')
'13/3/2015'

>>> order_date('2012/20/4')
'20/4/2012'

# Here both 3/4/1000 and 4/3/1000 are possible.
>>> order_date('3/4/1000')
Traceback (most recent call last):
...
ValueError: Ambiguous date was given

>>> order_date('3/3/2050')
'3/3/2050'

>>> order_date('1/1/1')
Traceback (most recent call last):
...
ValueError: The date cannot be valid

>>> order_date('12/6')
Traceback (most recent call last):
...
ValueError: The date is too short, format should be X/Y/Z

>>> order_date('2000/2000')
Traceback (most recent call last):
...
ValueError: The date is too short, format should be X/Y/Z

>>> order_date('Foo/Bar/Baz')
Traceback (most recent call last):
...
TypeError: The date should be made up of '/' separated INTEGERS

"""

Solution

Wrong bounds

Your filters on your day/month aren't right:

day = [i for i in (x,y,z) if i < 31]
month = list(set(i for i in (x,y,z) if i < 12))


31 is a valid day and 12 is a valid month, so those should be <=. Additionally, you're not excluding negative numbers - so you'll want 1 <= i <= 31 for the first filter and 1 <= i <= 12 for the second.

Dealing with the tuple

Since just about everywhere you deal with (x,y,z) as a tuple, I would just keep it as a tuple the whole way and not unpack it. You only unpack it to check the length, so we can just make that check explicit. Also, if I passed in something like 1/2/3/4/5, you'd tell me that my date was too short, so this can get both right:

elems = un_ambiguous_date.split('/')
if len(elems) != 3:
    raise ValueError("The date is too {}, format should be X/Y/Z".format(
        'short' if len(elems) < 3 else 'long'))

try:
    elems = map(int, elems)
except ValueError:
    raise ...

day = [i for i in elems if 1 <= i <= 31]    
# etc.


The last join

Instead of:

return '/'.join(map(str, (day[0], month[0], year[0])))


I would just use format:

return '{}/{}/{}'.format(day[0], month[0], year[0])


It's shorter and more readable.

Combine the last two checks

I would write a helper function that returns the first element of a single-element list:

def front(lst):
    if not lst:
        raise ValueError("The date cannot be valid")
    if len(lst) > 1:
        raise ValueError("Ambiguous date was given")

    return lst[0]


And then you can just write:

return '{}/{}/{}'.format(front(day), front(month), front(year))

Code Snippets

day = [i for i in (x,y,z) if i < 31]
month = list(set(i for i in (x,y,z) if i < 12))
elems = un_ambiguous_date.split('/')
if len(elems) != 3:
    raise ValueError("The date is too {}, format should be X/Y/Z".format(
        'short' if len(elems) < 3 else 'long'))

try:
    elems = map(int, elems)
except ValueError:
    raise ...

day = [i for i in elems if 1 <= i <= 31]    
# etc.
return '/'.join(map(str, (day[0], month[0], year[0])))
return '{}/{}/{}'.format(day[0], month[0], year[0])
def front(lst):
    if not lst:
        raise ValueError("The date cannot be valid")
    if len(lst) > 1:
        raise ValueError("Ambiguous date was given")

    return lst[0]

Context

StackExchange Code Review Q#114476, answer score: 6

Revisions (0)

No revisions yet.