21
\$\begingroup\$

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')
\$\endgroup\$
7
  • \$\begingroup\$ 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? \$\endgroup\$
    – Luis Mendo
    Aug 11, 2016 at 0:40
  • \$\begingroup\$ I'll delete my answer then. But it's still not clear what is allowed and what is not. \$\endgroup\$
    – Luis Mendo
    Aug 11, 2016 at 1:02
  • \$\begingroup\$ 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 \$\endgroup\$
    – Luis Mendo
    Aug 11, 2016 at 1:08
  • \$\begingroup\$ 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. \$\endgroup\$
    – Nick T
    Aug 11, 2016 at 1:13
  • \$\begingroup\$ 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) \$\endgroup\$
    – Luis Mendo
    Aug 11, 2016 at 1:39

21 Answers 21

44
\$\begingroup\$

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).
\$\endgroup\$
3
  • 1
    \$\begingroup\$ Just curious, how are you ranking the month with "bMAanlseovc"? Index of first character match? \$\endgroup\$ Aug 11, 2016 at 2:24
  • \$\begingroup\$ I've added an explanation. \$\endgroup\$
    – Dennis
    Aug 11, 2016 at 2:29
  • 9
    \$\begingroup\$ Wow, that's really clever! \$\endgroup\$ Aug 11, 2016 at 2:31
15
\$\begingroup\$

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)
\$\endgroup\$
1
  • 5
    \$\begingroup\$ I'm impressed. A very short piece of code without using code-golf specific language. \$\endgroup\$
    – ShuberFu
    Aug 11, 2016 at 18:18
14
\$\begingroup\$

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))
\$\endgroup\$
4
  • 5
    \$\begingroup\$ "No online interpreter link here because this is one slow submission." In which case, you may as well sort the months by hand. ;-) \$\endgroup\$
    – owacoder
    Aug 11, 2016 at 12:09
  • \$\begingroup\$ Maybe you could PR a feature request to optimize pow/mod... \$\endgroup\$
    – Nick T
    Sep 12, 2016 at 22:10
  • \$\begingroup\$ @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... \$\endgroup\$
    – Sp3000
    Sep 13, 2016 at 0:09
  • \$\begingroup\$ Not a separate operator or anything, just some deeper introspection to see if a power operation is followed by modular division. Sounds easy? :P \$\endgroup\$
    – Nick T
    Sep 13, 2016 at 0:11
6
\$\begingroup\$

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!

\$\endgroup\$
4
  • 2
    \$\begingroup\$ At last, you reveal the secret behind the black magic you used to quickly calculate the correct month in your Jelly answer! \$\endgroup\$
    – Value Ink
    Aug 11, 2016 at 1:48
  • 1
    \$\begingroup\$ Would changing s[10%len(s)] to (4*s)[10] work? \$\endgroup\$ Aug 11, 2016 at 2:43
  • 1
    \$\begingroup\$ @ljeabmreosn That works indeed. Thanks! \$\endgroup\$
    – Dennis
    Aug 11, 2016 at 2:46
  • 1
    \$\begingroup\$ Haven't seen the <strike>ab</strike>use of default arguments in a lambda yet :P \$\endgroup\$
    – Nick T
    Aug 11, 2016 at 5:14
4
\$\begingroup\$

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]))
\$\endgroup\$
0
4
\$\begingroup\$

Julia 1.0, 28 bytes

~m=hash("lWbAj"*m)
m^n=~m<~n

Try it online!

This solution prepends a string to each month before calculating the hash. The magic values in this and the other solutions were found by brute force.

Simple hash, 29 bytes

~m=hash(m)%69675331
m^n=~m<~n

Try it online!

String repetition + modulo, 29 bytes

~m=hash(m^69)%22670
m*n=~m<~n

Try it online!

\$\endgroup\$
3
\$\begingroup\$

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]}
\$\endgroup\$
3
\$\begingroup\$

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&|.))
\$\endgroup\$
3
\$\begingroup\$

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!)

\$\endgroup\$
2
  • \$\begingroup\$ 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 \$\endgroup\$
    – Laikoni
    Aug 17, 2016 at 9:27
  • \$\begingroup\$ Great suggestions. Sometimes you just overlook these things. Thanks a lot. Will edit it shortly. \$\endgroup\$
    – bower
    Aug 19, 2016 at 17:45
2
\$\begingroup\$

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

}
\$\endgroup\$
2
  • \$\begingroup\$ You could use substring instead if charAt \$\endgroup\$
    – anatolyg
    Aug 11, 2016 at 20:01
  • \$\begingroup\$ @anatolyg thanks, I am not sure how that one escaped me. I was also able to remove "" + since there are no raw chars anymore. \$\endgroup\$
    – user18932
    Aug 11, 2016 at 20:28
2
\$\begingroup\$

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.

\$\endgroup\$
3
  • \$\begingroup\$ 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. \$\endgroup\$
    – anatolyg
    Aug 31, 2016 at 10:16
  • \$\begingroup\$ Also, what does bfac do? \$\endgroup\$
    – anatolyg
    Aug 31, 2016 at 10:24
  • \$\begingroup\$ @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 :-) \$\endgroup\$
    – ceilingcat
    Sep 12, 2016 at 22:59
1
\$\begingroup\$

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;
}
\$\endgroup\$
1
\$\begingroup\$

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.

\$\endgroup\$
1
\$\begingroup\$

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.

\$\endgroup\$
1
\$\begingroup\$

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.

\$\endgroup\$
1
\$\begingroup\$

awk, 73 bytes

function __(_){return index("=anebarprayunulugepctovec",substr(_,2,2))/2}

The full function, for any awk and any locale setting, 73 bytes

echo -n 'function __(_){return index("=anebarprayunulugepctovec",substr(_,2,2))/2}' |
gwc -lcm
      0      73      73

The core components only, 50 bytes

echo -n   'index("=anebarprayunulugepctovec",substr(_,2,2))/2' | 
gwc -lcm 
      0      50      50

This is better than the shorter trick involving the 11th letter, because that one requires all inputs to be full length names. this one works for both 3-letter abbreviated ones and their full length counterparts

\$\endgroup\$
0
\$\begingroup\$

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...

\$\endgroup\$
1
  • \$\begingroup\$ for(i=0;i<12;)c.push(p[[4,3,7,0,8,6,5,1,11,10,9,2][i++]]); \$\endgroup\$ Aug 12, 2016 at 17:27
0
\$\begingroup\$

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
\$\endgroup\$
0
\$\begingroup\$

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}
\$\endgroup\$
0
\$\begingroup\$

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
\$\endgroup\$
0
\$\begingroup\$

Thunno, \$ 29 \log_{256}(96) \approx \$ 23.87 bytes

De11AH"bMAanlseovc"AhEZZz:.AK

Attempt This Online!

Port of Dennis's Jelly answer.

Explanation

De          # Duplicate and map over the list
  11AH      # Get the 11th item of the string (1-indexed, modular)
      "..." # Push the string of each month's 11th letter
Ah          # Get the index in this string (January returns -1)
  E         # End map
   ZZ       # Zip with the original list
     z:     # Sort this list
       .AK  # And get the last item of each
            # Implicit output
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.