123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268 |
- <?php
- namespace MathPHP;
- use MathPHP\Exception;
- /**
- * Search
- * Various functions to find specific indices in an array.
- */
- class Search
- {
- /**
- * Search Sorted
- * Find the array indices where items should be inserted to maintain sorted order.
- *
- * Inspired by and similar to Python NumPy's searchsorted
- *
- * @param float[]|int[] $haystack Sorted array with standard increasing numerical array keys
- * @param float $needle Item wanting to insert
- *
- * @return int Index of where you would insert the needle and maintain sorted order
- */
- public static function sorted(array $haystack, float $needle): int
- {
- if (empty($haystack)) {
- return 0;
- }
- $index = 0;
- foreach ($haystack as $i => $val) {
- if ($needle > $val) {
- $index++;
- } else {
- return $index;
- }
- }
- return $index;
- }
- /**
- * ArgMax
- * Find the array index of the maximum value.
- *
- * In case of the maximum value appearing multiple times, the index of the first occurrence is returned.
- * In the case NAN is present, the index of the first NAN is returned.
- *
- * Inspired by and similar to Python NumPy's argmax
- *
- * @param float[]|int[] $values
- *
- * @return int Index of the first occurrence of the maximum value
- *
- * @throws Exception\BadDataException if the array of values is empty
- */
- public static function argMax(array $values): int
- {
- if (empty($values)) {
- throw new Exception\BadDataException('Cannot find the argMax of an empty array');
- }
- // Special case: NAN wins if present
- $nanPresent = \array_filter(
- $values,
- function ($value) {
- return \is_float($value) && \is_nan($value);
- }
- );
- if (\count($nanPresent) > 0) {
- foreach ($values as $i => $v) {
- if (\is_nan($v)) {
- return $i;
- }
- }
- }
- // Standard case: Find max and return index
- return self::baseArgMax($values);
- }
- /**
- * NanArgMax
- * Find the array index of the maximum value, ignoring NANs
- *
- * In case of the maximum value appearing multiple times, the index of the first occurrence is returned.
- *
- * Inspired by and similar to Python NumPy's nanargmax
- *
- * @param float[]|int[] $values
- *
- * @return int Index of the first occurrence of the maximum value
- *
- * @throws Exception\BadDataException if the array of values is empty
- * @throws Exception\BadDataException if the array only contains NANs
- */
- public static function nanArgMax(array $values): int
- {
- if (empty($values)) {
- throw new Exception\BadDataException('Cannot find the argMax of an empty array');
- }
- $valuesWithoutNans = \array_filter(
- $values,
- function ($value) {
- return !\is_nan($value);
- }
- );
- if (\count($valuesWithoutNans) === 0) {
- throw new Exception\BadDataException('Array of all NANs has no nanArgMax');
- }
- return self::baseArgMax($valuesWithoutNans);
- }
- /**
- * Base argMax calculation
- * Find the array index of the maximum value.
- *
- * In case of the maximum value appearing multiple times, the index of the first occurrence is returned.
- *
- * @param float[]|int[] $values
- *
- * @return int Index of the first occurrence of the maximum value
- *
- * @throws \LogicException if the array of values is empty - should never happen
- */
- private static function baseArgMax(array $values): int
- {
- $max = \max($values);
- foreach ($values as $i => $v) {
- if ($v === $max) {
- return $i;
- }
- }
- throw new \LogicException('argMax values is empty--should not happen');
- }
- /**
- * ArgMin
- * Find the array index of the minimum value.
- *
- * In case of the minimum value appearing multiple times, the index of the first occurrence is returned.
- * In the case NAN is present, the index of the first NAN is returned.
- *
- * Inspired by and similar to Python NumPy's argmin
- *
- * @param float[]|int[] $values
- *
- * @return int Index of the first occurrence of the minimum value
- *
- * @throws Exception\BadDataException if the array of values is empty
- */
- public static function argMin(array $values): int
- {
- if (empty($values)) {
- throw new Exception\BadDataException('Cannot find the argMin of an empty array');
- }
- // Special case: NAN wins if present
- $nanPresent = \array_filter(
- $values,
- function ($value) {
- return \is_float($value) && \is_nan($value);
- }
- );
- if (\count($nanPresent) > 0) {
- foreach ($values as $i => $v) {
- if (\is_nan($v)) {
- return $i;
- }
- }
- }
- // Standard case: Find max and return index
- return self::baseArgMin($values);
- }
- /**
- * NanArgMin
- * Find the array index of the minimum value, ignoring NANs
- *
- * In case of the minimum value appearing multiple times, the index of the first occurrence is returned.
- *
- * Inspired by and similar to Python NumPy's nanargin
- *
- * @param float[]|int[] $values
- *
- * @return int Index of the first occurrence of the minimum value
- *
- * @throws Exception\BadDataException if the array of values is empty
- * @throws Exception\BadDataException if the array only contains NANs
- */
- public static function nanArgMin(array $values): int
- {
- if (empty($values)) {
- throw new Exception\BadDataException('Cannot find the nanArgMin of an empty array');
- }
- $valuesWithoutNans = \array_filter(
- $values,
- function ($value) {
- return !\is_nan($value);
- }
- );
- if (\count($valuesWithoutNans) === 0) {
- throw new Exception\BadDataException('Array of all NANs has no nanArgMax');
- }
- return self::baseArgMin($valuesWithoutNans);
- }
- /**
- * Base argMin calculation
- * Find the array index of the minimum value.
- *
- * In case of the maximum value appearing multiple times, the index of the first occurrence is returned.
- *
- * @param float[]|int[] $values
- *
- * @return int Index of the first occurrence of the minimum value
- *
- * @throws \LogicException if the array of values is empty - should never happen
- */
- private static function baseArgMin(array $values): int
- {
- $max = \min($values);
- foreach ($values as $i => $v) {
- if ($v === $max) {
- return $i;
- }
- }
- throw new \LogicException('argMin values is empty--should not happen');
- }
- /**
- * NonZero
- * Find the array indices of the scalar values that are non-zero.
- *
- * Considered 0:
- * int 0, -0
- * float 0.0, -0.0
- * string 0, -0, 0.0, -0.0
- * bool false
- *
- * Inspired by Python NumPy's nonzero
- *
- * @param float[]|int[] $values
- *
- * @return int[]
- */
- public static function nonZero(array $values): array
- {
- $indices = [];
- foreach ($values as $i => $v) {
- if (!\is_scalar($v)) {
- continue;
- }
- if ($v != 0) {
- $indices[] = $i;
- }
- }
- return $indices;
- }
- }
|