There is a bug, or an improvement, depending on how you want to see it.
This:
def foo
(2..).each do |x|
return 1
end
end
typeof(foo) # => Int32 | Nil
The bug is that for a loop that never ends, the compiler think it ends, then we get Nil in the type.
The compiler knows (2..).each { ... } will never end. It can even know that at compile time (the E in Range(B, E) is Nil, there are already some checks around that). If there’s no end, we can generate while true as a loop. Then the compiler knows the loop never ends. Then the type of foo becomes Int32.
This compiles|runs using loop do ... versus (2..).each do |pow|.
WHY?
They are functionally equivalent (to a user), but obviously not to the compiler.
def palindromegen(digit, count)
nums = [] of UInt64
nn = digit * 11
num = 0.to_u64
pow = 1
loop do pow += 1 # replaces: (2..).each do |pow|
base = 10_u64**(pow >> 1)
n1 = base * digit
n2 = base * (digit + 1)
n1.step(to: n2 - 1, by: 10) do |n1|
ns = (n1 // 10).to_s
(n1..n1 + 9).each_with_index do |pal, i|
num = (pal.to_s + pal.to_s.reverse).to_u64 if pow.odd?
num = (ns + "0" + ns.reverse).to_u64 + base * i if pow.even?
nums << num if num % nn == 0
return nums if nums.size == count
end
end
end
end
count = 20
puts "First 20 palindromic gapful numbers 100 ending with:"
(1..9).each { |digit| print "#{digit} : #{palindromegen(digit, count)}\n" }
count = 100
puts "\nLast 15 of first 100 palindromic gapful numbers ending in:"
(1..9).each { |digit| print "#{digit} : #{palindromegen(digit, count).last(15)}\n"
This also compiles|runs using (2..).select do |pow|
def palindromegen(digit, count)
nums = [] of UInt64
nn = digit * 11
num = 0.to_u64
(2..).select do |pow|
base = 10_u64**(pow >> 1)
n1 = base * digit
n2 = base * (digit + 1)
n1.step(to: n2 - 1, by: 10) do |n1|
ns = (n1 // 10).to_s
(n1..n1 + 9).each_with_index do |pal, i|
num = (pal.to_s + pal.to_s.reverse).to_u64 if pow.odd?
num = (ns + "0" + ns.reverse).to_u64 + base * i if pow.even?
nums << num if num % nn == 0
return nums if nums.size == count
end
end
end
end
This also works.
def palindromegen(digit, count)
nums = [] of UInt64
nn = digit * 11
num = 0.to_u64
pow = 1
while true
pow += 1
base = 10_u64**(pow >> 1)
n1 = base * digit
n2 = base * (digit + 1)
n1.step(to: n2 - 1, by: 10) do |n1|
ns = (n1 // 10).to_s
(n1..n1 + 9).each_with_index do |pal, i|
num = (pal.to_s + pal.to_s.reverse).to_u64 if pow.odd?
num = (ns + "0" + ns.reverse).to_u64 + base * i if pow.even?
nums << num if num % nn == 0
return nums if nums.size == count
end
end
end
end
So there’s something intrinsically different to the compiler about (2..).each.
This is a fruitless back-and-forth because you’re not focusing on what I’m raising, as a language user.
I consider the behavior in this example of the endless range form (2..).eacha bug because it doesn’t behave as I expect and as I desire it to behave!.
You don’t care that I consider it a bug, or even just undesirable behavior, so I as a user have to bear the consequences of what I consider is a poor design decision, which can be fixed.
So there’s no point for me to discuss this anymore since I figured out how to write equivalent functional code that performs as I want.
Hey @jhass I like your struct implementation, and ran it over all the outputs examples. It has comparable speed with my version (+|- 1|2 secs), and shows off Crystal’s OOP ability. Do you mind if I post it on Rosetta Code? I’ll list you as it contributor in that thread.
BTW: I corrected a minor error in a loop: 9.times should be 10.times
Why did you rename @gap to @nn? I agree it’s not a good name, mainly because I had no real context on what this is even solving, but better than @nn certainly. If you feel the need to explain what it is in a comment, rename the variable instead. Code is write once, read often. So from the comment, sounds like @gap_divisor or something might be a good name.
What does lo stand for? I avoid abbreviations that are not 110% clear in the context.
I’m not sure why you feel the need to choose different variable names for upper_half and palindrome_half, reassigning there does not create a union since the variable is not captured anywhere. It’s the same value, just in different representations that are never needed at the same time. I even considered to just add a .map(&.to_s) initially, but wanted to avoid allocating the two iterators.
Also actually let’s use .skip(count - keep).first(keep) and maybe extract the printing to a method instead of copy pasting it five times. The initial two variants seems different enough to keep as is, but looking at those it seems too similar too often.
In general I harmonized all 3 versions (my 2 and yours) and used names that best (to me) conceptually portrayed|explained their roles in the algorithm, and ended up using 95% of your naming conventions. @gap is misleading, and @nn better, by showing the form of the divisor for each digit (considered @dd, but eh…). Basically, I wanted to use short|concise|clear names. The comments are more important than the names, like this_lo|next_lo, which really didn’t need to be changed from n1|n2 in my original versions, because they’re only used once. Ultimately, I chose the names that made the most sense to me, and made me smile.
Tried .skip(count - keep).first(keep) too, but it provided no performance|memory advantages I could detect, so I kept the shorter code. (Though good to know it’s doable.)
I also added the Ruby versions, which are about a 98% literal translation.
The bottom line is, this was a good learning exercise for me. I think all the versions now are much easier to comprehend, and illustrate different capabilities of Crystal|Ruby. And I’m finished with this task.