您好我试图抓住一个数组中的最后一个对象,其中键在for循环中低于$ i。
原因是因为数组键是unix时间,我需要抓住最接近当前时间的元素,在我的情况下是$ i。
如何在PHP或laravel中实现这一目标?
基本上这是因为我正在创建一个图表,我在包含数据日期的数组之间添加了错过的日期。
对于每个丢失的日期,它应使用当前日期数据之前的日期。
我希望它有意义。
编辑1:
我尝试使用的是laravels array_first helper:
$first = array_first($array, function ($value, $key) use ($i) {
return $key <= $i;
});
但是这没用。 我也尝试了phps end()函数,它获取了数组中的最后一个对象,但是这也没有用。
以下是整个功能的代码:
public function getPortfolioHistory($days, $currency, $type)
{
if(count($this->getHoldingLogs()) > 0)
{
//$history = Cache::remember('1213:'.$this->id, 1440, function() use ($days, $currency){
$logs = $this->getHoldingLogs(); // Get all logs
$fromdate = strtotime(\Carbon\Carbon::today()->subDays($days)); // Gets the last day it should get prices from
$array = array(); // Array that handles dates
$token = array(); // Array that handles token amounts for calculations
$cmc_id = array(); //Array that handles coinmarketcap ids
$pricelog = array(); //Array that handles coinmarketcap historical prices
$paid_usd = 0;
foreach($logs as $log)
{
// This block checks if the holdinglogs date is older than the fromdate, it should set its date to the $fromdate (starting date)
if(strtotime($log->date) < $fromdate)
{
$log->date = date('Y-m-d H:i:s', $fromdate);
}
// Just a variable for resetting $worth_usd for each $log
$worth_usd = 0;
$paid_usd = 0;
// Reformat the date
$date = date('Y-m-d', strtotime($log->date));
// Checks if this token has been set or not in the $token array, if it has been set we update its amount.
if(!isSet($token[$log->token]))
{
$token[$log->token] = array('token' => $log->token, 'amount' => $log->amount, 'cmc_id' => $log->token_cmc_id, 'paid' => $log->paid_usd);
} else {
if($log->paid_usd == 0 && $log->amount < 0)
{
$log->paid_usd = 0 - (($token[$log->token]['paid'] / $token[$log->token]['amount']) * (abs($log->amount)));
$log->save();
}
$token[$log->token] = array('token' => $log->token, 'amount' => $token[$log->token]['amount'] + $log->amount, 'cmc_id' => $log->token_cmc_id, 'paid' => $token[$log->token]['paid'] + $log->paid_usd);
}
// Since we need to recalculate using the amounts each day, we loop through the tokens in $token
foreach($token as $key => $coin)
{
// This checks if we have already read in the price array for this token, if it has not been set we get it.
if(!isSet($pricelog[$coin['cmc_id']]) && $coin['cmc_id'] != "FIAT")
{
$pricelog[$coin['cmc_id']] = $this->getHistorical($coin['cmc_id']);
}
// If the token is a fiat we set its price to 1 and then we multiply the amount * price * fiat later in the code.
if($coin['cmc_id'] != "FIAT")
{
$price = $this->returnPrice($pricelog[$coin['cmc_id']], $log->date);
} else {
$price = 1;
}
// Here we calculate the worth.
if($coin['cmc_id'] != "FIAT")
{
$worth_usd += $coin['amount'] * $price['USD'];
} else {
if($key == "USD")
{
$worth_usd += $coin['amount'];
} else {
$worth_usd += $coin['amount'] / (1 / Multiplier::where('currency', $coin['token'])->select('price')->first()->price); // If its a fiat and its not USD we convert it to usd.
}
}
$paid_usd += $coin['paid'];
}
// Sets the record in the array with all the needed data.
$array = array_set($array, strtotime($date), array('worth_usd' => $worth_usd, 'tokens' => $token, 'date' => $date, 'paid' => $paid_usd, 'generated' => "no"));
}
// Sort the array by key.
ksort($array);
// This is the method depixel wrote to add missing dates.
for($i = key($array); $i <= strtotime(date('Y-m-d')); $i += 86400)
{
if(isSet($array[$i]) == false)
{
// This gets the record before $i, so we can read in the latest holdings.
$latest = end($array);
echo date('Y-m-d', $i) . " - " . $latest['date'] . "<br>";
// Set worth_usd to 0.
$worth_usd = 0;
$paid_usd = 0;
// Formats the $i unix time to a suitable date.
$missingdate = date('Y-m-d', $i);
foreach($latest['tokens'] as $key => $coin)
{
if(!isSet($pricelog[$coin['cmc_id']]) && $coin['cmc_id'] != "FIAT")
{
$pricelog[$coin['cmc_id']] = $this->getHistorical($coin['cmc_id']);
}
if($coin['cmc_id'] != "FIAT")
{
$price = $this->returnPrice($pricelog[$coin['cmc_id']], $missingdate);
} else {
$price = 1;
}
// Worth of this holding this day
if($coin['cmc_id'] != "FIAT")
{
$worth_usd += $coin['amount'] * $price['USD'];
} else {
if($key == "USD")
{
$worth_usd += $coin['amount'];
} else {
$worth_usd += $coin['amount'] / (1 / Multiplier::where('currency', $coin['token'])->select('price')->first()->price);
}
}
$paid_usd += $coin['paid'];
}
if($missingdate != date('Y-m-d'))
{
$array = array_set($array, $i, array('worth_usd' => $worth_usd, 'tokens' => $token, 'date' => $missingdate, 'paid' => $paid_usd, 'generated' => 'yes'));
}
}
}
// Sort array by time
ksort($array);
// Print it out
$history = $array;
// });
echo "<pre>";
echo var_dump($history);
echo "</pre>";
foreach($history as $worth)
{
if($type == "Value")
{
echo $worth['worth_usd'].", ";
} else {
echo $worth['worth_usd'] - $worth['paid'],", ";
}
}
}
}
编辑3:
我实际上是通过使用它解决了它:
$latest = null;
foreach ($array as $key => $date) {
if ($latest === null || $key < $i && $key > key($latest)) {
$latest = $date;
}
}
但如果有人有更好的方法,请告诉我。
答案 0 :(得分:0)
(旁注:你的解决方案实际上不起作用,因为它有一个很大的错误:key($latest)
表示“在名为$latest
的数组的当前索引处获取密钥的值” - 但$latest
不是您正在搜索的数组,也不知道它来自$array
中的键。)
你正在排序$array
,这很好,但是有一个重要的好处就是让你错过了:能够进行二分搜索。
不幸的是,由于你的数组键不是连续的和从零开始的(即实际索引),二进制搜索会有点困难。所以我会改变这一行(顺便说一句,你在这里使用array_set()
是完全没必要的):
$array = array_set($array, strtotime($date), array('worth_usd' => $worth_usd, 'tokens' => $token, 'date' => $date, 'paid' => $paid_usd, 'generated' => "no"));
到此:
$array[] = ['date' => $date, 'time' => strtotime($date), 'worth_usd' => $worth_usd, 'tokens' => $token, 'paid' => $paid_usd, 'generated' => 'no'];
现在您的数组键只是索引,因此您可以执行二进制搜索以查找当前日期之前的最新条目的索引:
$lbound = 0;
$rbound = count($array) - 1;
$current_time = time(); // or whatever timestamp you're looking for
while($rbound - $lbound > 1) {
$midpoint = $lbound + ($rbound - $lbound) / 2;
if ($array[$midpoint]['time'] <= $current_time) {
$lbound = $midpoint;
continue;
}
$rbound = $midpoint;
}
if ($array[$lbound]['time'] === $current_time) {
$lbound--;
}
// Now $lbound is the index of the most recent entry before the current time
// -- unless the first element in the array is the current time, in which
// case $lbound will be -1, which would represent failure or "not found".
// However, even in that case, the insertion point for the new entry
// would still be $lbound + 1.
// So you would insert a new entry like this:
// array_splice($array, $lbound + 1, 0, [$new_entry]);
// don't forget the [ ] around that array variable!
尚未测试该代码,所以希望它是正确的。唯一需要注意的是,这段代码不适用于只有两个元素的数组(但你甚至不需要对那些小的元素进行二进制搜索)。
[编辑]
如果你需要考虑一个2元素的数组,你可以使用下面的代码,虽然由于额外的if
而不是那么高效:
$lbound = $midpoint = 0;
$rbound = count($array) - 1;
$current_time = time(); // or whatever timestamp you're looking for
while($lbound <= $rbound) {
$midpoint = $lbound + ($rbound - $lbound) / 2;
if ($array[$midpoint]['time'] === $current_time) {
break;
}
if ($array[$midpoint]['time'] < $current_time) {
$lbound = $midpoint + 1;
continue;
}
$rbound = $midpoint - 1;
}
if ($array[$midpoint]['time'] >= $current_time) {
$midpoint--;
}
// and in this case, $midpoint will hold the position of the most recent
// entry before the current time -- unless the first element in the array
// is the current time, in which case $midpoint will be -1, which would
// represent failure or "not found".
// However, even in that case, the insertion point for the new entry
// would still be $midpoint + 1.
// So you would insert a new entry like this:
// array_splice($array, $midpoint + 1, 0, [$new_entry]);
// don't forget the [ ] around that array variable!