Is there a programmatic way to elaborate the 'half-winds' in raku?

Assuming we have the principal winds to hand:

my @principals = <N NE E SE S SW W NW>;

Then we can follow the definition a la Wikipedia:

The name of each half-wind is constructed by combining the names of the principal winds to either side

This means we want to take principal winds in overlapping pairs, which rotor could do fairly neatly:

say @principals.rotor(2 => -1);

Which gives us:

((N NE) (NE E) (E SE) (SE S) (S SW) (SW W) (W NW))

Which sadly has an off by one problem, because it misses (NW, N). OK, we can include N again, at the cost of a little beauty:

say @principals[*,0].flat.rotor(2 => -1)

Giving:

((N NE) (NE E) (E SE) (SE S) (S SW) (SW W) (W NW) (NW N))

If we join them:

say @principals[*,0].flat.rotor(2 => -1).map(*.join)

We get:

(NNE NEE ESE SES SSW SWW WNW NWN)

Which isn't right yet, because the next thing the article says is:

with the cardinal wind coming first and the intercardinal wind second

The cardinal winds are the one character ones, which can be fixed with a sort:

say @principals[*,0].flat.rotor(2 => -1).map(*.sort(*.chars).join)

Which finally looks about right:

(NNE ENE ESE SSE SSW WSW WNW NNW)

Except that the half winds are these placed between the principal winds, which can be solved with zipping them and flattening the result:

say flat @principals Z @principals[*,0].flat.rotor(2 => -1).map(*.sort(*.chars).join)

Finally giving us:

(N NNE NE ENE E ESE SE SSE S SSW SW WSW W WNW NW NNW)

If we want it shorter, the duplicate mention of @principals can go away with a given:

say flat $_ Z .[*,0].flat.rotor(2 => -1).map(*.sort(*.chars).join) given @principals

The .join can just become list stringification:

say flat $_ Z .[*,0].flat.rotor(2 => -1).map(~*.sort(*.chars)) given @principals:

And the inner flattening can be replaced with a use of the | slip operator:

say flat $_ Z (|$_,.[0]).rotor(2 => -1).map(~*.sort(*.chars)) given @principals;

Which is still longer than just listing them out in the first place, which is what I'd probably do, but it was fun trying to beat that anyway...


FWIW, in this case I would go for readability of the code, which I think your original code is the most readable you can get.

But if you want to get algorithmic, I would work off of the @halfs using an array slice using a sequence:

my @halfs = <N NNE NE ENE E ESE SE SSE S SSW SW WSW W WNW NW NNW>;
my @mids  = @halfs[1,3...*];
my @ords  = @halfs[0,2...*];
my @cards = @halfs[0,4...*];

say @cards; #[N E S W]
say @ords;  #[N NE E SE S SW W NW] 
say @mids;  #[NNE ENE ESE SSE SSW WSW WNW NNW]
say @halfs; #[N NNE NE ENE E ESE SE SSE S SSW SW WSW W WNW NW NNW]

Leaving the quarter winds as an exercise for the reader, but basically that would be using the quarter winds as the base, and work off of that in a similar fashion.


TL;DR For no good reason, this nanswer goes after just the bonus points.

my \quadrant = <N NbE NNE NEbN NE NEbE ENE EbN E>;

my @quarts = |quadrant,
             |quadrant.reverse.map( *.trans: 'NE'=>'SE'),
             |quadrant\       .map( *.trans: 'NE'=>'SW'),
             |quadrant.reverse.map( *.trans: 'NE'=>'NW');

@quarts .=unique .batch(8) .map: *.say;

# (N NbE NNE NEbN NE NEbE ENE EbN)
# (E EbS ESE SEbE SE SEbS SSE SbE)
# (S SbW SSW SWbS SW SWbW WSW WbS)
# (W WbN WNW NWbW NW NWbN NNW NbW)

Perhaps I get some bonus points for explaining the above:

  • Prefix | "slips" the value on its right; the net effect of using it on each of the four quadrants is that the resulting list contains 32 elements rather than 4.

  • .reverse reverses a list, corresponding to the way the wind points notation reverses between NE/SW and SE/NW.

  • .map(...) applies a function or lambda to each element in its invocant list.

  • *.trans: 'NE'=>'SE' is a lambda that transliterates each N character to S and each E to E. The redundant E is there for the first transliteration because it felt a bit clearer than dropping it; I could also have added *.trans: 'NE'=>'NE' for the first quadrant but that felt less clear. NB. I use trans because it's succinct, but subst can always do the same stuff a trans does, even if it takes a lot more lines, and in Rakudo 2020.12 subst is around 5x-10x faster for almost all cases.

  • The \ in |quadrant\ .map... is an "unspace", a way to indicate to the compiler that it should parse the code as if the slash and whitespace wasn't there. Without it the | takes just quadrant as its operand. The unspace makes it take the whole quadrant.map( *.trans: 'NE'=>'SW') expression as its operand. (In my original version of this answer my code was wrong because I lined up the .map without the unspace.)

  • The .= in .=unique applies a method and then assigns the result back to its invocant, mutating it in place.

  • unique removes any elements of a list that are repeated. Without it there would be repeats of the four cardinal points (<N E S W>).

  • .batch(8) batches the points back up into groups of 8 corresponding to the four quadrants ready for pretty printing.

Bonus points

The rest of this answer is merely justification of my shameless bonus point scavenging.

From your Q:

bonus points for quarter winds!

From Liz's answer:

Leaving the quarter winds as an exercise for the reader

The 32 points of "quarter winds"

As Liz notes, a simple superset solution that includes hers and quarter winds would just entail:

using the quarter winds as the base, and work off of that in a similar fashion

In other words, doing the quarter winds bonus exercise could just entail the trivial to write, easily comprehensible, and sensibly maintainable thing, namely writing out the 32 points in the quarter wind list just as Liz did for the 16 points of the half winds. (But then I'd get no bonus points.)

Extending this to what are confusingly called half points (64 points) and quarter points (128), the sensible thing to do for them would also be to just write them out by hand, complete with Unicode fraction characters, which any PL that supports Unicode source code and a "quote words" like feature (eg Raku) makes trivial. I'm pretty sure anyone reading your code will thank you for keeping it simple if you do that.

But I wanted to have some fun, and stick with your programmatic theme, and try scavenge some "bonus points". Hence my code at the start.


I leave creation of even more unnecessarily complex cryptic cumbersome computational constructions of half/quarter points (complete with support for aliases and the differing conventions used around the world for these finer compasses) as an exercise for readers...

Tags:

Raku