【问题标题】:Convert Time:[Distance,Speed] data to Distance:[Time,Speed] data将时间:[距离,速度]数据转换为距离:[时间,速度]数据
【发布时间】:2014-01-09 23:14:13
【问题描述】:

我无法找出算法...

我有一长串 GPS 数据,它们以 1 秒的间隔记录时间、速度、距离。假设距离为米,速度为米/秒。可能有超过 2 小时的数据,或 7200 个点。这里的“时间”字段主要仅供参考。

因此,前 5 秒将是类似这样的值,其中 [1-5] 是秒。

$data = array(
  1 : array('distance'=>0, 'time'=>'2014-01-09 17:50:00', 'speed'=>0.0),
  2 : array('distance'=>2, 'time'=>'2014-01-09 17:50:01', 'speed'=>2.0),
  3 : array('distance'=>6, 'time'=>'2014-01-09 17:50:02', 'speed'=>4.0),
  4 : array('distance'=>10, 'time'=>'2014-01-09 17:50:03', 'speed'=>4.0),
  5 : array('distance'=>12, 'time'=>'2014-01-09 17:50:04', 'speed'=>2.0)
);

我想将其转换为以 1 米为间隔列出的数据,例如 [1-6] 为米。

$data = array(
  1 : array('seconds'=>1.5, 'time'=>'2014-01-09 17:50:01.500', 'speed'=>.666),
  2 : array('seconds'=>2, 'time'=>'2014-01-09 17:50:02', 'speed'=>2.0),
  3 : array('seconds'=>2.25, 'time'=>'2014-01-09 17:50:02.250', 'speed'=>4.0),
  4 : array('seconds'=>2.5, 'time'=>'2014-01-09 17:50:02.500', 'speed'=>4.0),
  5 : array('seconds'=>2.75, 'time'=>'2014-01-09 17:50:02.750', 'speed'=>4.0),
  6 : array('seconds'=>3, 'time'=>'2014-01-09 17:50:03', 'speed'=>4.0)
);

当然,这可以在没有时间字段的情况下完成。我在计算时遇到了麻烦,因为它绝对不是一对一的。如果我们从 7200 秒的数据开始,我们可能会根据所覆盖的距离(多于或少于 7200 米)得到更多或更少的数据。

编辑(2014 年 1 月 10 日)

以下是这两种方法的实际实现。我实际上很难决定我更喜欢哪种方法,迭代或递归方法。我可能会使用迭代

方法 1,迭代(@Ezequiel Muns,我做了非常小的修改):

function timeToDistance($data) {
  if(sizeof($data) == 0){ return; }
  $startTime = $data[0]['time'];

  $prev = null;
  $result = array();
  foreach ($data as $secs => $row) {
    $row['seconds'] = $secs; // to simplify passing in secs
    if ($prev == null) {
      // make sure we have a pair
      $prev = array( 'distance'=>0 );
    }
    foreach (distanceRowsBetween($startTime,$prev, $row) as $dist => $distRow) {
      $result[$dist] = $distRow;
    }
    $prev = $row;
  }
  return $result;
}

function distanceRowsBetween($startTime,$prevRow, $nextRow) {
  // Return the by-distance rows that are between $prevRow (exclusive)
  // and $nextRow (inclusive)
  $rows = array();
  $currDist = $prevRow['distance'];
  while (true) {
    // try to move to the next whole unit of distance
    $dDist = ceil($currDist) - $currDist;
    $dDist = $dDist == 0.0? 1.0 : $dDist; // dDist is 1 unit if currDist is whole
    $currDist += $dDist;
    if ($currDist > $nextRow['distance'])
      break;

    $currSpeed = $nextRow['speed'];
    $currSecs = strtotime($nextRow['time']) - strtotime($startTime);
    $currTime = $nextRow['time'];

    $rows[$currDist] =  array(
                          'speed' => $currSpeed,
                          'seconds' => $currSecs,
                          'time' => $currTime,
                         );
  }
  return $rows;
}

方法 2,递归(@Nathaniel Ford 伪代码,我是实际代码):

function data2dist($time_data = array()){
  $dist_data = array();
  if(sizeof($time_data) == 0){ return $dist_data; }

  $start_point = array_shift($time_data);
  $start_time = $start_point['time'];

  data2dist_sub($start_time, $time_data,$dist_data,$start_point);

  return $dist_data;
}

function data2dist_sub($start_time,&$time_data, &$dist_data, $start_point = array()){
  if(sizeof($time_data) == 0 && !isset($start_point)){
    return;
  }

  if(sizeof($dist_data) == 0){
    $prev_dist = 0;
  } else {
    $prev_dist = $dist_data[sizeof($dist_data)-1]['distance'];
  }
  // since distances are accumulating, get curr distance by subtracting last one
  $point_dist = $start_point['distance'] - $prev_dist;

  if($point_dist == 1){
    // exactly 1: perfect, add and continue
    $dist_data[] = $start_point;
    $start_point = array_shift($time_data);
  } else if($point_dist > 1){
    // larger than 1: effectively remove 1 from current point and send it forward
    $partial_point = $start_point;
    $partial_point['distance'] = 1 + $prev_dist;
    $dist_data[] = $partial_point;

  } else if($point_dist < 1){
    // less than 1, carry forward to the next item and continue (minor: this partial speed is absorbed into next item)
    $start_point = array_shift($time_data);
    if(!isset($start_point)){   return;   }

    $start_point['distance'] += $point_dist;
  }
  data2dist_sub($start_time,$time_data,$dist_data,$start_point);
}

【问题讨论】:

  • 告诉我们更多关于您尝试过的内容?哪里失败了?

标签: php algorithm time distance


【解决方案1】:

您可以简化这一点,注意对于每对连续的按时间行,您需要计算 0 个或多个按距离行,而这些仅取决于这两个按时间行。

所以从一个函数开始做这个更简单的计算,这是一个骨架,为了简单起见,不考虑转换后的“秒”、“速度”和“时间”值的计算。

function distanceRowsBetween($prevRow, $nextRow) {
    // Return the by-distance rows that are between $prevRow (exclusive)
    // and $nextRow (inclusive)
    $rows = array();
    $currDist = $prevRow['distance'];
    while (true) {
        // try to move to the next whole unit of distance
        $dDist = ceil($currDist) - $currDist;
        $dDist = $dDist == 0.0? 1.0 : $dDist; // dDist is 1 unit if currDist is whole
        $currDist += $dDist;
        if ($currDist > $nextRow['distance'])
            break;

        // calculate $currSecs at distance $currDist
        // calculate $currSpeed 
        // calculate $currTime 

        $rows[$currDist] = array(
            'speed' => $currSpeed,
            'seconds' => $currSecs,
            'time' => $currTime,
        );
    }
    return $rows;
}

现在您已经有了这一切,剩下的就是迭代输入中的每个连续对并累积产生的按距离行:

function timeToDistance($data) {
    $prev = null;
    $result = array();
    foreach ($data as $secs => $row) {
        $row['seconds'] = $secs; // to simplify passing in secs
        if ($prev == null) {
            $prev = $row; // make sure we have a pair
            continue;
        }
        foreach (distanceRowsBetween($prev, $row) as $dist => $distRow) {
            $result[$dist] = $distRow;
        }
        $prev = $row;
    }
    return $result;
}

请注意,在此函数中,我正在填充并传入行中的当前“秒”值,以减少传递给前一个函数的参数数量。

【讨论】:

  • 感谢此代码。它几乎是即插即用,除了一些小的模组,如果你好奇的话,你可以在我上面的编辑中看到。
  • 我接受了这个答案,因为提供了工作代码,并且输出数组也最容易自定义。
【解决方案2】:

这有点令人费解,并且有几个边缘情况使它变得困难。但是,您的基本算法应该归结为:

Take in an array of by-Time data points
Create a new array of by-Distance data points
Create a first by-Distance data point with 'zero' speed/distance
Pass this to your subfunction 

Subfunction (Takes by-Time array, by-Distance array and 'start point')
Take the first by-Time data point and 'add' it to the by-Distance data point, call this 'temp'
  Convert to seconds/speed
If distance covered by temp is exactly 1, add this new array to the by-Distance array
If it is more than one, subtract the portion that would equal one
  back-calculate distance/speed/time, add to by-Distance array
  Recurse into the subfunction, using the remainder as your new start point
If it is less than one
  Recurse into the subfunction, using the modified start point as new start point

请注意,子函数需要使用数组的可变副本:by-Time 数组应该慢慢缩小,而 by-Distance 数组会增长。此外,您将需要对函数进行蹦床(而不是使用直接递归),因为使用 7200 个数据点,您可能会在堆栈帧中拥有更多的数据点,并且您会遇到潜在的内存问题。

【讨论】:

  • 感谢这个递归布局。我继续并首先实现了这一点,如上面的编辑中所述。代码运行良好。我并不热衷于在整个递归过程中传递常量 $startTime,但它是必需的,我不喜欢全局变量。此外,我已经将我的需求配对,并且可能只需要距离->速度输出,而不是使用时间或秒部分。不过,这是一个很大的帮助,谢谢。