Sort the months of the year

19

0

Write a function or program that takes string inputs, fully-spelled, English month names in title case: January, February, March, etc. (null/CR/LF terminated OK, delimited with some non-alpha character if you so choose) and either

  • compares two inputs, returning a Truthy value if the second input is greater (in month order) than the first. Equal values result in a Falsey value

  • or sorts an arbitrary sequence (list, delimited string, etc.) of them in chronological order

(The crux of the challenge is defining a method/expression that gives the correct lexicographical sort. Some languages might have a shorter answer with one or the other)

You cannot use any internal time parsing methods (e.g. strptime) to translate the month name into a number or a pre-canned mapping of month names. Use properties of the strings themselves, a parsimonious look-up table you define, or something clever.

Example

Functioning examples, though the first is prohibited by the rules...

import datetime
def is_later_month(a, b):
    '''
    Example of prohibited code because it relies on language 
    features about how to parse month names
    '''
    return datetime.strptime(a, '%B') < datetime.strptime(b, '%B') 

The below versions are OK though, because we code that info

months = {
    'January':  1, 'February':  2, 'March':      3,
    'April':    4, 'May':       5, 'June':       6,
    'July':     7, 'August':    8, 'September':  9,
    'October': 10, 'November': 11, 'December':  12,
}
def is_later_month(a, b):
    """
    Returns True/False when comparing two months.
    """
    return months[a] < months[b]

Or you could do a sorting function

months = {'as above...'}
def sort_months(l):
    """
    Sorts list and returns it. Different input and output than the above, 
    but equally valid. Sorting versus comparing might be shorter in your
    favorite language.
    """
    return sorted(l, key=lambda x: months[x]) 

Example tests

assert is_later_month('January', 'February')
assert is_later_month('January', 'December')
assert is_later_month('November', 'December')
assert not is_later_month('July', 'July')
assert not is_later_month('October', 'September')

Nick T

Posted 2016-08-11T00:21:45.523

Reputation: 3 197

You cannot use any internal time parsing methods (e.g. strptime) to translate the month name into a number. This is a bit unclear. Can we use a language's predefined literal that contains the months' names? – Luis Mendo – 2016-08-11T00:40:00.203

I'll delete my answer then. But it's still not clear what is allowed and what is not. – Luis Mendo – 2016-08-11T01:02:39.033

The problem is you can't anticipate all those potential tricks, such as predefined arrays. Perhaps a better option would have been to use a less common set of strings, such as made-up names. But it's too late now for that I guess – Luis Mendo – 2016-08-11T01:08:04.197

Is what I'm expressing clear? If Python had a builtin months that was a list of all Month names, I'd want to forbid months[x] < months[y] as an answer. The list of month names has some more peculiar features (varying length, commonality) that make the challenge easier/harder over randomly generated strings. – Nick T – 2016-08-11T01:13:38.247

Yes, I think it's clear. I just fear that there may be other similar cases that you haven't explicitly ruled out (but I don't know which ones) – Luis Mendo – 2016-08-11T01:39:04.510

Can someone please provide (as a comment here) an example of a solution that violates the rule: "You cannot use any internal time parsing methods (e.g. strptime) to translate the month name into a number or a pre-canned mapping of month names." ? – Bevo – 2016-08-12T16:19:23.530

@Bevo there's one in the question above (marked "prohibited", using strptime). The "spirit of the rules" are that the month names are to be treated as arbitrary strings with an arbitrary order. Any a priori knowledge that a language might have about that string:order mapping is prohibited. – Nick T – 2016-08-12T17:14:55.743

Answers

41

Jelly, 19 bytes

11ị“bMAanlseovc”iµÞ

This is a monadic link that takes a list as argument and sorts it. Try it online!

Background

Jelly uses modular, 1-based indexing. If we repeat the months names often enough to obtain 11 characters, we get the following array.

J a n u a r y J a n u
F e b r u a r y F e b
M a r c h M a r c h M
A p r i l A p r i l A
M a y M a y M a y M a
J u n e J u n e J u n
J u l y J u l y J u l
A u g u s t A u g u s
S e p t e m b e r S e
O c t o b e r O c t o
N o v e m b e r N o v
D e c e m b e r D e c

In the 11th (last) column, all characters are different, so we can use them to identify the order of the months.

How it works

11ị“bMAanlseovc”iµÞ  Monadic link. Argument: A (array of months)

                 µ   Combine the preceding chain into a link.
                  Þ  Sort A by that link.
11ị                    Select the 11th character of the month's name.
   “bMAanlseovc”       Find the index of that character in "bMAanlseovc".
                       For 'u' ("January"), this returns 0 (not found).

Dennis

Posted 2016-08-11T00:21:45.523

Reputation: 196 637

1Just curious, how are you ranking the month with "bMAanlseovc"? Index of first character match? – ljeabmreosn – 2016-08-11T02:24:38.943

I've added an explanation. – Dennis – 2016-08-11T02:29:58.383

8Wow, that's really clever! – ljeabmreosn – 2016-08-11T02:31:29.940

15

x86 machine code, 26 25 bytes

Hexdump:

ff 32 8b 01 34 c0 68 30 5f 43 01 59 f7 e1 91 5a
80 f2 c0 f7 e2 3b c8 d6 c3

Assembly code:

    push dword ptr [edx];
    mov eax, [ecx];
    xor al, 0xc0;
    push 0x01435f30;
    pop ecx;
    mul ecx;
    xchg eax, ecx;
    pop edx;
    xor dl, 0xc0;
    mul edx;
    cmp ecx, eax;
    _emit 0xd6;
    ret;

The following hash function happens to put the month names in the proper order (found by brute force):

(x ^ 0xc0) * 0x01435f30

It is applied to the first 4 bytes (32 bits) of the input string, arranged in little-endian order. Then comparing the result and using SALC to set the result register (al):

  • -1 (true) if the months are in order
  • 0 (false) if the second month precedes the first month (or they are the same)

anatolyg

Posted 2016-08-11T00:21:45.523

Reputation: 10 719

4I'm impressed. A very short piece of code without using code-golf specific language. – ShuberFu – 2016-08-11T18:18:21.423

13

Jelly, 15 bytes

Oḅ32 354*%991µÞ

No online interpreter link here because this is one slow submission. The program uses the hashing function 354^(input interpreted as base 32 int) % 991 as the sort key, which happens to give outputs in the right order. The program won't finish any time soon because the results of the exponentiation are giant - for "September", a number with 0.24 quadrillion digits needs to be calculated!

Jelly explanation:

              Þ         Sort by...
             µ          Monadic link consisting of...

O                       Convert month string to code points
 ḅ32                    Take base 32
     354*               Perform 354 to the power of the result
         %991           Take modulo 991

Python proof of concept script - note the use of pow for modular exponentiation, which is much more efficient:

import random

def base_convert(string, base):
    total = 0

    for c in string:
        total = total * base + ord(c)

    return total

def month_hash(month):
    return pow(354, base_convert(month, 32), 991)

months = ["January", "February", "March", "April", "May", "June", "July",
    "August", "September", "October", "November", "December"]
random.shuffle(months)

print(months)
print(sorted(months, key=month_hash))

Sp3000

Posted 2016-08-11T00:21:45.523

Reputation: 58 729

5"No online interpreter link here because this is one slow submission." In which case, you may as well sort the months by hand. ;-) – owacoder – 2016-08-11T12:09:04.857

Maybe you could PR a feature request to optimize pow/mod... – Nick T – 2016-09-12T22:10:37.220

@NickT That's an excellent idea, but unfortunately with the way the interpreter's set up (with each operator defined separately), that might be a little tricky. And Jelly doesn't work well with operators that have more than two arguments, so defining a separate operator wouldn't quite work either... – Sp3000 – 2016-09-13T00:09:18.950

Not a separate operator or anything, just some deeper introspection to see if a power operation is followed by modular division. Sounds easy? :P – Nick T – 2016-09-13T00:11:29.820

5

Python, 64 61 57 bytes

lambda x,y,g='bMAanlseovc'.find:g((x*4)[10])<g((y*4)[10])

The lambda takes two months as input and compares them. Test it on Ideone.

Thanks to @ljeabmreosn for golfing off 3 bytes and paving the way for 3 more!

Dennis

Posted 2016-08-11T00:21:45.523

Reputation: 196 637

2At last, you reveal the secret behind the black magic you used to quickly calculate the correct month in your Jelly answer! – Value Ink – 2016-08-11T01:48:58.587

1Would changing s[10%len(s)] to (4*s)[10] work? – ljeabmreosn – 2016-08-11T02:43:42.780

1@ljeabmreosn That works indeed. Thanks! – Dennis – 2016-08-11T02:46:31.590

1Haven't seen the <strike>ab</strike>use of default arguments in a lambda yet :P – Nick T – 2016-08-11T05:14:47.173

4

Python, 81 71 bytes

lambda x,y,m='anebarprayunulugepctovec':m.index(x[1:3])<m.index(y[1:3])

https://repl.it/CluN/1

Compares the index in m of the second and third letters of two months.

83 byte version to sort a list of months:

lambda x:sorted(x,key=lambda i:'JanFebMarAprMayJunJulAugSepOctNovDec'.index(i[:3]))

atlasologist

Posted 2016-08-11T00:21:45.523

Reputation: 2 945

3

Ruby, 58 bytes

Uses the month sorting trick from @atlasologist's answer.

->a{a.sort_by{|i|"anebarprayunulugepctovec".index i[1,2]}}

The comparison function is a bit longer, at 63 bytes

->a,b{m=->i{"anebarprayunulugepctovec".index i[1,2]};m[a]<m[b]}

Value Ink

Posted 2016-08-11T00:21:45.523

Reputation: 10 608

3

J, 66 65 bytes

Uses the fact that f(m) = 2*(ord(m[0])+ord(m[-1]))//len(m) is a valid function in the limited domain of the 12 months:

>/@:('7/HEäWa<+0'&i.@(3 :'u:<.(#>y)%~+:+/3&u:({.>y),({:>y)')"0)"1

Usage:

   bigger =: >/@:('7/HEäWa<+0'&i.@(3 :'u:<.(#>y)%~+:+/3&u:({.>y),({:>y)')"0)"1
   bigger ('May'; 'March')
1
   bigger ('May'; 'June')
0

(By no means is this the best idea, but I didn't want steal anyone's ranking trick!)

Here is a shorter version using @atlasologist's method:

J, 63 bytes

m=:[:}.3{.]
[:>/(12 2$'anebarprayunulugepctovec')i.([:m[),:[:m]

Usage:

   bigger =: [:>/(12 2$'anebarprayunulugepctovec')i.([:m[),:[:m]
   'January' bigger 'May'
0
   'June' bigger 'May'
1

And a much shorter version using @Dennis's clever method:

J, 34 bytes

>&:('ubMAanlseov'&i.@:{.@:(10&|.))

ljeabmreosn

Posted 2016-08-11T00:21:45.523

Reputation: 341

3

Haskell, 74 bytes

My first code golf, yay! The general idea of this one is inspired by the top answer in Jelly, and the fact that when the month names are cycled, the 11th character is always unique.

e s=head.drop 10$cycle s;a#b=elem(e b)$tail$dropWhile(/=e a)"ubMAanlseovc"

Here is an ungolfed version to see how it works:

order :: String
order = "ubMAanlseovc"

eleventhChar :: String -> Char
eleventhChar
  = head . drop 10 $ cycle

inOrder :: String -> String -> Bool
inOrder m1 m2
  = elem (eleventhChar m2) (tail $ dropWhile (/= eleventhChar m1) order)

The e function represents the eleventhChar function (sadly can't strip off 4 bytes due to the monomorphism restriction I think) and the # infix function corresponds to the inOrder function.

A neat little solution, but there may be ways of shaving off more bytes (I found some just while writing this!)

bower

Posted 2016-08-11T00:21:45.523

Reputation: 41

You could shorten e s=head.drop 10$cycle s like you did in your explanation by using . instead of $: e=head.drop 10.cycle. However using the list index operator !! is even shorter: e=(!!10).cycle – Laikoni – 2016-08-17T09:27:42.830

Great suggestions. Sometimes you just overlook these things. Thanks a lot. Will edit it shortly. – bower – 2016-08-19T17:45:06.417

2

Java, 133 123

Golfed:

boolean f(String a,String b){return h(a)<h(b);}int h(String s){return"anebarprayunulugepctovec".indexOf(s.substring(1,3));}

I was searching for a clever technique like in the assembler answer, but it was taking too long to figure out so I went with the same technique everyone else used.

Ungolfed:

import java.util.Random;

public class SortTheMonthsOfTheYear {

  public static void main(String[] args) {
    // @formatter:off
    String[] MONTHS = new String[] {
        "January", "February", "March",
        "April",   "May",      "June",
        "July",    "August",   "September",
        "October", "November", "December"
    };
    // @formatter:on

    Random r = new Random();
    for (int i = 0; i < 100; ++i) {
      int m1 = r.nextInt(MONTHS.length);
      int m2 = r.nextInt(MONTHS.length);
      System.out.println("Input: " + MONTHS[m1] + " < " + MONTHS[m2]);
      System.out.println("Expected: " + (m1 < m2));
      System.out.println("Actual:   " + new SortTheMonthsOfTheYear().f(MONTHS[m1], MONTHS[m2]));
      System.out.println();
    }
  }

  // Begin golf
  boolean f(String a, String b) {
    return h(a) < h(b);
  }

  int h(String s) {
    return "anebarprayunulugepctovec".indexOf(s.substring(1, 3));
  }
  // End golf

}

user18932

Posted 2016-08-11T00:21:45.523

Reputation:

You could use substring instead if charAt – anatolyg – 2016-08-11T20:01:18.743

@anatolyg thanks, I am not sure how that one escaped me. I was also able to remove "" + since there are no raw chars anymore. – None – 2016-08-11T20:28:40.160

2

ARM machine language on Linux 44 40 bytes

e28fc001     add ip, pc, #1
e12fff1c     bx ip
6803         ldr r3, [r0, #0]
6808         ldr r0, [r1, #0]
4a05         ldr r2, [pc, #20]
f08303dd     eor.w r3, r3, #221
f08000dd     eor.w r0, r0, #221
4353         muls r3, r2
4350         muls r0, r2
4283         cmp r3, r0
bfac         ite ge
2000         movge r0, #0
2001         movlt r0, #1
4770         bx lr
2f68f24c

I used a different hash function than anatolyg's solution and tried to use thumb instructions to save a few bytes (though I blew 8 bytes entering thumb mode).

You can try this out on a Raspberry Pi or Android device with GNURoot.

int main(int argc,char**argv){
return ((int(*)(char*,char*))"\
\1\xc0\x8f\xe2\
\x1c\xff\x2f\xe1\
\3\x68\x8\x68\
\5\x4a\x83\xf0\
\xdd\3\x80\xf0\
\xdd\x43\x53\x43\
\x50\x4a\x83\x42\
\xac\bf\0\x20\
\1\x20\x70\x47\
\x4c\xf2\x68\x2f\
")(argv[1],argv[2]);}

To run enter something like

$ ./foo January February; echo $?

The current version now handles the equality case (and others) correctly.

ceilingcat

Posted 2016-08-11T00:21:45.523

Reputation: 5 503

I think you don't need code that explicitly switches into Thumb mode. From what I remember, you only need to tell the linker your procedure is in thumb mode, and the linker will set the LSB in your procedure's address to 1, so the processor will switch to Thumb mode automatically when your code is called. – anatolyg – 2016-08-31T10:16:39.197

Also, what does bfac do? – anatolyg – 2016-08-31T10:24:06.197

@anatolyg ite ge conditionally executes the next instruction (movge r0, #0) if r3 >= r0, otherwise the instruction following that is executed (movlt r0, #1). I think there is room to knock off a couple of bytes here but I haven't had time to work on this :-) – ceilingcat – 2016-09-12T22:59:04.420

1

Perl 6, 55 bytes

*.sort({index 'anebarprayunulugepctovec',.substr(1,2)})

It would require a few more bytes for the comparison versions:

{[<] @_».&{index 'anebarprayunulugepctovec',.substr(1,2)}}
{[<] .map: {index 'anebarprayunulugepctovec',.substr(1,2)}}

Test:

#! /usr/bin/env perl6
use v6.c;
use Test;

my @months = <
  January February March April May June July
  August September October November December
>;

my &month-sort = *.sort({index 'anebarprayunulugepctovec',.substr(1,2)});

plan 100;

for ^100 {
  # 「.pick(*)」 returns all elements in random order
  is-deeply month-sort(@months.pick(*)), @months.List;
}

Brad Gilbert b2gills

Posted 2016-08-11T00:21:45.523

Reputation: 12 713

1

Haskell, 118 characters

data M=Ju|Fr|Mc|Ai|My|Je|Jy|Au|St|Oo|Ne|De deriving(Ord,Eq,Read)
r=read.([head,last]<*>).lines.take 4
a#b=(r a::M)<r b

Uses the fact that each month name is unique in its first and fourth characters (or 3rd for May) to define a data type that can be automatically parsed and compared by the language. The 'r' function converts a string by grabbing the first four characters (or fewer), then just picking the first and last. Then 'a#b' is an operator to compare the values:

*Main> "June" # "March"
False
*Main> "June" # "July"
True
*Main> "January" # "July"
True
*Main> "January" # "November"
True
*Main> "December" # "November"
False

Could probably be done in a more efficient way, but I wanted to try doing it using a useful data type to represent the months.

Jules

Posted 2016-08-11T00:21:45.523

Reputation: 211

1

Python 83 82 bytes

lambda x,y,m=(lambda a:'2&9<@FD6A?L%'.find(chr(sum(map(ord,a[:3]))%77))):m(x)<m(y)

Test: https://repl.it/repls/TimelyDecimalBrowsers

Gets the sum of the first 3 chars and creates a single char to search.

Baris

Posted 2016-08-11T00:21:45.523

Reputation: 111

1

PowerShell, 96 88 63 bytes

$input|Sort{'anebarprayunulugepctovec'.IndexOf((-join$_[1,2]))}

e.g.

PS C:\Code> 'February', 'January', 'December', 'April' | .\monthsort.ps1
January
February
April
December

Now does the second challenge of sorting a list into order; previous versions did the comparison of two month test:

v2.
$l,$r=$args|%{-join$_[1,2]};($d='anebarprayunulugepctovec').indexof($l)-lt$d.IndexOf($r)

v1.
$l,$r=$args|%{-join$_[1,2]};$r-match('an|eb|ar|pr|ay|un|ul|ug|ep|ct|ov|ec'-split$l)[1].Trim('|')

e.g.

PS C:\code> .\Test-MonthsInOrder.ps1 January February
True

Based on the second two characters in the month name.

TessellatingHeckler

Posted 2016-08-11T00:21:45.523

Reputation: 2 412

0

Javascript, 118 bytes

u=p=>{p=p.split` `.sort();c=[];for(i=0;i<12;i++){c.push(p["4 3 7 0 8 6 5 1 11 10 9 2".split` `[i]]);}return c.join` `}

Could be golfed more, probobly by getting rid of c and using array.map, but this is what I have for now...

Bald Bantha

Posted 2016-08-11T00:21:45.523

Reputation: 463

for(i=0;i<12;)c.push(p[[4,3,7,0,8,6,5,1,11,10,9,2][i++]]); – pinkfloydx33 – 2016-08-12T17:27:29.203

0

Bash, 101 bytes

this is function like is_later

f(){ s=ubMAanlseovc a=$1$1$1 b=$2$2$2 c=${a:10:1} d=${b:10:1} e=${s%$c*} f=${s%$d*};((${#e}<${#f}));}

test

$ f January December && echo later || echo not later
not later

user58494

Posted 2016-08-11T00:21:45.523

Reputation: 1

0

k4, 29

{x@<"ubMAanlseovc"?(*|11#)'x}

A port of @Dennis's Jelly answer.

This is the sorter, not the comparator; interestingly, the comparator is trivially implementable by the same algorithm, and only one byte longer:

{(<)."ubMAanlseovc"?(*|11#)'x}

Aaron Davies

Posted 2016-08-11T00:21:45.523

Reputation: 881

0

Bash + coreutils, 94 bytes93 bytes

s(){ x=FMAyulgSOND;for y;{ rev<<<${y:0:3}|tr -d -C $x|tr $x C-M;echo B$y;}|sort|cut -f2 -dB;}

This is an attempt to come up with a transform that sorts lexicographically. If you look closely at the transform key FMAyulgSOND you can see the months February through December (January becomes empty after the transform; it's pulled to top by use of 'B' as the separator). Reversing, truncating, and removing non-keyed letters allow this trick to be pulled off.

90 bytes using C Locale

s(){ x=FMAyulgSOND;for y;{ rev<<<${y:0:3}|tr -d -C $x|tr $x C-M;echo \␉$y;}|sort|cut -f2;}

...where ␉ is the tab character.

80 bytes using C Locale

s(){ x=anebarprayunulugepctovec;for y;{ echo ${x%${y:1:2}*}\␉$y;}|sort|cut -f2;}

...using @atlasolog's method. Bound to be a way to use this approach to work with more locales.

Test/Usage

s December November October September August July June May April March February January

outputs:

January
February
March
April
May
June
July
August
September
October
November
December

H Walters

Posted 2016-08-11T00:21:45.523

Reputation: 1 536