I’ve been doing the Weekly
Challenges. The
latest
involved date calculations and numerical decompositions. (Note
that this is open until 14 November 2021.)
Task 1: Workdays
You are given a year, $year
in 4-digits form.
Write a script to calculate the total number of workdays in the given year.
For the task, we consider Monday - Friday as workdays.
So there's a limited number of year configurations: ending on each
weekday (Monday-Sunday), and leap year or not. In every case, the
first 364 days of the year provide 52 weeks and thus 260 working days.
In a non-leap year. 1 January-30 December is 364 days, so the only one
that matters is 31 December; the total workdays will be 261 if that
falls on a Monday to Friday, 260 otherwise.
For a leap year, 30 and 31 December are both relevant. If 31 December
falls on a Tuesday to Friday, add 2 working days to the base of 260;
Saturday or Monday, 1; Sunday, 0.
And we already have a weekday-of-31-December calculator from last
week's challenge 1.
So (in the Raku) we recycle that:
sub p($y) {
return ($y+floor($y/4)-floor($y/100)+floor($y/400))%7;
}
Add a leap-year calculator:
sub leapyear($y) {
return ($y%4 == 0 && ($y%100 != 0 || $y%400 == 0));
}
(In languages with a divmod
function one could combine the two, but
I didn't bother.)
Then use both functions to generate an index into a lookup table of
extra days. (Note that p()
gives 1 for a Monday, etc.)
sub workdays($y) {
my $i=p($y);
if (leapyear($y)) {
$i+=7;
}
return 260+[0,1,1,1,1,0,0,1,2,2,2,2,1].[$i];
}
In PostScript, the leap year calculator:
/leapyear {
dup dup
4 mod 0 eq {
100 mod 0 ne exch
400 mod 0 eq or
{
true
} {
false
} ifelse
} {
pop pop false
} ifelse
} bind def
And the same calculation and offset.
/workdays {
dup
p exch
leapyear {
7 add
} if
[ 0 1 1 1 1 0 0 1 2 2 2 2 1 ] exch get 260 add
} bind def
Task 2: Split Number
You are given a perfect square.
Write a script to figure out if the square root of the given number
is same as sum of 2 or more splits of the given number.
The examples make it clear that "splits" are additive: i.e. express
the number as a string of digits, insert "+" signs between some of
them, and evaluate the resulting expression.
This is more or less what I do: with digits ABC, I could place a +
sign before B, before C, or both. Using a bitmask, I generate series
of possible locations for these – then prepend 0, append the index one
past the last character, and use them in pairs as dividing points in
the digit string. Thus with ABC I'd generate:
- 0 1 3 = A + BC
- 0 2 3 = AB + C
- 0 1 2 3 = A + B + C
Here's the Ruby:
def splnum(n)
k=Integer.sqrt(n)
if k*k!=n then
return 0
end
I could do string slicing, but I'm happier with arrays.
d=n.to_s.split("")
dl=d.length-1
This will fail in the case of no splits at all (n=1) but I don't
really care.
1.upto((1<<dl)-1) do |s|
sa=[0]
0.upto(dl-1) do |i|
if s & (1<<i) > 0 then
sa.push(i+1)
end
end
sa.push(dl+1)
With the list of splits, slice the array, join and parse each slice
into an integer, and add them. (Actually there's a better trick for
that, but I didn't know it at the time; see next week's post.)
c=0
0.upto(sa.length()-2) do |j|
c+=d.slice(sa[j],sa[j+1]-sa[j]).join("").to_i
end
if c==k then
return 1
end
end
return 0
end
The numbers for which this is valid are of course
A104113 "Numbers which when chopped into
one, two or more parts, added and squared result in the same number";
that doesn't have much to say, and nor does its square root
A038206 "Can express a(n) with the digits
of a(n)^2 in order, only adding plus signs", though it goes give a
recursive generator in Python.
And yes, it works in PostScript too, though it's a bit longer and
needs some library functions (i2s
and apush
) written for earlier
challenges.
/splnum {
/n exch def
/ret 0 def
n sqrt cvi /k exch def
k k mul n ne {
0 exit
} if
/d n i2s def
/dl d length 1 sub def
1 1 1 dl bitshift 1 sub {
/sa [ 0 ] def
/s exch def
0 1 dl 1 sub {
/i exch def
s 1 i bitshift and 0 gt {
/sa sa i 1 add apush def
} if
} for
/sa sa dl 1 add apush def
/c 0 def
0 1 sa length 2 sub {
/j exch def
d
sa j get
sa j 1 add get sa j get sub
getinterval cvi
c add /c exch def
} for
c k eq {
/ret 1 def
exit
} if
} for
ret
} bind def
Full code on
github.