仅当日期值连续时才按多列对二维数组的行进行分组

发布于 2024-11-30 00:09:17 字数 985 浏览 1 评论 0原文

如果除 startdate 之外的所有值都相同,我想要合并多个关联数组。如果两个数组确实相同,我想合并它们并创建一个新元素 enddate ,以便 startdateenddate 显示日期范围。该范围内的所有日期都必须在原始数组中表示,即如果缺少日期,则不得合并其两侧的日期。

Array('color'=>'red','size'=>'large','shape'=>'circle','startdate'=>'2011-08-17')
Array('color'=>'red','size'=>'large','shape'=>'circle','startdate'=>'2011-08-18')
Array('color'=>'red','size'=>'large','shape'=>'square','startdate'=>'2011-08-20')

应该变成:

Array('color'=>'red','size'=>'large','shape'=>'circle','startdate'=>'2011-08-17','enddate'=>'2011-08-18')
Array('color'=>'red','size'=>'large','shape'=>'square','startdate'=>'2011-08-20')

到目前为止,我已经尝试循环遍历每个数组并创建一个多维数组:

foreach($arrays as $id => $array){
    $mergearray[$array['red']][$array['large']][$array['circle']] = $id;
}

以便检查另一个数组是否具有相同的值。我正在尝试使用这些数组来重建原始结构中的数组。

I have multiple associative arrays that I want to merge if all values except startdate are the same. If two arrays are indeed the same, I want to merge them and create a new element enddate so that startdate and enddate show the date range. All dates within the range must be represented in the original arrays, i.e. if a date is missing, the dates on either side of it must not be merged.

Array('color'=>'red','size'=>'large','shape'=>'circle','startdate'=>'2011-08-17')
Array('color'=>'red','size'=>'large','shape'=>'circle','startdate'=>'2011-08-18')
Array('color'=>'red','size'=>'large','shape'=>'square','startdate'=>'2011-08-20')

should become:

Array('color'=>'red','size'=>'large','shape'=>'circle','startdate'=>'2011-08-17','enddate'=>'2011-08-18')
Array('color'=>'red','size'=>'large','shape'=>'square','startdate'=>'2011-08-20')

So far I have tried looping through each array and creating a multidimensional array:

foreach($arrays as $id => $array){
    $mergearray[$array['red']][$array['large']][$array['circle']] = $id;
}

in order to check whether another array has the same values. I'm trying to use those arrays to reconstruct arrays in the original structure.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

心碎的声音 2024-12-07 00:09:17

避免使用日期/日期函数,这是浪费 CPU 时间,没有任何额外的好处(戴夫的回答)。
避免大量使用数组函数(adlawson 的回答),同样浪费时间,只需以智能方式排序数组和处理就会快得多。

但最重要的是,你想做什么,为什么这些数组是这样的,以及......这一切背后是否有 SQL ?

因为如果有的话,有比尝试合并格式错误的数组更简单的解决方案(恕我直言,您寻求的转换意味着在某个时刻出了问题。start_time 变成 end_time 表示尝试保留完整的项目历史,在 PHP 本身中没有地位(恕我直言)。

如果确实是您需要的,我会给您正确的 PHP 代码,但我很怀疑这是否是您应用程序前进的正确方法。

Avoid the day/ date functions it's a waste of cpu time for no added benefit (answer from Dave).
Avoid the massive array function use (answer from adlawson), same waste of time, just ordering arrays and processing the smart way will be much faster.

But mostly, what are you trying to do, why are those arrays like that and ... is there an SQL behind all that ?

Because if there is, there's much simpler solutions than attempting to merge badly-formed arrays (with all due respect, the transformation you seek implies that something went wrong at some point.. a start_time becoming an end_time denotes an attempt at keeping a full item history, which has no place in PHP itself imho).

I'll give you the right PHP code if it really is what you need but I have quite a doubt it's the correct way to go forward for your application.

去了角落 2024-12-07 00:09:17

像这样的事情应该可以解决问题:

$newArray = array();
$newLine = false;
$prev_day = false;
foreach ($array as $line) {
    $this_day = strtotime($line['startdate']);
    if (($newLine) && ($prev_day == strtotime('-1 day', $this_day))) {
        $newLine['enddate'] = $line['startdate'];
        $newArray[] = $newLine;
        $newLine = false;
    } elseif (!$newLine) {
        $newLine = $line;
    }
    if ($newLine) {
        $newLine['enddate'] = $line['startdate'];
    }
    $prev_day = $this_day;
}
$newArray[] = $newLine;

Something like this should do the trick:

$newArray = array();
$newLine = false;
$prev_day = false;
foreach ($array as $line) {
    $this_day = strtotime($line['startdate']);
    if (($newLine) && ($prev_day == strtotime('-1 day', $this_day))) {
        $newLine['enddate'] = $line['startdate'];
        $newArray[] = $newLine;
        $newLine = false;
    } elseif (!$newLine) {
        $newLine = $line;
    }
    if ($newLine) {
        $newLine['enddate'] = $line['startdate'];
    }
    $prev_day = $this_day;
}
$newArray[] = $newLine;
走走停停 2024-12-07 00:09:17

我看不到这种合并的实际实现,但这应该可行。它可能看起来很冗长,但它会处理一些事情。

/**
 * @example array_merge_allbutstartdate($array1, $array2, $array3, $array4, $arr...)
 * @param array $array1
 * @param array $array2, $arr...
 * @return array
 */
function array_merge_allbutstartdate(array $array1, array $array2)
{
    $out  = array();
    $date = array();
    $startKey = 'startdate';
    $endKey   = 'enddate';

    foreach (func_get_args() as $item) {
        if (!is_array($item)) {
            trigger_error('All arguments should be an array.', E_USER_ERROR);
        }

        $temp = null;
        if (isset($item[$startKey])) {
            $temp = $item[$startKey];
            unset($item[$startKey]);
        }

        if (!in_array($item, $out)) {
            $i = count($out);
            $out[] = $item;
        } else {
            $i = array_search($item, $out);
        }

        if (null !== $temp) {
            $date[$i][] = $temp;
        }
    }

    foreach ($date as $j => $row) {
        array_map('strtotime', $row);
        $start = array_search(min($row), $row);
        $end = array_search(max($row), $row);

        // Add start date
        $out[$j][$startKey] = $date[$j][$start];

        // Only add end date if it is not equal to start date
        if ($date[$j][$start] !== $date[$j][$end]) {
            $out[$j][$endKey] = $date[$j][$end];
        }
   }

    return $out;
}

I can't see the actual implementation of such a merge, but this should work. It may look long winded, but it takes care of a few things as it goes.

/**
 * @example array_merge_allbutstartdate($array1, $array2, $array3, $array4, $arr...)
 * @param array $array1
 * @param array $array2, $arr...
 * @return array
 */
function array_merge_allbutstartdate(array $array1, array $array2)
{
    $out  = array();
    $date = array();
    $startKey = 'startdate';
    $endKey   = 'enddate';

    foreach (func_get_args() as $item) {
        if (!is_array($item)) {
            trigger_error('All arguments should be an array.', E_USER_ERROR);
        }

        $temp = null;
        if (isset($item[$startKey])) {
            $temp = $item[$startKey];
            unset($item[$startKey]);
        }

        if (!in_array($item, $out)) {
            $i = count($out);
            $out[] = $item;
        } else {
            $i = array_search($item, $out);
        }

        if (null !== $temp) {
            $date[$i][] = $temp;
        }
    }

    foreach ($date as $j => $row) {
        array_map('strtotime', $row);
        $start = array_search(min($row), $row);
        $end = array_search(max($row), $row);

        // Add start date
        $out[$j][$startKey] = $date[$j][$start];

        // Only add end date if it is not equal to start date
        if ($date[$j][$start] !== $date[$j][$end]) {
            $out[$j][$endKey] = $date[$j][$end];
        }
   }

    return $out;
}
紫轩蝶泪 2024-12-07 00:09:17

鉴于您已经有一个数组数组,您实际上要做的是删除连续条目(每个时间跨度的第一个条目除外)。

您的算法将是:

$expected_start_time= 0; // initial val
foreach($all_entries as $k => &$v) {
    $start_time = strtotime($v['startdate']);
    if($start_time != $expected_start_time) {
        $range_start =& $v; // this is a range beginning. Put end date in here
    } else {
        $range_start['enddate'] = $v['startdate'];
        unset($all_entries[$k]);
    }
    $expected_date = strtotime('+1 day', $start_time);
}

这基本上是戴夫·柴尔德答案的更简单、更到位的版本。

Given that you have an array of arrays already, what you're actually trying to do is DELETE consecutive entries (other than the first entry for each time span).

Your algorithm would be:

$expected_start_time= 0; // initial val
foreach($all_entries as $k => &$v) {
    $start_time = strtotime($v['startdate']);
    if($start_time != $expected_start_time) {
        $range_start =& $v; // this is a range beginning. Put end date in here
    } else {
        $range_start['enddate'] = $v['startdate'];
        unset($all_entries[$k]);
    }
    $expected_date = strtotime('+1 day', $start_time);
}

This is basically a more minimal, and in-place version of Dave Child's answer.

心作怪 2024-12-07 00:09:17
function group_and_sort($data)
{
    $out = array();
    while($data) {

        // Shift off the first element
        $buffer = array_shift($data);
        $end_date = $buffer['startdate'];

        // Try to group successive elements...
        while($data) {

            // Case 1: Does the next element differ by more than just date?
            if(count(array_diff_assoc($buffer, $data[0])) > 1) {
                break;
            }

            // Case 2: Does the next element have an unexpected date?
            $expected_date = date('Y-m-d', strtotime('+1 day', strtotime($end_date)));
            if($data[0]['startdate'] != $expected_date) {
                break;
            }

            // Otherwise, push end_date forward and throw away the element
            $end_date = $data[0]['startdate'];
            array_shift($data);
        }

        // If the dates differ, record the range.
        if($buffer['startdate'] != $end_date) {
            $buffer['enddate'] = $end_date;
        }

        $out[] = $buffer;
    }

    return $out;
}

假设元素已按日期排序。如果不是,您可以

function sort_startdate($a, $b)
{
    return strcmp($a['startdate'], $b['startdate']);
}
usort($data, 'sort_startdate');

在将其传递给group_and_sort($data)之前使用:。

function group_and_sort($data)
{
    $out = array();
    while($data) {

        // Shift off the first element
        $buffer = array_shift($data);
        $end_date = $buffer['startdate'];

        // Try to group successive elements...
        while($data) {

            // Case 1: Does the next element differ by more than just date?
            if(count(array_diff_assoc($buffer, $data[0])) > 1) {
                break;
            }

            // Case 2: Does the next element have an unexpected date?
            $expected_date = date('Y-m-d', strtotime('+1 day', strtotime($end_date)));
            if($data[0]['startdate'] != $expected_date) {
                break;
            }

            // Otherwise, push end_date forward and throw away the element
            $end_date = $data[0]['startdate'];
            array_shift($data);
        }

        // If the dates differ, record the range.
        if($buffer['startdate'] != $end_date) {
            $buffer['enddate'] = $end_date;
        }

        $out[] = $buffer;
    }

    return $out;
}

Assumes the elements are already sorted by date. If they're not, you could use:

function sort_startdate($a, $b)
{
    return strcmp($a['startdate'], $b['startdate']);
}
usort($data, 'sort_startdate');

prior to passing it to group_and_sort($data).

去了角落 2024-12-07 00:09:17

确保您的输入数据至少按 startdate 值排序。

迭代行,并使用由 3 个标识列构建的字符串来确定分组。

如果第一次遇到某个组,或者当前行没有紧随专用组的最后一个日期,则开始一个新组。

如果多次遇到某个组并且具有连续的日期,则将该新日期写入新的结束日期

使用引用可以防止需要跟踪先前的值已被推入结果数组的位置。 演示

usort($array, fn($a, $b) => $a['startdate'] <=> $b['startdate']);
$result = [];
foreach ($array as $row) {
    $compositeKey = implode(
        '_',
        [$row['color'], $row['size'], $row['shape']]
    );
    if (
        !isset($ref[$compositeKey])
        || ($row['startdate'] != date('Y-m-d', strtotime(($ref[$compositeKey]['enddate'] ?? $ref[$compositeKey]['startdate']) . ' + 1 day')))
    ) {
        if (isset($ref[$compositeKey])) {
           unset($ref[$compositeKey]);
        }
        $ref[$compositeKey] = $row;
        $result[] =& $ref[$compositeKey];
    } else {
        $ref[$compositeKey]['enddate'] = $row['startdate'];
    }
}
var_export($result);

Ensure that your input data is at least sorted by startdate values.

Iterate over the rows, and use a string built from the 3 identifying columns to determine the grouping.

If a group is encountered for the first time or the current row does not immediately follow the last date of the dedicated group, then start a new group.

If a group is encountered more than once and has a consecutive date, then write that new date as the new enddate.

Using references prevents needing to keep track of where previous values have been pushed into the result array. Demo

usort($array, fn($a, $b) => $a['startdate'] <=> $b['startdate']);
$result = [];
foreach ($array as $row) {
    $compositeKey = implode(
        '_',
        [$row['color'], $row['size'], $row['shape']]
    );
    if (
        !isset($ref[$compositeKey])
        || ($row['startdate'] != date('Y-m-d', strtotime(($ref[$compositeKey]['enddate'] ?? $ref[$compositeKey]['startdate']) . ' + 1 day')))
    ) {
        if (isset($ref[$compositeKey])) {
           unset($ref[$compositeKey]);
        }
        $ref[$compositeKey] = $row;
        $result[] =& $ref[$compositeKey];
    } else {
        $ref[$compositeKey]['enddate'] = $row['startdate'];
    }
}
var_export($result);
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文