/src/Corner.php

https://github.com/jnewman/AHPMath · PHP · 211 lines · 100 code · 30 blank · 81 comment · 11 complexity · 65473b8ba134b2d1ad5a07f7b3cbacf7 MD5 · raw file

  1. <?php
  2. class Corner{
  3. protected $data = array();
  4. /**
  5. * @author Joshua Newman
  6. * @param array $seqence
  7. * @desc $sequence should be the n*(n-1)/2 portion of a square matrix.
  8. * For example, this sequence:
  9. *
  10. * $corner = array(
  11. * 2, 1/3, 4, 5,
  12. * 6, 9, 2,
  13. * 1/3, 5,
  14. * 1/2,
  15. * );
  16. *
  17. * $c = new Corner($corner);
  18. *
  19. * Results in:
  20. *
  21. * array(
  22. * array( 1, 2, 1/3, 4, 5,),
  23. * array( 1/2, 1, 6, 9, 2,),
  24. * array( 3, 1/6, 1, 1/3, 5,),
  25. * array( 1/4, 1/9, 3, 1, 1/2,),
  26. * array( 1/5, 1/2, 1/5, 2, 1,),
  27. * );
  28. *
  29. */
  30. public function __construct(array $sequence) {
  31. $this->width = self::widthFromResults(count($sequence));
  32. $this->data = $this->toCorner($sequence);
  33. }
  34. /**
  35. * @return string a reasonable representation of the Corner as an
  36. * evaluatable string.
  37. */
  38. public function __toString() {
  39. return (string)var_export($this->data);
  40. }
  41. /**
  42. * @return Matrix
  43. */
  44. public function toMatrix() {
  45. $right = array_filter($this->data);
  46. // Make internal data ready for use as left-side.
  47. $this->pad()->transpose()->reciprocate()->data;
  48. foreach ($right as $key => $row) {
  49. $this->data[$key] = array_merge($this->data[$key], $row);
  50. }
  51. unset($row);
  52. return new AHPMatrix($this->data);
  53. }
  54. /**
  55. * @param array $sequence
  56. * @return Corner
  57. */
  58. protected function toCorner(array $sequence) {
  59. $corner = array();
  60. $length = self::resultsFromWidth($this->width);
  61. $w = $this->width - 1; // Since I haven't padded it yet.
  62. while ($w--){
  63. $row = array();
  64. $range = range(0, $w);
  65. foreach ($range as $i) {
  66. $row[] = array_shift($sequence);
  67. }
  68. $corner[] = $row;
  69. }
  70. return $corner;
  71. }
  72. /**
  73. *
  74. * @return Corner
  75. */
  76. protected function transpose() {
  77. if (count($this->data[0]) < count($this->data[1])) {
  78. throw new Exception("
  79. The first row must be longer than the second row.
  80. ");
  81. }
  82. $limit = count($this->data);
  83. $parts = array();
  84. for($i = 0; $i < $limit; $i++) {
  85. $row = array();
  86. $j = 0;
  87. while ($j < $i + 1) {
  88. $row[] = array_shift($this->data[$j++]);
  89. }
  90. $parts[] = $row;
  91. }
  92. $this->data = $parts;
  93. return $this;
  94. }
  95. protected function pad() {
  96. // Instead of real branches, just flip and flag
  97. $flag = 0;
  98. if(count($this->data[0]) < count($this->data[1])){
  99. $this->transpose();
  100. $flag = 1;
  101. }
  102. $this->data[] = array(1);
  103. $i = 0;
  104. while ($i < $this->width - 1) {
  105. array_unshift($this->data[$i++], 1);
  106. }
  107. if($flag) $this->transpose();
  108. return $this;
  109. }
  110. protected function reciprocate() {
  111. foreach ($this->data as &$row) {
  112. foreach ($row as &$value) {
  113. $value = 1 / $value;
  114. }
  115. unset($value); // Remove the pointer to avoid overwriting.
  116. }
  117. unset($row); // Remove the pointer to avoid overwriting.
  118. return $this;
  119. }
  120. ///////////////////////////////////////////////////////////////////////////////
  121. //// What follows was created by Brent Kesler for an earlier project and later
  122. //// ported to PHP by @author Josh Newman
  123. ///////////////////////////////////////////////////////////////////////////////
  124. /**
  125. *
  126. * @param integer $width
  127. * @return integer number of elements necessary to permute $width elements.
  128. */
  129. final private static function resultsFromWidth($width) {
  130. return $width * ($width - 1) / 2;
  131. }
  132. /**
  133. * @param integer $results
  134. * @return integer
  135. * @desc Given:
  136. * width-squared = 2r + width --> width = sqrt(2r + width)
  137. * width > 0 --> 2r < 2r + width
  138. * results > width --> 2r + width < 3r
  139. * --> 2r < 2r + width < 3r
  140. * --> sqrt(2r) < width < sqrt(3r)
  141. *
  142. * width is an integer -->
  143. */
  144. final private static function widthFromResults($results) {
  145. $min = ceil(sqrt(2 * $results));
  146. $max = floor(sqrt(3 * $results));
  147. // Test resultsFromWidth for values between $min and $max
  148. return self::widthFromResultsHelper($results, $min, $max);
  149. }
  150. /**
  151. *
  152. * @param integer $results
  153. * @param integer $min
  154. * @param integer $max
  155. */
  156. final private static function widthFromResultsHelper($results, $min, $max) {
  157. /*
  158. * if($min === $max) doesn't work if $results === 2
  159. */
  160. if ($min >= $max) {
  161. return $max;
  162. } else {
  163. /*
  164. * Instead of going through all numbers between min and max, we'll
  165. * test a possible width near the halfway point and cut down the
  166. * range.
  167. */
  168. $testWidth = $min + $max - $min / 2;
  169. $testResults = self::resultsFromWidth($testWidth);
  170. if($testResults === $results) {
  171. return $testResults;
  172. } elseif($testResults > $results) {
  173. // Cut off the top half of the range.
  174. return self::widthFromResultsHelper($results, $min, $testWidth);
  175. } else { // i.e., elif($testResults < $results)
  176. // Cut off the bottom half of the range.
  177. return self::widthFromResultsHelper($results, $testWidth, $max);
  178. }
  179. }
  180. }
  181. }