├── .gitignore ├── histo.png ├── image.png ├── histo_filtered.png ├── README.md ├── index.html ├── graph.json └── persistence.jl /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode -------------------------------------------------------------------------------- /histo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wikunia/Persistence/master/histo.png -------------------------------------------------------------------------------- /image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wikunia/Persistence/master/image.png -------------------------------------------------------------------------------- /histo_filtered.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wikunia/Persistence/master/histo_filtered.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Visualization 2 | 3 | This repository visualizes the multiplicative persistence. 4 | Inspired by this Numberphile video: [YouYube Numberphile](https://www.youtube.com/watch?v=Wim9WJeDTHQ&feature=youtu.be&fbclid=IwAR07tiGLYsmdzmFKYFJBKzdVQnYowwfR5VM9eFrJaZhTPEYalMFJvRLIog8). 5 | Feel free to read more about the code on my blog [OpenSourcES](http://opensourc.es/blog/persistence) 6 | 7 | The [visualization](https://wikunia.github.io/Persistence/) shows the persistence graph for the numbers up to 100. 8 | Color indicates the number of steps and the connection shows the next step. You can hover over a node to see the number and the path it takes. 9 | 10 | ![visual](image.png) 11 | 12 | The code used for creating the graph can be found in: `persistence.jl` and can be called with `create_bf_list()` which creates the `graph.json`. 13 | 14 | Some more visualizations: 15 | This shows a histogram of the persistence of all "ascending" numbers with up to 20 digits. "ascending" means that 23 is okay but 32. For multiplicative persistence they are the same anyway. 16 | 17 | ![histo ascending](histo.png) 18 | 19 | The following histogram was created by reducing the search space. i.e 22 is not reasonable as 4 is smaller and they are equivalent as 2*2 = 4. 20 | 21 | ![histo ascending filtered](histo_filtered.png) 22 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /graph.json: -------------------------------------------------------------------------------- 1 | {"nodes":[{"name":"0","id":0,"group":0},{"name":"1","id":1,"group":0},{"name":"2","id":2,"group":0},{"name":"3","id":3,"group":0},{"name":"4","id":4,"group":0},{"name":"5","id":5,"group":0},{"name":"6","id":6,"group":0},{"name":"7","id":7,"group":0},{"name":"8","id":8,"group":0},{"name":"9","id":9,"group":0},{"name":"10: 0","id":10,"group":1},{"name":"11: 1","id":11,"group":1},{"name":"12: 2","id":12,"group":1},{"name":"13: 3","id":13,"group":1},{"name":"14: 4","id":14,"group":1},{"name":"15: 5","id":15,"group":1},{"name":"16: 6","id":16,"group":1},{"name":"17: 7","id":17,"group":1},{"name":"18: 8","id":18,"group":1},{"name":"19: 9","id":19,"group":1},{"name":"20: 0","id":20,"group":1},{"name":"21: 2","id":21,"group":1},{"name":"22: 4","id":22,"group":1},{"name":"23: 6","id":23,"group":1},{"name":"24: 8","id":24,"group":1},{"name":"25: 10,0","id":25,"group":2},{"name":"26: 12,2","id":26,"group":2},{"name":"27: 14,4","id":27,"group":2},{"name":"28: 16,6","id":28,"group":2},{"name":"29: 18,8","id":29,"group":2},{"name":"30: 0","id":30,"group":1},{"name":"31: 3","id":31,"group":1},{"name":"32: 6","id":32,"group":1},{"name":"33: 9","id":33,"group":1},{"name":"34: 12,2","id":34,"group":2},{"name":"35: 15,5","id":35,"group":2},{"name":"36: 18,8","id":36,"group":2},{"name":"37: 21,2","id":37,"group":2},{"name":"38: 24,8","id":38,"group":2},{"name":"39: 27,14,4","id":39,"group":3},{"name":"40: 0","id":40,"group":1},{"name":"41: 4","id":41,"group":1},{"name":"42: 8","id":42,"group":1},{"name":"43: 12,2","id":43,"group":2},{"name":"44: 16,6","id":44,"group":2},{"name":"45: 20,0","id":45,"group":2},{"name":"46: 24,8","id":46,"group":2},{"name":"47: 28,16,6","id":47,"group":3},{"name":"48: 32,6","id":48,"group":2},{"name":"49: 36,18,8","id":49,"group":3},{"name":"50: 0","id":50,"group":1},{"name":"51: 5","id":51,"group":1},{"name":"52: 10,0","id":52,"group":2},{"name":"53: 15,5","id":53,"group":2},{"name":"54: 20,0","id":54,"group":2},{"name":"55: 25,10,0","id":55,"group":3},{"name":"56: 30,0","id":56,"group":2},{"name":"57: 35,15,5","id":57,"group":3},{"name":"58: 40,0","id":58,"group":2},{"name":"59: 45,20,0","id":59,"group":3},{"name":"60: 0","id":60,"group":1},{"name":"61: 6","id":61,"group":1},{"name":"62: 12,2","id":62,"group":2},{"name":"63: 18,8","id":63,"group":2},{"name":"64: 24,8","id":64,"group":2},{"name":"65: 30,0","id":65,"group":2},{"name":"66: 36,18,8","id":66,"group":3},{"name":"67: 42,8","id":67,"group":2},{"name":"68: 48,32,6","id":68,"group":3},{"name":"69: 54,20,0","id":69,"group":3},{"name":"70: 0","id":70,"group":1},{"name":"71: 7","id":71,"group":1},{"name":"72: 14,4","id":72,"group":2},{"name":"73: 21,2","id":73,"group":2},{"name":"74: 28,16,6","id":74,"group":3},{"name":"75: 35,15,5","id":75,"group":3},{"name":"76: 42,8","id":76,"group":2},{"name":"77: 49,36,18,8","id":77,"group":4},{"name":"78: 56,30,0","id":78,"group":3},{"name":"79: 63,18,8","id":79,"group":3},{"name":"80: 0","id":80,"group":1},{"name":"81: 8","id":81,"group":1},{"name":"82: 16,6","id":82,"group":2},{"name":"83: 24,8","id":83,"group":2},{"name":"84: 32,6","id":84,"group":2},{"name":"85: 40,0","id":85,"group":2},{"name":"86: 48,32,6","id":86,"group":3},{"name":"87: 56,30,0","id":87,"group":3},{"name":"88: 64,24,8","id":88,"group":3},{"name":"89: 72,14,4","id":89,"group":3},{"name":"90: 0","id":90,"group":1},{"name":"91: 9","id":91,"group":1},{"name":"92: 18,8","id":92,"group":2},{"name":"93: 27,14,4","id":93,"group":3},{"name":"94: 36,18,8","id":94,"group":3},{"name":"95: 45,20,0","id":95,"group":3},{"name":"96: 54,20,0","id":96,"group":3},{"name":"97: 63,18,8","id":97,"group":3},{"name":"98: 72,14,4","id":98,"group":3},{"name":"99: 81,8","id":99,"group":2},{"name":"100: 0","id":100,"group":1}],"links":[{"source":10,"target":0,"value":0},{"source":11,"target":1,"value":0},{"source":12,"target":2,"value":0},{"source":13,"target":3,"value":0},{"source":14,"target":4,"value":0},{"source":15,"target":5,"value":0},{"source":16,"target":6,"value":0},{"source":17,"target":7,"value":0},{"source":18,"target":8,"value":0},{"source":19,"target":9,"value":0},{"source":20,"target":0,"value":0},{"source":21,"target":2,"value":0},{"source":22,"target":4,"value":0},{"source":23,"target":6,"value":0},{"source":24,"target":8,"value":0},{"source":25,"target":10,"value":0},{"source":26,"target":12,"value":0},{"source":27,"target":14,"value":0},{"source":28,"target":16,"value":0},{"source":29,"target":18,"value":0},{"source":30,"target":0,"value":0},{"source":31,"target":3,"value":0},{"source":32,"target":6,"value":0},{"source":33,"target":9,"value":0},{"source":34,"target":12,"value":0},{"source":35,"target":15,"value":0},{"source":36,"target":18,"value":0},{"source":37,"target":21,"value":0},{"source":38,"target":24,"value":0},{"source":39,"target":27,"value":0},{"source":40,"target":0,"value":0},{"source":41,"target":4,"value":0},{"source":42,"target":8,"value":0},{"source":43,"target":12,"value":0},{"source":44,"target":16,"value":0},{"source":45,"target":20,"value":0},{"source":46,"target":24,"value":0},{"source":47,"target":28,"value":0},{"source":48,"target":32,"value":0},{"source":49,"target":36,"value":0},{"source":50,"target":0,"value":0},{"source":51,"target":5,"value":0},{"source":52,"target":10,"value":0},{"source":53,"target":15,"value":0},{"source":54,"target":20,"value":0},{"source":55,"target":25,"value":0},{"source":56,"target":30,"value":0},{"source":57,"target":35,"value":0},{"source":58,"target":40,"value":0},{"source":59,"target":45,"value":0},{"source":60,"target":0,"value":0},{"source":61,"target":6,"value":0},{"source":62,"target":12,"value":0},{"source":63,"target":18,"value":0},{"source":64,"target":24,"value":0},{"source":65,"target":30,"value":0},{"source":66,"target":36,"value":0},{"source":67,"target":42,"value":0},{"source":68,"target":48,"value":0},{"source":69,"target":54,"value":0},{"source":70,"target":0,"value":0},{"source":71,"target":7,"value":0},{"source":72,"target":14,"value":0},{"source":73,"target":21,"value":0},{"source":74,"target":28,"value":0},{"source":75,"target":35,"value":0},{"source":76,"target":42,"value":0},{"source":77,"target":49,"value":0},{"source":78,"target":56,"value":0},{"source":79,"target":63,"value":0},{"source":80,"target":0,"value":0},{"source":81,"target":8,"value":0},{"source":82,"target":16,"value":0},{"source":83,"target":24,"value":0},{"source":84,"target":32,"value":0},{"source":85,"target":40,"value":0},{"source":86,"target":48,"value":0},{"source":87,"target":56,"value":0},{"source":88,"target":64,"value":0},{"source":89,"target":72,"value":0},{"source":90,"target":0,"value":0},{"source":91,"target":9,"value":0},{"source":92,"target":18,"value":0},{"source":93,"target":27,"value":0},{"source":94,"target":36,"value":0},{"source":95,"target":45,"value":0},{"source":96,"target":54,"value":0},{"source":97,"target":63,"value":0},{"source":98,"target":72,"value":0},{"source":99,"target":81,"value":0},{"source":100,"target":0,"value":0}]} -------------------------------------------------------------------------------- /persistence.jl: -------------------------------------------------------------------------------- 1 | using JSON 2 | using Plots 3 | using PyPlot 4 | 5 | """ 6 | create_cache(;longest=240) 7 | 8 | Create the cache for faster calculation. This fills the variable `cache` with a vector[1..8] (representing digits 2..9) 9 | of a dictionary holding d^x for d: 2..9 and x up to longest. 10 | """ 11 | function create_cache(;longest=240) 12 | cache = Vector{Vector{BigInt}}() 13 | 14 | for d = 2:9 15 | cd = zeros(longest+1) 16 | for i = 0:longest 17 | cd[i+1] = d^convert(BigInt,i) 18 | end 19 | push!(cache, cd) 20 | end 21 | 22 | ### check for up to 10^power whether the digits are reasonable 23 | # i.e 22 is not reasonable as 4 is smaller same with 24 | power = 7 25 | next_possible = zeros(Int, 10^power) 26 | smallest_possible_for_x = 10^power*ones(Int, 9^power) 27 | l = length(smallest_possible_for_x) 28 | last_i = 1 29 | for i=1:10^power 30 | m = prod(digits(i)) 31 | # if it ends in 0 => not reasonable only for step 2 32 | if 0 < m <= l && m % 10 != 0 33 | if i < smallest_possible_for_x[m] 34 | smallest_possible_for_x[m] = i 35 | next_possible[last_i+1:i] .= i 36 | last_i = i 37 | end 38 | end 39 | end 40 | smallest_possible_for_x[1:9] = collect(1:9); 41 | return cache, next_possible 42 | end 43 | 44 | function digitprod(x::T)::T where {T<:Integer} 45 | val = one(T) 46 | ten = T(10) 47 | while !iszero(val) && !iszero(x) 48 | (x, r) = divrem(x, ten) 49 | val *= r 50 | end 51 | return val 52 | end 53 | 54 | """ 55 | per_small(x) 56 | 57 | Recursive way of calculating the persistence works only with small numbers (not BigInt) 58 | Return the number of steps 59 | """ 60 | function per_small(x) 61 | x < 10 && return 0 62 | return 1 + per_small(digitprod(x)) 63 | end 64 | 65 | """ 66 | per(x) 67 | 68 | Recursive way of calculating the persistence works with BigInt 69 | Return the number of steps 70 | """ 71 | function per(x) 72 | x < 10 && return 0 73 | return 1 + per(prod(convert.(BigInt,digits(x)))) 74 | end 75 | 76 | """ 77 | per_while(x, cache) 78 | 79 | while way of calculating the persistence by using the cache. Fastest for large numbers. 80 | Return the number of steps 81 | """ 82 | function per_while(x, cache) 83 | steps = 0 84 | n = zeros(Int, 8) # from 2 to 9 85 | while x >= 10 86 | val = one(BigInt) 87 | ten = BigInt(10) 88 | while !iszero(val) && !iszero(x) 89 | (x, r) = divrem(x, ten) 90 | if r == 0 91 | return steps+1 92 | end 93 | if r > 1 94 | n[r-1] += 1 95 | end 96 | end 97 | x = prod_arr(n, cache) 98 | n[1:8] .= 0 99 | steps += 1 100 | end 101 | return steps 102 | end 103 | 104 | """ 105 | per_while_arr(x) 106 | 107 | while way of calculating the persistence by using the cache. Fastest for large numbers. 108 | Return the steps. i.e x=3279 -> [378,168,48,32,6] 109 | """ 110 | function per_while_arr(x, cache) 111 | steps = 0 112 | arr = Vector{Int}() 113 | n = zeros(Int, 8) # from 2 to 9 114 | while x >= 10 115 | val = one(BigInt) 116 | ten = BigInt(10) 117 | while !iszero(val) && !iszero(x) 118 | (x, r) = divrem(x, ten) 119 | if r == 0 120 | push!(arr,0) 121 | return arr 122 | end 123 | if r > 1 124 | n[r-1] += 1 125 | end 126 | end 127 | x = prod_arr(n, cache) 128 | push!(arr,x) 129 | n[1:8] .= 0 130 | steps += 1 131 | end 132 | return arr 133 | end 134 | 135 | """ 136 | prod_arr(a::Vector{Int},cache) 137 | 138 | Use the array representation of a number to calculate the product of its digits. 139 | It uses the global cache 140 | Return the product i.e x=2379 => array respresentation [1,1,0,0,0,1,0,1] -> 378 141 | """ 142 | function prod_arr(a::Vector{Int},cache) 143 | n = ones(BigInt, 8) 144 | for i=2:9 145 | n[i-1] = cache[i-1][a[i-1]+1] 146 | end 147 | return prod(n) 148 | end 149 | 150 | """ 151 | per_while_simple(x) 152 | 153 | While way of calculating the persistence without using the cache. Works with big numbers. 154 | Return the number of steps 155 | """ 156 | function per_while_simple(x) 157 | steps = 0 158 | while x >= 10 159 | x = digitprod(x) 160 | steps += 1 161 | end 162 | return steps 163 | end 164 | 165 | """ 166 | per_while_simple(x) 167 | 168 | While way of calculating the persistence without using the cache. Doesn't work with big numbers but faster with small numbers. 169 | Return the number of steps 170 | """ 171 | function per_while_simple_small(x) 172 | steps = 0 173 | while x >= 10 174 | x = digitprod(x) 175 | steps += 1 176 | end 177 | return steps 178 | end 179 | 180 | """ 181 | per_while_print(x) 182 | 183 | Print the steps using per_while works without cache and big numbers 184 | """ 185 | function per_while_print(x) 186 | steps = 0 187 | println(x) 188 | while x >= 10 189 | x = prod(convert.(BigInt,digits(x))) 190 | println(x) 191 | steps += 1 192 | end 193 | return steps 194 | end 195 | 196 | mutable struct Node 197 | name :: String 198 | id :: Int 199 | group :: Int 200 | 201 | Node(name,id,group) = new(name,id,group) 202 | end 203 | 204 | mutable struct Link 205 | source :: Int 206 | target :: Int 207 | value :: Int 208 | 209 | Link(source,target,value) = new(source,target,value) 210 | end 211 | 212 | mutable struct Graph 213 | nodes :: Vector{Node} 214 | links :: Vector{Link} 215 | 216 | Graph() = new() 217 | end 218 | 219 | """ 220 | arr2json(a) 221 | 222 | Return a graph of a vector of per_while_arr calls 223 | """ 224 | function arr2json(a) 225 | G = Graph() 226 | G.nodes = Vector{Node}() 227 | G.links = Vector{Link}() 228 | c = 0 229 | for x in a 230 | name = string(c)*": "*join(x,",") 231 | if length(x) == 0 232 | name = string(c) 233 | end 234 | push!(G.nodes, Node(name,c,length(x))) 235 | if length(x) >= 1 236 | push!(G.links, Link(c,x[1],0)) 237 | end 238 | c += 1 239 | end 240 | return G 241 | end 242 | 243 | """ 244 | create_bf_list(;stop=100) 245 | 246 | Writes a graph representation of all (bruteforce = bf) numbers from 0 to stop to graph.json 247 | """ 248 | function create_bf_list(;stop=100) 249 | result_arr = fill(Vector{Int}(),stop+1) 250 | for i = 0:stop 251 | result_arr[i+1] = per_while_arr(i, cache) 252 | end 253 | write("graph.json", JSON.json(arr2json(result_arr))) 254 | end 255 | 256 | function create_histogram(;stop=10000) 257 | gr() 258 | cache, next_possible = create_cache(;longest=4) 259 | bins = collect(0:12) # 0-11 260 | arr = zeros(Int,stop+1) 261 | for i = 0:stop 262 | arr[i+1] = per_while(i, cache) 263 | end 264 | histogram(arr, label="Histogram", bins=bins, xticks=0:12) 265 | end 266 | 267 | """ 268 | create_list(;longest=15, shortest=0, fct=per_while_simple) 269 | 270 | Check all reasonable numbers between a length of shortest and longest using the function fct to calculate the persistence of a number. 271 | Print if a number with higher persistence was found or a smaller number with the same persistence. 272 | """ 273 | function create_list(;longest=15, shortest=0, fct=per_while_simple) 274 | pyplot() 275 | cache, next_possible = create_cache(;longest=longest) 276 | fct_params = [] 277 | if string(fct) == "per_while" 278 | push!(fct_params, cache) 279 | end 280 | 281 | best_x = 0 282 | best_s = 0 283 | c = parse(BigInt, "0") 284 | tt = 0.0 285 | start_time = time() 286 | # variable which gets checked 287 | x = BigInt(1) 288 | current_length = 1 289 | tail = 1 290 | histo = zeros(Int,13) 291 | while true 292 | x += 1 293 | sx = string(x) 294 | # keeping track of the current length of the number 295 | if x >= 10^convert(BigInt,current_length) 296 | current_length += 1 297 | println("current_length: ", current_length) 298 | end 299 | if sx[end] == '0' 300 | # if the number is 10...0 then the next reasonable one is 22...2 301 | if sx[1] == '1' 302 | x = parse(BigInt,"2"^current_length) 303 | else 304 | # check how many 0 we have in the end like 2223000 after 2222999 305 | # then the next reasonable is 22233333 306 | tail = 1 307 | while sx[end-tail] == '0' 308 | tail += 1 309 | end 310 | front = sx[1:end-tail] 311 | back = front[end] 312 | x = parse(BigInt,front*back^tail) 313 | end 314 | end 315 | sx = string(x) 316 | if length(sx) > longest 317 | println("Checked all") 318 | break 319 | end 320 | first_digits = parse(Int,sx[1:min(length(sx),7)]) 321 | if next_possible[first_digits] != first_digits 322 | # jumping to the next reasonable number 323 | first_digits = next_possible[first_digits] 324 | x = parse(BigInt, string(first_digits)*string(first_digits)[end:end]^(current_length-length(string(first_digits)))) 325 | end 326 | t = time_ns() 327 | s = fct(x, fct_params...) 328 | tt += time_ns()-t 329 | histo[s+1] += 1 330 | if s > best_s 331 | best_s = s 332 | best_x = x 333 | println("Found better: ", x , " needs ", s , " steps and has a length of: ", length(string(x))) 334 | end 335 | c += 1 336 | if c % 10000 == 0 337 | println("Checked ", convert(Int,(c/10000)) , " * 10,000 numbers") 338 | end 339 | end 340 | println("Checked ", c , " numbers") 341 | println("Time needed for per_while: ", tt/10^9) 342 | println("Time needed in total: ", time()-start_time) 343 | Plots.plot(0:12, histo, linetype=:bar, xticks=0:12, yaxis=:log, 344 | title="Numbers with a persistence of 0-12 up $longest digits\nof filtered \"ascending\" numbers i.e no 42 and no 24 only 8 as 2*4=8", 345 | legend=false, size=(900,600), titlefont=font(14)) 346 | end 347 | 348 | create_list(;longest=40) 349 | # create_histogram() --------------------------------------------------------------------------------