1 /* 2 Copyright (c) 2015-2021 Nick Papanastasiou, Timur Gafarov 3 4 Boost Software License - Version 1.0 - August 17th, 2003 5 6 Permission is hereby granted, free of charge, to any person or organization 7 obtaining a copy of the software and accompanying documentation covered by 8 this license (the "Software") to use, reproduce, display, distribute, 9 execute, and transmit the Software, and to prepare derivative works of the 10 Software, and to permit third-parties to whom the Software is furnished to 11 do so, all subject to the following: 12 13 The copyright notices in the Software and this entire statement, including 14 the above license grant, this restriction and the following disclaimer, 15 must be included in all copies of the Software, in whole or in part, and 16 all derivative works of the Software, unless such copies or derivative 17 works are solely in the form of machine-executable object code generated by 18 a source language processor. 19 20 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 23 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 24 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 25 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 26 DEALINGS IN THE SOFTWARE. 27 */ 28 29 /** 30 * Combinatorics 31 * 32 * Copyright: Nick Papanastasiou 2015-2021. 33 * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). 34 * Authors: Nick Papanastasiou, Timur Gafarov 35 */ 36 module dlib.math.combinatorics; 37 38 import std.functional: memoize; 39 import std.algorithm: reduce, map; 40 import std.range: iota; 41 import std.bigint; 42 43 /// Returns the factorial of n 44 ulong factorial(ulong n) @safe nothrow 45 { 46 if(n <= 1) 47 { 48 return 1; 49 } 50 51 alias mfac = memoize!factorial; 52 53 return n * mfac(n - 1); 54 } 55 56 /// 57 unittest 58 { 59 assert(factorial(10) == 3_628_800); 60 61 int n = 5; 62 assert(n.factorial == 5.factorial && 5.factorial == 120); 63 } 64 65 /// Computes the nth fibonacci number 66 ulong fibonacci(ulong n) 67 { 68 if(n == 0 || n == 1) 69 { 70 return n; 71 } 72 73 alias mfib = memoize!fibonacci; 74 75 return mfib(n - 1) + mfib(n - 2); 76 } 77 78 /// Common vernacular for fibonacci 79 alias fib = fibonacci; 80 81 /// 82 unittest 83 { 84 import std.array: array; 85 auto fibs = iota(1, 21).map!(n => fib(n)).array; 86 assert(fibs == [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 87 233, 377, 610, 987, 1597, 2584, 4181, 6765]); 88 } 89 90 91 /// Computes the double factorial of n: n * (n - 2) * (n - 4) * ... * 1 92 ulong doubleFactorial(ulong n) 93 { 94 if (n <= 1) 95 { 96 return 1; 97 } 98 99 alias mDoubleFac = memoize!doubleFactorial; 100 101 return n * doubleFactorial(n - 2); 102 } 103 104 /// 105 unittest 106 { 107 import std.array: array; 108 auto dfacs = iota(1, 21).map!(n => doubleFactorial(n)).array; 109 assert(dfacs == [1, 2, 3, 8, 15, 48, 105, 384, 945, 3840, 110 10395, 46080, 135135, 645120, 2027025, 111 10321920, 34459425, 185794560, 654729075, 112 3715891200]); 113 } 114 115 /// Computes the hyperfactorial of n: 1^1 * 2^2 * 3^3 * ... n^n 116 BigInt hyperFactorial(ulong n) 117 { 118 if(n <= 1) 119 { 120 return BigInt("1"); 121 } 122 123 alias mhfac = memoize!hyperFactorial; 124 125 return BigInt(n ^^ n) * hyperFactorial(n - 1); 126 } 127 128 /// 129 unittest 130 { 131 import std.array: array; 132 auto hfacs = iota(1, 6).map!(n => hyperFactorial(n)).array; 133 assert(hfacs == [1, 4, 108, 27648, 86400000]); 134 } 135 136 /++ 137 + Compute the number of combinations of `objects` types of items 138 + when considered `taken` at a time, where order is ignored 139 +/ 140 ulong combinations(ulong objects, ulong taken) @safe nothrow 141 { 142 if (objects < taken) 143 { 144 return 0; 145 } 146 147 return objects.factorial / (taken.factorial * (objects - taken).factorial); 148 } 149 150 /// Common vernacular for combinations 151 alias C = combinations; 152 153 /// Ditto 154 alias choose = combinations; 155 156 /// 157 unittest 158 { 159 assert(1.choose(2) == 0); 160 assert(5.choose(2) == 10); 161 } 162 163 /++ 164 + Compute the number of permutations of `objects` types of items 165 + when considered `taken` at a time, where order is considered 166 +/ 167 ulong permutations(ulong objects, ulong taken) @safe nothrow 168 { 169 return objects.factorial / (objects - taken).factorial; 170 } 171 172 // Common vernacular for permutations 173 alias P = permutations; 174 175 /// 176 unittest 177 { 178 assert(10.P(2) == 90); 179 } 180 181 /// Computes the nth Lucas number 182 ulong lucas(ulong n) @safe nothrow 183 { 184 if (n == 0) 185 { 186 return 2; 187 } 188 189 if (n == 1) 190 { 191 return 1; 192 } 193 194 alias mlucas = memoize!lucas; 195 196 return mlucas(n - 1) + mlucas(n - 2); 197 } 198 199 unittest 200 { 201 import std.algorithm: map; 202 import std.array; 203 auto lucasRange = iota(0, 12).map!(k => lucas(k)).array; 204 assert(lucasRange == [2, 1, 3, 4, 7, 11, 18, 29, 47, 76, 123, 199]); 205 }