20

最初の質問。穏やかな。

私は、技術者がタスクに費やした時間を追跡するソフトウェアに取り組んでいます。ソフトウェアは、曜日と時間帯に基づいて異なる請求可能なレート乗数を認識するように拡張する必要があります。(例:「平日の午後5時から1時間半。」)

ソフトウェアを使用する技術者は、日付、開始時間、終了時間 (時間と分) を記録するだけです。ソフトウェアは、レート乗数が変化する境界で時間エントリを部分に分割することが期待されます。1 回のエントリーで複数日にまたがることはできません。

料金表の部分的なサンプルを次に示します。第 1 レベルの配列キーは明らかに曜日です。第 2 レベルの配列キーは、新しい乗数が有効になる時刻を表し、配列内の次の連続するエントリまで実行されます。配列の値は、その時間範囲の乗数です。

[rateTable] => Array
    (
        [Monday] => Array
            (
                [00:00:00] => 1.5
                [08:00:00] => 1
                [17:00:00] => 1.5
                [23:59:59] => 1
            )

        [Tuesday] => Array
            (
                [00:00:00] => 1.5
                [08:00:00] => 1
                [17:00:00] => 1.5
                [23:59:59] => 1
            )
        ...
    )

平易な英語では、これは午前 0 時から午前 8 時までは 1 時間半の料金、午後 8 時から午後 5 時までは通常の料金、午後 5 時から 11 時 59 分までは 1 時間半の料金を表します。これらの休憩が発生する時間は秒単位で任意である可能性があり、各日に任意の数の休憩が存在する可能性があります。(この形式は完全に交渉可能ですが、私の目標は、可能な限り人間が判読できるようにすることです。)

例として、月曜日の 15:00:00 (午後 3 時) から 21:00:00 (午後 9 時) に記録された時間エントリは、1x で請求される 2 時間と 1.5x で請求される 4 時間で構成されます。1 回のエントリが複数の休憩にまたがることも可能です。上記の rateTable の例を使用すると、午前 6 時から午後 9 時までの時間エントリには、午前 6 時から 8 時までの 1.5x、午前 8 時から午後 5 時までの 1x、午後 5 時から 9 時までの 1.5x の 3 つのサブ範囲があります。対照的に、時間エントリが 08:15:00 から 08:30:00 までのみであり、1 つの乗数の範囲に完全に含まれる可能性もあります。

曜日、開始時刻、終了時刻を取り、必要なサブパーツに解析できる PHP のコーディング (または少なくともアルゴリズムの考案) を実際に使用することができます。出力を (開始、停止、乗数) トリプレットの複数のエントリで構成される配列にすることが理想的です。上記の例の場合、出力は次のようになります。

[output] => Array
    (
        [0] => Array
            (
                [start] => 15:00:00
                [stop] => 17:00:00
                [multiplier] => 1
            )

        [1] => Array
            (
                [start] => 17:00:00
                [stop] => 21:00:00
                [multiplier] => 1.5
            )
    )

単一の(開始、停止)を(潜在的に)複数のサブパーツに分割するロジックに頭を悩ませることはできません。

4

5 に答える 5

3

別のアプローチを使用し、いくつかの考慮事項に基づいて rateTable 表現を変更します。

  • $rateTable には間隔が記述されていますが、適切にエンコードしないのはなぜですか?
  • フロンティアで何が起こるか (私の例では、火曜日と月曜日は境界定義に 2 つの異なるアプローチを使用しています);
  • 得られる結果は同等のタイプですが、異なる表現を使用します。
  • 23:59:59=>ハックのようです。今は説明できませんが、頭の後ろでベルが鳴っていて、気をつけろと言っています。

大事なことを言い忘れましたが、私の個人的な経験から言えば、アルゴリズムに頭を悩ませることができない場合、同僚が同じ問題を抱えている可能性が高く (あなたが成功して問題を解決したとしても)、コードはバグの主な原因になります。よりシンプルで効率的なソリューションを見つけたとしても、それは時間とお金と頭痛の種になります。ソリューションがそれほど効率的でなくても、おそらくそれは利益になるでしょう。

$rateTable = array(
    'Monday' => array (
        array('start'=>'00:00:00','stop'=>'07:59:59','multiplier'=>1.5),
        array('start'=>'08:00:00','stop'=>'16:59:59','multiplier'=>1),
        array('start'=>'17:00:00','stop'=>'23:59:59','multiplier'=>1.5)
    ),
    'Tuesday'=> array (
        array('start'=>'00:00:00','stop'=>'08:00:00','multiplier'=>1.5),
        array('start'=>'08:00:00','stop'=>'17:00:00','multiplier'=>1),
        array('start'=>'17:00:00','stop'=>'23:59:59','multiplier'=>1.5)
    )
);

function map_shift($shift, $startTime, $stopTime)
{
    if ($startTime >= $shift['stop'] or $stopTime <= $shift['start']) {
        return;
    }
    return array(
        'start'=> max($startTime, $shift['start']),
        'stop' => min($stopTime, $shift['stop']),
        'multiplier' => $shift['multiplier']
    );
}

function bill($day, $start, $stop)
{
    $report = array();
    foreach($day as $slice) {
        $result = map_shift($slice, $start, $stop);
        if ($result) {
           array_push($report,$result);
        }
    }
    return $report;
}



/* examples */
var_dump(bill($rateTable['Monday'],'08:05:00','18:05:00'));
var_dump(bill($rateTable['Monday'],'08:05:00','12:00:00'));
var_dump(bill($rateTable['Tuesday'],'07:15:00','19:30:00'));
var_dump(bill($rateTable['Tuesday'],'07:15:00','17:00:00'));

少なくとも、元の形式を新しい形式に変換する関数が必要です。

$oldMonday = array (
   '00:00:00'=>1.5,
   '08:00:00'=>1,
   '17:00:00'=>1.5,
   '23:59:59'=>1
);

function convert($array) 
{
    return array_slice(
        array_map(
           function($start,$stop, $multiplier) 
           {
               return compact('start', 'stop','multiplier');
           },
           array_keys($array),
           array_keys(array_slice($array,1)),
           $array),
        0,
        -1);
}

var_dump(convert($oldMonday));

そして、はい、あなたはその場で変換を行うことができます

bill(convert($oldRateTable['Tuesday']),'07:15:00','17:00:00');

しかし、パフォーマンスを少し気にするなら...

于 2010-05-08T01:58:42.903 に答える
1

これが私の方法です

ずっと簡単にするために、すべてを秒に変換しました。

秒単位でインデックス化されたレート テーブルを次に示します。月曜日の時間枠は 3 つだけです

// 0-28800 (12am-8am) = 1.5
// 28800-61200 (8am-5pm) = 1
// 61200-86399 (5pm-11:50pm) = 1.5

$rate_table = array(
    'monday' => array (
        '28800' => 1.5,
        '61200' => 1,
        '86399' => 1.5
    )
);

この関数を使用して、hh:mm:ss を秒に変換します。

function time2seconds( $time ){
    list($h,$m,$s) = explode(':', $time);
    return ((int)$h*3600)+((int)$m*60)+(int)$s;
}

これは、レート テーブルを返す関数です。

function get_rates( $start, $end, $rate_table ) {

    $day = strtolower( date( 'l', strtotime( $start ) ) );

    // these should probably be pulled out and the function
    // should accept integers and not time strings
    $start_time = time2seconds( end( explode( 'T', $start ) ) );
    $end_time = time2seconds( end( explode( 'T', $end ) ) );

    $current_time = $start_time;

    foreach( $rate_table[$day] as $seconds => $multiplier ) {

        // loop until we get to the first slot
        if ( $start_time < $seconds ) {
            //$rate[ $seconds ] = ( $seconds < $end_time ? $seconds : $end_time ) - $current_time;

            $rate[] = array (

                'start' => $current_time,
                'stop' => $seconds < $end_time ? $seconds : $end_time,
                'duration' => ( $seconds < $end_time ? $seconds : $end_time ) - $current_time,
                'multiplier' => $multiplier

            );

            $current_time=$seconds;
            // quit the loop if the next time block is after clock out time
            if ( $current_time > $end_time ) break;
        }

    }

    return $rate;
}

使い方はこちら

$start = '2010-05-03T07:00:00';
$end = '2010-05-03T21:00:00';

print_r( get_rates( $start, $end, $rate_table ) );

戻り値

Array
(
    [0] => Array
        (
            [start] => 25200
            [stop] => 28800
            [duration] => 3600
            [multiplier] => 1.5
        )

    [1] => Array
        (
            [start] => 28800
            [stop] => 61200
            [duration] => 32400
            [multiplier] => 1
        )

    [2] => Array
        (
            [start] => 61200
            [stop] => 75600
            [duration] => 14400
            [multiplier] => 1.5
        )

)

基本的に、コードはレート テーブルをループし、指定されたタイム スロットから各レートに属する秒数を見つけます。

于 2010-05-08T01:31:59.010 に答える
0

これは基本的に@Loopoのアルゴリズムの適応です。

>まず、とを使用して時間を比較できると便利な<ので、最初にすべての時間(曜日+時間/分/秒)をUNIX時間オフセットに変換します。

// Code is messy and probably depends on how you structure things internally.

function timeOffset($dayOfWeek, $time) {
    // TODO Use standard libraries for this.
    $daysOfWeek = array('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday');

    $splitTime = explode(':', $time);
    $offset = (((int)array_search($dayOfWeek, $daysOfWeek) * 24 + (int)$time[0]) * 60 + (int)$time[1]) * 60 + (int)$time[2];

    return $offset;
}

$rateTable = array(
    'Monday' => array(
        '00:00:00' => 1.5,
        '08:00:00' => 1,
        '17:00:00' => 1.5,
    ),

    'Tuesday' => array(
        '00:00:00' => 1.5,
        '08:00:00' => 1,
        '17:00:00' => 1.5,
    )
);

$clockedTimes = array(
    array('Monday', '15:00:00', '21:00:00')
);

$rateTableConverted = array();

foreach($rateTable as $dayOfWeek => $times) {
    foreach($times as $time => $multiplier) {
        $offset = timeOffset($dayOfWeek, $time);
        $rateTableConverted[$offset] = $multiplier;
    }
}

ksort($rateTableConverted);

$clockedTimesConverted = array();

foreach($clockedTimes as $clock) {
    $convertedClock = array(
        'start' => timeOffset($clock[0], $clock[1]),
        'end'   => timeOffset($clock[0], $clock[2]),
    );

    $clockedTimesConverted[] = $convertedClock;
}

理想的には、これはすでに行われているはずです(たとえば、元のxx:yy:zz D文字列ではなく、これらの変換されたオフセットをデータベースに保存します)。

今度はスプリッター(クロージャーがないためヘルパー付き):

class BetweenValues {
    public $start, $end;

    public function __construct($start, $end) {
        $this->start = $start;
        $this->end = $end;
    }

    public function isValueBetween($value) {
        return $this->start <= $value && $value <= $this->end;
    }
}

class TimeRangeSplitter {
    private $rateTable;

    public function __construct($rateTable) {
        $this->rateTable = $rateTable;
    }

    private function getIntersectingTimes($times, $start, $end) {
        ksort($times);

        $betweenCalculator = new BetweenValues($start, $end);

        $intersecting = array_filter($times, array($betweenCalculator, 'isValueBetween'));

        /* If possible, get the time before this one so we can use its multiplier later. */
        if(key($intersecting) > 0 && current($intersecting) != $start) {
            array_unshift($intersecting, $times[key($intersecting) - 1]);
        }

        return array_values($intersecting);
    }

    public function getSplitTimes($start, $end) {
        $splits = array();

        $intersecting = $this->getIntersectingTimes(array_keys($this->rateTable), $start, $end);

        $curTime = $start;
        $curMultiplier = 0;

        foreach($intersecting as $sectionStartTime) {
            $splits[] = $this->getSplit($curTime, $sectionStartTime, $curMultiplier, $curTime);

            $curMultiplier = $this->rateTable[$sectionStartTime];
        }

        $splits[] = $this->getSplit($curTime, $end, $curMultiplier, $curTime);

        return array_filter($splits);
    }

    private function getSplit($time, $split, $multiplier, &$newTime) {
        $ret = NULL;

        if($time < $split) {
            $ret = array(
                'start' => $time,
                'end' => $split,
                'multiplier' => $multiplier,
            );

            $newTime = $split;
        }

        return $ret;
    }
}

そして、クラスを使用します:

$splitClockedTimes = array();
$splitter = new TimeRangeSplitter($rateTableConverted);

foreach($clockedTimesConverted as $clocked) {
    $splitClockedTimes[] = $splitter->getSplitTimes($clocked['start'], $clocked['end']);
}

var_dump($splitClockedTimes);

お役に立てれば。

于 2010-05-08T00:47:57.867 に答える
0

Einekiはアルゴリズムを解読しました。私の試みから欠落している部分は、各乗数範囲で利用可能な開始時間停止時間を持っていることでした。元のrateTableのデータ密度を評価するため、Einekiのconvert()ルーチンの内臓を使用して、configに格納されているテーブルを取得し、停止時間を追加しました。コードはすでに最小レートを自動作成(または入力)していますテーブル、コードの残りの部分がチョークしたり、警告/エラーをスローしたりしないことを保証するので、それを含めました。また、bill()とmap_shift()を一緒に凝縮しました。これは、この2つがお互いなしでは有用な目的を持っていないためです。

<?php

//-----------------------------------------------------------------------
function CompactSliceData($start, $stop, $multiplier)
// Used by the VerifyRateTable() to change the format of the multiplier table.
{
    return compact('start', 'stop','multiplier');
}

//-----------------------------------------------------------------------
function VerifyAndConvertRateTable($configRateTable)
// The rate table must contain keyed elements for all 7 days of the week. 
// Each subarray must contain at LEAST a single entry for '00:00:00' => 
// 1 and '23:59:59' => 1. If the first entry does not start at midnight, 
// a new element will be added to the array to represent this. If given 
// an empty array, this function will auto-vivicate a "default" rate 
// table where all time is billed at 1.0x.
{
    $weekDays = array('Monday', 'Tuesday', 'Wednesday', 
            'Thursday', 'Friday', 'Saturday', 
            'Sunday',);  // Not very i18n friendly?     

    $newTable = array();
    foreach($weekDays as $day)
    {
        if( !array_key_exists($day, $configRateTable) 
            || !is_array($configRateTable[$day]) 
            || !array_key_exists('00:00:00', $configRateTable[$day]) )
        {
            $configRateTable[$day]['00:00:00'] = 1;
        }

        if( !array_key_exists($day, $configRateTable) 
            || !is_array($configRateTable[$day]) 
            || !array_key_exists('23:59:59', $configRateTable[$day]) )
        {
            $configRateTable[$day]['23:59:59'] = 1;
        }

        // Convert the provided table format to something we can work with internally.
        // Ref: http://stackoverflow.com/questions/2792048/slicing-a-time-range-into-parts
        $newTable[$day] = array_slice(
                array_map(
                   'CompactSliceData',
                   array_keys($configRateTable[$day]),
                   array_keys(array_slice($configRateTable[$day],1)),
                   $configRateTable[$day]),
                0,-1);
    }
    return $newTable;
}

//-----------------------------------------------------------------------
function SliceTimeEntry($dayTable, $start, $stop)
// Iterate through a day's table of rate slices and split the $start/$stop
// into parts along the boundaries.
// Ref: http://stackoverflow.com/questions/2792048/slicing-a-time-range-into-parts
{
    $report = array();
    foreach($dayTable as $slice) 
    {
        if ($start < $slice['stop'] && $stop > $slice['start'])
        {
           $report[] = array(
                    'start'=> max($start, $slice['start']),
                    'stop' => min($stop, $slice['stop']),
                    'multiplier' => $slice['multiplier']
                );
        }
    }
    return $report;
}


/* examples */
$rateTable = array(
    'Monday' => array('00:00:00' => 1.5, '08:00:00' => 1, '17:00:00' => 1.5),
    'Tuesday' => array('00:00:00' => 1.5, '08:00:00' => 1, '17:00:00' => 1.5),
    'Wednesday' => array('00:00:00' => 1.5, '08:00:00' => 1, '17:00:00' => 1.5),
    'Thursday' => array('00:00:00' => 1.5, '08:00:00' => 1, '17:00:00' => 1.5),
    'Friday' => array('00:00:00' => 1.5, '08:00:00' => 1, '17:00:00' => 1.5),
    'Saturday' => array('00:00:00' => 1.5, '15:00:00' => 2),
    'Sunday' => array('00:00:00' => 1.5, '15:00:00' => 2),
);

$rateTable = VerifyAndConvertRateTable($rateTable);

print_r(SliceTimeEntry($rateTable['Monday'],'08:05:00','18:05:00'));
print_r(SliceTimeEntry($rateTable['Monday'],'08:05:00','12:00:00'));
print_r(SliceTimeEntry($rateTable['Tuesday'],'07:15:00','19:30:00'));
print_r(SliceTimeEntry($rateTable['Tuesday'],'07:15:00','17:00:00'));

?>

皆さん、特にエイネキに感謝します。

于 2010-05-11T20:04:59.557 に答える
0

私は次のようなものを提案します

get total time to allocate (workstop - workstart) 

find the start slot (the last element where time < workstart)
and how much of start slot is billable, reduce time left to allocate

move to next slot

while you have time left to allocate

   if the end time is in the same slot
       get the portion of the time slot that is billable
   else
       the whole slot is billable
       reduce the time to allocate by the slot time 


   (build your output array) and move to the next slot

loop while

日/時間/分の計算を処理しやすくするために、すべての時間を内部で秒に変換する方が簡単な場合があります。

于 2010-05-07T23:09:56.983 に答える