Эвристическая сортировка любых «видов» технических измерений в PHP - PullRequest
0 голосов
/ 19 марта 2019

Я разные списки с измерениями одного и того же размера, но немного смешанные единицы, такие как

"1 м, 200 мм, 1 фут"

или, может быть, также

"1 ° C, 273 K" и т. Д.

Теперь я хочу отсортировать их по абсолютному порядку

"200 мм, 1 фут, 1 м" и "273 К, 1 ° C"

Мне интересно, является ли это уже решенной проблемой, поскольку я не хочу изобретать велосипед. Боюсь, это могут быть какие-то вопросы «покупки расширений PHP», но я уже нашел несколько полезных пакетов:

https://github.com/PhpUnitsOfMeasure/php-units-of-measure может вести любые разговоры между единицами измерения.

Я уже создал код для разделения номера и единицы измерения.

Итак, что я думаю, чтобы "грубой силой" подразделение до определенного измерения из этих:

https://github.com/PhpUnitsOfMeasure/php-units-of-measure/tree/master/source/PhysicalQuantity

Затем я мог бы выбрать первое измерение и преобразовать все в первый «основной» модуль СИ и отсортировать его.

правый

Ответы [ 2 ]

2 голосов
/ 19 марта 2019

Как правило, вам нужно преобразовать эти единицы измерения в некоторые общие измерения, но только с целью сортировки.

Используйте usort() и пользовательскую функцию обратного вызова.При обратном вызове выполните преобразование с целью сравнения.

Обязательно сохраните исходную единицу измерения при возврате результата, иначе будут появляться ошибки округления.

0 голосов
/ 19 марта 2019

Это решение, которое я придумал, основываясь на предложениях

public function testCompareLength()
{
    $this->assertLessThan(0, $this->objectDe->compareFunction('100 mm', '1 m'));
}

public function testCompareTemperature()
{
    $this->assertLessThan(0, $this->objectDe->compareFunction('1 K', '0 °C'));
    $this->assertGreaterThan(0, $this->objectDe->compareFunction('0 °C', '1 K'));
    $this->assertEquals(0, $this->objectDe->compareFunction('-273 °C', '0 K'));
}

/**
 * @param $numberString
 *
 * @return array
 */
public function parseNumber($numberString): array
{
    $values = preg_split('/(?<=[0-9.,])(?=[^0-9,.]+)/i', $numberString);

    $float = $values[0];
    $unit = $values[1] ?? '';

    $decPos = strpos($float, '.');
    if ($decPos === false) {
        $precision = 0;
    } else {
        $precision = strlen($float) - $decPos - 1;
    }

    return ['float' => $float, 'unit' => $unit, 'precision' => $precision];
}


private function heuristicMeasureFactory($measure)
{
    $prioritizedDimensions = [
        Temperature::class,
        Length::class,
    ];

    $unit = trim($measure['unit']);

    foreach ($prioritizedDimensions as $class) {
        foreach ($class::getUnitDefinitions() as $definition) {
            if ($definition->getName() == $unit) {
                return new $class($measure['float'], $unit);
            }
        }
    }

    // now process aliases
    foreach ($prioritizedDimensions as $class) {
        foreach ($class::getUnitDefinitions() as $definition) {
            foreach ($definition->aliases as $alias) {
                if ($alias == $unit) {
                    return new $class($measure['float'], $unit);
                }
            }
        }
    }

    return null; // NaN
}

/**
 * Sort apples and oranges -- kind of. Not.
 *
 * Compares two strings which represend a measurement of the same physical dimension
 */
public function compareFunction($a, $b)
{
    $definitions = Temperature::getUnitDefinitions();

    $aParsed = $this->parseNumber($a);
    $aVal = $this->heuristicMeasureFactory($aParsed);

    $bParsed = $this->parseNumber($b);
    $bVal = $this->heuristicMeasureFactory($bParsed);

    if ($aVal == null || $bVal == null) {
        return strnatcmp($aVal, $bVal); // fallback to string comparision
    }

    return bccomp($aVal->subtract($bVal)->toNativeUnit(), 0, 36);
}
...