112 Bouncy numbers.sf 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. #!/usr/bin/ruby
  2. # Author: Trizen
  3. # Date: 12 April 2023
  4. # https://github.com/trizen
  5. # Generate numbers with increasing and decreasing digits, in a given base.
  6. # See also:
  7. # https://projecteuler.net/problem=112
  8. # Runtime: 1 minute, 19 seconds.
  9. func increasing_numbers(limit, base=10) {
  10. func f(n, min_d) {
  11. var seq = [n]
  12. for d in (min_d ..^ base) {
  13. var v = (n*base + d)
  14. v <= limit || break
  15. seq << __FUNC__(v, d)...
  16. }
  17. return seq
  18. }
  19. map(1..min(limit,base-1), {|d| f(d, d) }).flat.sort
  20. }
  21. func decreasing_numbers(limit, base=10) {
  22. func f(n, max_d) {
  23. var seq = [n]
  24. for d in (0 .. max_d) {
  25. var v = (n*base + d)
  26. v <= limit || break
  27. seq << __FUNC__(v, d)...
  28. }
  29. return seq
  30. }
  31. map(1..min(limit,base-1), {|d| f(d, d) }).flat.sort
  32. }
  33. func increasing_numbers_count(limit, base=10) {
  34. func f(n, min_d) {
  35. var count = 1
  36. for d in (min_d ..^ base) {
  37. var v = (n*base + d)
  38. v <= limit || break
  39. count += __FUNC__(v, d)
  40. }
  41. return count
  42. }
  43. sum(1..min(limit,base-1), {|d| f(d,d) })
  44. }
  45. func decreasing_numbers_count(limit, base=10) {
  46. func f(n, max_d) {
  47. var count = 1
  48. for d in (0 .. max_d) {
  49. var v = (n*base + d)
  50. v <= limit || break
  51. count += __FUNC__(v, d)
  52. }
  53. return count
  54. }
  55. sum(1..min(limit,base-1), {|d| f(d,d) })
  56. }
  57. func non_bouncy_count_slow(limit, base=10) {
  58. increasing_numbers(limit, base) + decreasing_numbers(limit, base) -> uniq.len
  59. }
  60. func non_bouncy_count(limit, base=10) {
  61. if (limit < base) {
  62. return max(0, limit)
  63. }
  64. var t = (increasing_numbers_count(limit, base) + decreasing_numbers_count(limit, base))
  65. t -= ((base-1) * (limit.len(base)-1))
  66. var r = (base**limit.len(base) - 1)/(base-1)
  67. for d in (1 ..^ base) {
  68. r*d > limit && break
  69. --t
  70. }
  71. return t
  72. }
  73. assert_eq(
  74. 2e2.of(non_bouncy_count),
  75. 2e2.of(non_bouncy_count_slow)
  76. )
  77. assert_eq(
  78. non_bouncy_count_slow(1e3),
  79. non_bouncy_count(1e3),
  80. )
  81. assert_eq(
  82. non_bouncy_count_slow(23870),
  83. non_bouncy_count(23870)
  84. )
  85. var target = 0.99
  86. say ":: Searching for an upper-bound, using binary search..."
  87. var upper_bound = {|k|
  88. (1 - non_bouncy_count(k)/k) <=> target
  89. }.bsearch(100)
  90. say ":: Upper-bound: #{upper_bound}"
  91. var non_bouncy = Set(increasing_numbers(upper_bound)+decreasing_numbers(upper_bound) -> sort.uniq...)
  92. var nbc = non_bouncy.len
  93. assert_eq(nbc, non_bouncy_count(upper_bound))
  94. for (var k = upper_bound; k > 1; --k) {
  95. if ((1 - nbc/k) == target) {
  96. say ":: Candidate: #{k}"
  97. }
  98. if (non_bouncy.has(k)) {
  99. --nbc
  100. }
  101. }