Dijkstra算法(即单源最短路径)解析

架构思想zkbhj 发表了文章 • 0 个评论 • 1694 次浏览 • 2017-12-04 15:58 • 来自相关话题

 单源最短路径问题,即在图中求出给定顶点到其它任一顶点的最短路径。在弄清楚如何求算单源最短路径问题之前,必须弄清楚最短路径的最优子结构性质。

一.最短路径的最优子结构性质

   该性质描述为:如果P(i,j)={Vi....Vk..Vs...Vj}是从顶点i到j的最短路径,k和s是这条路径上的一个中间顶点,那么P(k,s)必定是从k到s的最短路径。下面证明该性质的正确性。

   假设P(i,j)={Vi....Vk..Vs...Vj}是从顶点i到j的最短路径,则有P(i,j)=P(i,k)+P(k,s)+P(s,j)。而P(k,s)不是从k到s的最短距离,那么必定存在另一条从k到s的最短路径P'(k,s),那么P'(i,j)=P(i,k)+P'(k,s)+P(s,j)<P(i,j)。则与P(i,j)是从i到j的最短路径相矛盾。因此该性质得证。

二.Dijkstra算法

   由上述性质可知,如果存在一条从i到j的最短路径(Vi.....Vk,Vj),Vk是Vj前面的一顶点。那么(Vi...Vk)也必定是从i到k的最短路径。为了求出最短路径,Dijkstra就提出了以最短路径长度递增,逐次生成最短路径的算法。譬如对于源顶点V0,首先选择其直接相邻的顶点中长度最短的顶点Vi,那么当前已知可得从V0到达Vj顶点的最短距离dist[j]=min{dist[j],dist+matrix[i][j]}。根据这种思路,

假设存在G=<V,E>,源顶点为V0,U={V0},dist[i]记录V0到i的最短距离,path[i]记录从V0到i路径上的i前面的一个顶点。

1.从V-U中选择使dist[i]值最小的顶点i,将i加入到U中;

2.更新与i直接相邻顶点的dist值。(dist[j]=min{dist[j],dist[i]+matrix[i][j]})

3.知道U=V,停止。[/i][/i][/i][/i][/i][/i]

伪代码实现如下:function Dijkstra(Graph, source):

dist[source] ← 0 // Distance from source to source
prev[source] ← undefined // Previous node in optimal path initialization

for each vertex v in Graph: // Initialization
if v ≠ source // Where v has not yet been removed from Q (unvisited nodes)
dist[v] ← infinity // Unknown distance function from source to v
prev[v] ← undefined // Previous node in optimal path from source
end if
add v to Q // All nodes initially in Q (unvisited nodes)
end for

while Q is not empty:
u ← vertex in Q with min dist[u] // Source node in first case
remove u from Q

for each neighbor v of u: // where v has not yet been removed from Q.
alt ← dist[u] + length(u, v)
if alt < dist[v]: // A shorter path to v has been found
dist[v] ← alt
prev[v] ← u
end if
end for
end while

return dist, prev

end function[/u][/u][u]例如,下面是一个包含 9 个顶点的图,每条边分别标识了距离。
 
源顶点 source = 0,初始时,[/u][u][u]sptSet = {false, false, false, false, false, false, false, false, false};
distSet = {0, INF, INF, INF, INF, INF, INF, INF, INF};[/u][/u]
[u]将 0 包含至 sptSet 中;[/u][u][u]sptSet = {true, false, false, false, false, false, false, false, false};[/u][/u]
[u]更新 0 至其邻接节点的距离;[/u][u][u]distSet = {0, 4, INF, INF, INF, INF, INF, 8, INF};[/u][/u]
[u]



 
选择不在 sptSet 中的 Min Distance 的顶点,为顶点 1,则将 1 包含至 sptSet;[/u][u][u]sptSet = {true, true, false, false, false, false, false, false, false};[/u][/u]
[u]更新 1 至其邻接节点的距离;[/u][u][u]distSet = {0, 4, 12, INF, INF, INF, INF, 8, INF};[/u][/u]
[u]



 
选择不在 sptSet 中的 Min Distance 的顶点,为顶点 7,则将 7 包含至 sptSet;[/u][u][u]sptSet = {true, true, false, false, false, false, false, true, false};[/u][/u]
[u]更新 7 至其邻接节点的距离;[/u][u][u]distSet = {0, 4, 12, INF, INF, INF, 9, 8, 15};[/u][/u]
[u]



 
选择不在 sptSet 中的 Min Distance 的顶点,为顶点 6,则将 6 包含至 sptSet;[/u][u][u]sptSet = {true, true, false, false, false, false, true, true, false};[/u][/u]
[u]更新 6 至其邻接节点的距离;[/u][u][u]distSet = {0, 4, 12, INF, INF, 11, 9, 8, 15};[/u][/u]
[u]



 
以此类推,直到遍历结束。[/u][u][u]sptSet = {true, true, true, true, true, true, true, true, true};
distSet = {0, 4, 12, 19, 21, 11, 9, 8, 14};[/u][/u]
[u]



 
最终结果为源顶点 0 至所有顶点的距离:[/u][u][u]Vertex Distance from Source
0 0
1 4
2 12
3 19
4 21
5 11
6 9
7 8
8 14[/u][/u][u]参考文档:https://www.cnblogs.com/gaochu ... .html[/u] 查看全部
 单源最短路径问题,即在图中求出给定顶点到其它任一顶点的最短路径。在弄清楚如何求算单源最短路径问题之前,必须弄清楚最短路径的最优子结构性质。

一.最短路径的最优子结构性质

   该性质描述为:如果P(i,j)={Vi....Vk..Vs...Vj}是从顶点i到j的最短路径,k和s是这条路径上的一个中间顶点,那么P(k,s)必定是从k到s的最短路径。下面证明该性质的正确性。

   假设P(i,j)={Vi....Vk..Vs...Vj}是从顶点i到j的最短路径,则有P(i,j)=P(i,k)+P(k,s)+P(s,j)。而P(k,s)不是从k到s的最短距离,那么必定存在另一条从k到s的最短路径P'(k,s),那么P'(i,j)=P(i,k)+P'(k,s)+P(s,j)<P(i,j)。则与P(i,j)是从i到j的最短路径相矛盾。因此该性质得证。

二.Dijkstra算法

   由上述性质可知,如果存在一条从i到j的最短路径(Vi.....Vk,Vj),Vk是Vj前面的一顶点。那么(Vi...Vk)也必定是从i到k的最短路径。为了求出最短路径,Dijkstra就提出了以最短路径长度递增,逐次生成最短路径的算法。譬如对于源顶点V0,首先选择其直接相邻的顶点中长度最短的顶点Vi,那么当前已知可得从V0到达Vj顶点的最短距离dist[j]=min{dist[j],dist+matrix[i][j]}。根据这种思路,

假设存在G=<V,E>,源顶点为V0,U={V0},dist[i]记录V0到i的最短距离,path[i]记录从V0到i路径上的i前面的一个顶点。

1.从V-U中选择使dist[i]值最小的顶点i,将i加入到U中;

2.更新与i直接相邻顶点的dist值。(dist[j]=min{dist[j],dist[i]+matrix[i][j]})

3.知道U=V,停止。
[/i][/i][/i][/i][/i][/i]

伪代码实现如下:
function Dijkstra(Graph, source):

dist[source] ← 0 // Distance from source to source
prev[source] ← undefined // Previous node in optimal path initialization

for each vertex v in Graph: // Initialization
if v ≠ source // Where v has not yet been removed from Q (unvisited nodes)
dist[v] ← infinity // Unknown distance function from source to v
prev[v] ← undefined // Previous node in optimal path from source
end if
add v to Q // All nodes initially in Q (unvisited nodes)
end for

while Q is not empty:
u ← vertex in Q with min dist[u] // Source node in first case
remove u from Q

for each neighbor v of u: // where v has not yet been removed from Q.
alt ← dist[u] + length(u, v)
if alt < dist[v]: // A shorter path to v has been found
dist[v] ← alt
prev[v] ← u
end if
end for
end while

return dist, prev

end function[/u][/u]
[u]例如,下面是一个包含 9 个顶点的图,每条边分别标识了距离。
 
源顶点 source = 0,初始时,
[/u]
[u][u]sptSet = {false, false, false, false, false, false, false, false, false};
distSet = {0, INF, INF, INF, INF, INF, INF, INF, INF};[/u][/u]

[u]将 0 包含至 sptSet 中;[/u]
[u][u]sptSet = {true, false, false, false, false, false, false, false, false};[/u][/u]

[u]更新 0 至其邻接节点的距离;[/u]
[u][u]distSet = {0, 4, INF, INF, INF, INF, INF, 8, INF};[/u][/u]

[u]
281631318781591.jpg

 
选择不在 sptSet 中的 Min Distance 的顶点,为顶点 1,则将 1 包含至 sptSet;
[/u]
[u][u]sptSet = {true, true, false, false, false, false, false, false, false};[/u][/u]

[u]更新 1 至其邻接节点的距离;[/u]
[u][u]distSet = {0, 4, 12, INF, INF, INF, INF, 8, INF};[/u][/u]

[u]
281635078621307.jpg

 
选择不在 sptSet 中的 Min Distance 的顶点,为顶点 7,则将 7 包含至 sptSet;
[/u]
[u][u]sptSet = {true, true, false, false, false, false, false, true, false};[/u][/u]

[u]更新 7 至其邻接节点的距离;[/u]
[u][u]distSet = {0, 4, 12, INF, INF, INF, 9, 8, 15};[/u][/u]

[u]
281637061284639.jpg

 
选择不在 sptSet 中的 Min Distance 的顶点,为顶点 6,则将 6 包含至 sptSet;
[/u]
[u][u]sptSet = {true, true, false, false, false, false, true, true, false};[/u][/u]

[u]更新 6 至其邻接节点的距离;[/u]
[u][u]distSet = {0, 4, 12, INF, INF, 11, 9, 8, 15};[/u][/u]

[u]
281638430509082.jpg

 
以此类推,直到遍历结束。
[/u]
[u][u]sptSet = {true, true, true, true, true, true, true, true, true};
distSet = {0, 4, 12, 19, 21, 11, 9, 8, 14};[/u][/u]

[u]
281640391919096.jpg

 
最终结果为源顶点 0 至所有顶点的距离:
[/u]
[u][u]Vertex   Distance from Source
0 0
1 4
2 12
3 19
4 21
5 11
6 9
7 8
8 14[/u][/u]
[u]参考文档:https://www.cnblogs.com/gaochu ... .html[/u]

2017自如技术大赛研发组题目七:单源最短路径算法问题

PHPzkbhj 发表了文章 • 0 个评论 • 1893 次浏览 • 2017-12-04 00:08 • 来自相关话题

7. 自如今年年底就会拥有50W间房子,大家知道每间房房子都是需要配置完才能出租给自如客的,整个房租的配置过程是很复杂的,每天都需要大量的物流师傅将家电,家具等物品从仓库送到需要配置的每个房间。为了能在更多的时间配置更多的房子,我要不断的优化物流从仓库A到房间G的路径或者仓库B到房间E的距离,请写出一种算法给你任意图中两点,计算出两点之间的最短距离。
注:A B C D E F G H 都可能是仓库或者房间,点与点之间是距离
示例:






输入:AE 
输出:最短距离:22 
【PHP实现】<?php
/**
* Class Road
* 两点间最短路径计算类
* User: 郑凯
* Date: 2017/12/2
* Time: 19:48
* Dijkstra 算法,迪科斯彻算法(Dijkstra),又称为单源最短路径算法。
* Dijstra算法是运用贪心的策略,从源点开始,不断地通过相联通的点找出到其他点的最短距离。
*/

class Road {

//节点对应的字母名
public $pointName = array(
0 => "A",
1 => "B",
2 => "C",
3 => "D",
4 => "E",
5 => "F",
6 => "G",
7 => "H",

);

//地图上的点已经点之间的路径长度
public $points = array(
"A" => array(
"A" => 0,
"B" => 15,
"C" => 6,
"D" => 0,
"E" =>0,
"F" =>25,
"G" =>0,
"H" =>0
),
"B" => array(
"A" => 15,
"B" => 0,
"C" => 9,
"D" => 0,
"E" =>7,
"F" =>0,
"G" =>0,
"H" =>0
),
"C" => array(
"A" => 6,
"B" => 9,
"C" => 0,
"D" => 11,
"E" =>0,
"F" =>0,
"G" =>0,
"H" =>0
),
"D" => array(
"A" => 0,
"B" => 0,
"C" => 11,
"D" => 0,
"E" =>12,
"F" =>0,
"G" =>0,
"H" =>5
),
"E" => array(
"A" => 0,
"B" => 7,
"C" => 0,
"D" => 12,
"E" =>0,
"F" =>5,
"G" =>0,
"H" =>7
),
"F" => array(
"A" => 25,
"B" => 0,
"C" => 0,
"D" => 0,
"E" =>5,
"F" =>0,
"G" =>12,
"H" =>0
),
"G" => array(
"A" => 0,
"B" => 0,
"C" => 0,
"D" => 0,
"E" =>0,
"F" =>12,
"G" =>0,
"H" =>17
),
"H" => array(
"A" => 0,
"B" => 0,
"C" => 0,
"D" => 5,
"E" =>7,
"F" =>0,
"G" =>17,
"H" =>0
),
);

//有向图存储
private $graph = array(
array(0,15,6,0,0,25,0,0),
array(15,0,9,0,7,0,0,0),
array(6,9,0,11,0,0,0,0),
array(0,0,11,0,12,0,0,5),
array(0,7,6,12,0,5,0,7),
array(25,0,6,0,5,0,12,0),
array(0,0,0,0,0,12,0,17),
array(0,0,0,5,7,25,17,0)
);

//设置一个无穷大距离的数字
private $soBig = 999999;

private $path = array();


private function getOptimalPath($a, $b)
{

if($a == $b){
return "start and end could not be one point";
}


//读取已经选择的点
$idx = array_keys($this->points);
$a = array_search($a, $idx);
$b = array_search($b, $idx);

//存储路径上节点距离$a点的最小距离
$c = array();
$path = array();

//初始化距离起点最近的节点的最小距离
for($i=1;$i<8;$i++)
{
if($this->graph[$a][$i]>0)
{
$c[$i] = $this->graph[$a][$i];
$path[$i]['name'] = $this->pointName[$a];
$path[$i]['key'] = $a;
}
else
{
$c[$i] = $this->soBig;
}
}

//用于存储已经选择的节点
$selected = array($a);
$all = array(1,2,3,4,5,6,7);
unset($all[$a-1]);
$others = $all;

// n-1次循环完成转移节点任务
for($i=0;$i<count($this->graph)-1;$i++)
{
//查找剩余节点中距离起点最近的节点v
$current_min = $this->soBig;
$current_min_v = 0;
foreach($others as $k=>$v)
{
if($c[$v] < $current_min)
{

$current_min = $c[$v];
$current_min_v = $v;
}
}






//从$others更新顶点到$selected中
array_push($selected,$current_min_v);
array_splice($others,array_search($current_min_v,$others),1);


//更新最短路径
foreach($others as $k=>$u)
{

if($this->graph[$current_min_v][$u]!=0&&$c[$u]>$c[$current_min_v]+$this->graph[$current_min_v][$u])
{

$path[$u]['name'] = $this->pointName[$current_min_v];
$path[$u]['key'] = $current_min_v;
$c[$u] = $c[$current_min_v]+$this->graph[$current_min_v][$u];

}
}





}

$this->getRoadLine($path,$b,$a);
$finalPath = $this->pointName[$a];
foreach(array_reverse($this->path) as $x => $y){
$finalPath.=$y;
}
$finalPath.=$this->pointName[$b];

return $c[$b]." ".$finalPath;
}


//获取路径信息
public function getRoadLine($path,$b,$a)
{
if($path[$b]['key'] !== $a){
$this->path = $path[$b]['name'];
$this->getRoadLine($path,$path[$b]['key'],$a);
}

}

public function getResult($towPoint){

return $this->getOptimalPath(substr($towPoint,0,1),substr($towPoint, -1));

}


}

//获取输入变量
$params = trim(fgets(STDIN), " \t\n\r\0\x0B");
$road = new Road();
echo $road->getResult($params); 查看全部
7. 自如今年年底就会拥有50W间房子,大家知道每间房房子都是需要配置完才能出租给自如客的,整个房租的配置过程是很复杂的,每天都需要大量的物流师傅将家电,家具等物品从仓库送到需要配置的每个房间。为了能在更多的时间配置更多的房子,我要不断的优化物流从仓库A到房间G的路径或者仓库B到房间E的距离,请写出一种算法给你任意图中两点,计算出两点之间的最短距离。
注:A B C D E F G H 都可能是仓库或者房间,点与点之间是距离
示例:

QQ截图20171204000641.jpg


输入:AE 
输出:最短距离:22 
【PHP实现】
<?php
/**
* Class Road
* 两点间最短路径计算类
* User: 郑凯
* Date: 2017/12/2
* Time: 19:48
* Dijkstra 算法,迪科斯彻算法(Dijkstra),又称为单源最短路径算法。
* Dijstra算法是运用贪心的策略,从源点开始,不断地通过相联通的点找出到其他点的最短距离。
*/

class Road {

//节点对应的字母名
public $pointName = array(
0 => "A",
1 => "B",
2 => "C",
3 => "D",
4 => "E",
5 => "F",
6 => "G",
7 => "H",

);

//地图上的点已经点之间的路径长度
public $points = array(
"A" => array(
"A" => 0,
"B" => 15,
"C" => 6,
"D" => 0,
"E" =>0,
"F" =>25,
"G" =>0,
"H" =>0
),
"B" => array(
"A" => 15,
"B" => 0,
"C" => 9,
"D" => 0,
"E" =>7,
"F" =>0,
"G" =>0,
"H" =>0
),
"C" => array(
"A" => 6,
"B" => 9,
"C" => 0,
"D" => 11,
"E" =>0,
"F" =>0,
"G" =>0,
"H" =>0
),
"D" => array(
"A" => 0,
"B" => 0,
"C" => 11,
"D" => 0,
"E" =>12,
"F" =>0,
"G" =>0,
"H" =>5
),
"E" => array(
"A" => 0,
"B" => 7,
"C" => 0,
"D" => 12,
"E" =>0,
"F" =>5,
"G" =>0,
"H" =>7
),
"F" => array(
"A" => 25,
"B" => 0,
"C" => 0,
"D" => 0,
"E" =>5,
"F" =>0,
"G" =>12,
"H" =>0
),
"G" => array(
"A" => 0,
"B" => 0,
"C" => 0,
"D" => 0,
"E" =>0,
"F" =>12,
"G" =>0,
"H" =>17
),
"H" => array(
"A" => 0,
"B" => 0,
"C" => 0,
"D" => 5,
"E" =>7,
"F" =>0,
"G" =>17,
"H" =>0
),
);

//有向图存储
private $graph = array(
array(0,15,6,0,0,25,0,0),
array(15,0,9,0,7,0,0,0),
array(6,9,0,11,0,0,0,0),
array(0,0,11,0,12,0,0,5),
array(0,7,6,12,0,5,0,7),
array(25,0,6,0,5,0,12,0),
array(0,0,0,0,0,12,0,17),
array(0,0,0,5,7,25,17,0)
);

//设置一个无穷大距离的数字
private $soBig = 999999;

private $path = array();


private function getOptimalPath($a, $b)
{

if($a == $b){
return "start and end could not be one point";
}


//读取已经选择的点
$idx = array_keys($this->points);
$a = array_search($a, $idx);
$b = array_search($b, $idx);

//存储路径上节点距离$a点的最小距离
$c = array();
$path = array();

//初始化距离起点最近的节点的最小距离
for($i=1;$i<8;$i++)
{
if($this->graph[$a][$i]>0)
{
$c[$i] = $this->graph[$a][$i];
$path[$i]['name'] = $this->pointName[$a];
$path[$i]['key'] = $a;
}
else
{
$c[$i] = $this->soBig;
}
}

//用于存储已经选择的节点
$selected = array($a);
$all = array(1,2,3,4,5,6,7);
unset($all[$a-1]);
$others = $all;

// n-1次循环完成转移节点任务
for($i=0;$i<count($this->graph)-1;$i++)
{
//查找剩余节点中距离起点最近的节点v
$current_min = $this->soBig;
$current_min_v = 0;
foreach($others as $k=>$v)
{
if($c[$v] < $current_min)
{

$current_min = $c[$v];
$current_min_v = $v;
}
}






//从$others更新顶点到$selected中
array_push($selected,$current_min_v);
array_splice($others,array_search($current_min_v,$others),1);


//更新最短路径
foreach($others as $k=>$u)
{

if($this->graph[$current_min_v][$u]!=0&&$c[$u]>$c[$current_min_v]+$this->graph[$current_min_v][$u])
{

$path[$u]['name'] = $this->pointName[$current_min_v];
$path[$u]['key'] = $current_min_v;
$c[$u] = $c[$current_min_v]+$this->graph[$current_min_v][$u];

}
}





}

$this->getRoadLine($path,$b,$a);
$finalPath = $this->pointName[$a];
foreach(array_reverse($this->path) as $x => $y){
$finalPath.=$y;
}
$finalPath.=$this->pointName[$b];

return $c[$b]." ".$finalPath;
}


//获取路径信息
public function getRoadLine($path,$b,$a)
{
if($path[$b]['key'] !== $a){
$this->path = $path[$b]['name'];
$this->getRoadLine($path,$path[$b]['key'],$a);
}

}

public function getResult($towPoint){

return $this->getOptimalPath(substr($towPoint,0,1),substr($towPoint, -1));

}


}

//获取输入变量
$params = trim(fgets(STDIN), " \t\n\r\0\x0B");
$road = new Road();
echo $road->getResult($params);

2017自如技术大赛研发组题目六:木木熊问题(类约瑟夫环问题)

PHPzkbhj 发表了文章 • 0 个评论 • 1986 次浏览 • 2017-12-04 00:06 • 来自相关话题

6. 每年夏季自如都会组织夏季夏令营活动,每年组委会都会给来京参加夏令营的小朋友准备好多礼物,今年也是如此。
组委会准备了一些小游戏来获得这些礼物,其中有一个游戏是这样的:组委会让小朋友围成一个圈。然后随机制定一个数e,让编号为0的小朋友开始报数。每次喊道e-1的小朋友直接出列,淘汰出局。从本次喊道e-1的下一个小朋友开始,继续从0报数...e-1淘汰出局...一直这样进行....最后进行到最后一个小朋友,这位小朋友可以拿到“熊帅”亲笔签名的“木木”熊毛绒玩具。
(注:小朋友的编号是从0到n-1)
示例:
输入:n=1314 e=520 
输出:796 
【PHP实现】
<?php
/**
* Created by PhpStorm.
* User: ZKBHJ
* Date: 2017/12/2
* Time: 18:14
*/

class Summer {

//小朋友总数
public $childCounts = 1314;

//小朋友围成的圈儿
public $childCircle = array();

//指定的随机数
public $e = 520;


//初始化围成一个圈儿
private function initCircle()
{

for($i = 0; $i< $this->childCounts; $i++){
$this->childCircle[$i] = $i;
}
}


private function findChild()
{

$index = 0;
for ($i = 2; $i <= $this->childCounts; $i ++) {
$index = ($index + $this->e) % $i;
}
return $index;

}

public function getResult()
{
$this->initCircle();
return $this->findChild();
}

}

//获取输入变量
$params = explode(' ', trim(fgets(STDIN), " \t\n\r\0\x0B[]"));
foreach($params as $k => $v){
$params[$k] = explode('=',$v);
}

$summer = new Summer();
$summer->childCounts = $params[0][1];
$summer->e = $params[1][1];
echo $summer->getResult(); 查看全部
6. 每年夏季自如都会组织夏季夏令营活动,每年组委会都会给来京参加夏令营的小朋友准备好多礼物,今年也是如此。
组委会准备了一些小游戏来获得这些礼物,其中有一个游戏是这样的:组委会让小朋友围成一个圈。然后随机制定一个数e,让编号为0的小朋友开始报数。每次喊道e-1的小朋友直接出列,淘汰出局。从本次喊道e-1的下一个小朋友开始,继续从0报数...e-1淘汰出局...一直这样进行....最后进行到最后一个小朋友,这位小朋友可以拿到“熊帅”亲笔签名的“木木”熊毛绒玩具。
(注:小朋友的编号是从0到n-1)
示例:
输入:n=1314 e=520 
输出:796 
【PHP实现】
<?php
/**
* Created by PhpStorm.
* User: ZKBHJ
* Date: 2017/12/2
* Time: 18:14
*/

class Summer {

//小朋友总数
public $childCounts = 1314;

//小朋友围成的圈儿
public $childCircle = array();

//指定的随机数
public $e = 520;


//初始化围成一个圈儿
private function initCircle()
{

for($i = 0; $i< $this->childCounts; $i++){
$this->childCircle[$i] = $i;
}
}


private function findChild()
{

$index = 0;
for ($i = 2; $i <= $this->childCounts; $i ++) {
$index = ($index + $this->e) % $i;
}
return $index;

}

public function getResult()
{
$this->initCircle();
return $this->findChild();
}

}

//获取输入变量
$params = explode(' ', trim(fgets(STDIN), " \t\n\r\0\x0B[]"));
foreach($params as $k => $v){
$params[$k] = explode('=',$v);
}

$summer = new Summer();
$summer->childCounts = $params[0][1];
$summer->e = $params[1][1];
echo $summer->getResult();

2017自如技术大赛研发组题目五:订单最优分配问题

PHPzkbhj 发表了文章 • 0 个评论 • 1501 次浏览 • 2017-12-04 00:04 • 来自相关话题

5. 服务目前每月会对搬家师傅进行评级,根据师傅的评级排名结果,我们将优先保证最优师傅的全天订单
假设师傅每天工作8个小时,给定一天n个订单,每个订单其占用时间长为Ti,挣取价值为Vi,1
现请您为师傅安排订单,并保证师傅挣取价值最大
输入格式
输入n组数据,每组以逗号分隔并且每一个订单的编号,时长,挣取价值,数字以空格分隔
输出格式
输出争取价值,订单编号,订单编号按照价值由大到小排序,争取价值相同,则按照每小时平均争取价值由大到小排序
 
输入样例
[MV10001 2 100,MV10002 3 120,MV10003 1 200,MV10004 3 200,MV10005 4 70,MV10006 3 120,MV10007 2 10,MV10008 2 30,MV10009 6 500,MV10010 3 400]
输出样例
800 MV10010 MV10003 MV10004 
【PHP实现】
<?php
/**
* Class ServiceOrder
* 服务师傅订单最优分配计算
* User: 郑凯
* Date: 2017/12/2
* Time: 18:13
*/

class ServiceOrder {

//待分配的订单数组
public $orderList = array();

//服务师傅每天的工作时长
public $workTime = 8;

//最终筛选出来的收益最大订单列表
public $finalOrderList = array();



//按照预设好的格式,将数据进行初始化,装填到数组中
public function initOrder($orderList)
{

//去除前后多余的[]
$orderList = substr($orderList, 1);
$orderList = substr($orderList, 0, -1);

$tempArray = explode(",",$orderList);
foreach($tempArray as $key => $value){
$this->orderList[$key] = explode(" ",$value);
}

}

function arraySequence($array, $field, $sort = 'SORT_DESC')
{
$arrSort = array();
foreach ($array as $uniqid => $row) {
foreach ($row as $key => $value) {
$arrSort[$key][$uniqid] = $value;
}
}
array_multisort($arrSort[$field], constant($sort), $array);
return $array;
}

//给师傅按最大收益安排订单
public function planOrder()
{

//计算每个订单的平均赚取收益
foreach($this->orderList as $key => $value){
$this->orderList[$key][3] = $value[2]/$value[1];
}

//将订单按照平均赚取收益由高到低排序
$this->orderList = $this->arraySequence($this->orderList,'3','SORT_DESC');

//按照每日工作时长限制,取收益率最高的头几个订单
$workTime = 0;
foreach($this->orderList as $k => $v){
if($this->workTime - $workTime >= $v[1]){
$this->finalOrderList[] = $v;
$workTime+=$v[1];
}

}

}

public function printOrder()
{

//输出总收益
$string = array_sum(array_map(create_function('$val', 'return $val["2"];'), $this->finalOrderList))." ";

//输出订单编号(按单笔订单收益由高到低排序,相同的,则按照平均赚取收益由高到低排序)
foreach($this->finalOrderList as $k => $v){
$total[$k] = $v[2];
$average[$k] = $v[3];
}
array_multisort($total, SORT_DESC, $average, SORT_DESC, $this->finalOrderList);

//输出订单号
foreach($this->finalOrderList as $k => $v){
$string .= $v[0]." ";
}

return $string;

}

public function getResult()
{
$this->planOrder();
return $this->printOrder();
}



}


//获取输入变量
$orderList = trim(fgets(STDIN), " \t\n\r\0\x0B");

$service = new ServiceOrder();
$service->initOrder($orderList);
echo $service->getResult(); 查看全部
5. 服务目前每月会对搬家师傅进行评级,根据师傅的评级排名结果,我们将优先保证最优师傅的全天订单
假设师傅每天工作8个小时,给定一天n个订单,每个订单其占用时间长为Ti,挣取价值为Vi,1
现请您为师傅安排订单,并保证师傅挣取价值最大
输入格式
输入n组数据,每组以逗号分隔并且每一个订单的编号,时长,挣取价值,数字以空格分隔
输出格式
输出争取价值,订单编号,订单编号按照价值由大到小排序,争取价值相同,则按照每小时平均争取价值由大到小排序
 
输入样例
[MV10001 2 100,MV10002 3 120,MV10003 1 200,MV10004 3 200,MV10005 4 70,MV10006 3 120,MV10007 2 10,MV10008 2 30,MV10009 6 500,MV10010 3 400]
输出样例
800 MV10010 MV10003 MV10004 
【PHP实现】
<?php
/**
* Class ServiceOrder
* 服务师傅订单最优分配计算
* User: 郑凯
* Date: 2017/12/2
* Time: 18:13
*/

class ServiceOrder {

//待分配的订单数组
public $orderList = array();

//服务师傅每天的工作时长
public $workTime = 8;

//最终筛选出来的收益最大订单列表
public $finalOrderList = array();



//按照预设好的格式,将数据进行初始化,装填到数组中
public function initOrder($orderList)
{

//去除前后多余的[]
$orderList = substr($orderList, 1);
$orderList = substr($orderList, 0, -1);

$tempArray = explode(",",$orderList);
foreach($tempArray as $key => $value){
$this->orderList[$key] = explode(" ",$value);
}

}

function arraySequence($array, $field, $sort = 'SORT_DESC')
{
$arrSort = array();
foreach ($array as $uniqid => $row) {
foreach ($row as $key => $value) {
$arrSort[$key][$uniqid] = $value;
}
}
array_multisort($arrSort[$field], constant($sort), $array);
return $array;
}

//给师傅按最大收益安排订单
public function planOrder()
{

//计算每个订单的平均赚取收益
foreach($this->orderList as $key => $value){
$this->orderList[$key][3] = $value[2]/$value[1];
}

//将订单按照平均赚取收益由高到低排序
$this->orderList = $this->arraySequence($this->orderList,'3','SORT_DESC');

//按照每日工作时长限制,取收益率最高的头几个订单
$workTime = 0;
foreach($this->orderList as $k => $v){
if($this->workTime - $workTime >= $v[1]){
$this->finalOrderList[] = $v;
$workTime+=$v[1];
}

}

}

public function printOrder()
{

//输出总收益
$string = array_sum(array_map(create_function('$val', 'return $val["2"];'), $this->finalOrderList))." ";

//输出订单编号(按单笔订单收益由高到低排序,相同的,则按照平均赚取收益由高到低排序)
foreach($this->finalOrderList as $k => $v){
$total[$k] = $v[2];
$average[$k] = $v[3];
}
array_multisort($total, SORT_DESC, $average, SORT_DESC, $this->finalOrderList);

//输出订单号
foreach($this->finalOrderList as $k => $v){
$string .= $v[0]." ";
}

return $string;

}

public function getResult()
{
$this->planOrder();
return $this->printOrder();
}



}


//获取输入变量
$orderList = trim(fgets(STDIN), " \t\n\r\0\x0B");

$service = new ServiceOrder();
$service->initOrder($orderList);
echo $service->getResult();

2017自如技术大赛研发组题目四:蓄水池蓄水量计算问题

PHPzkbhj 发表了文章 • 0 个评论 • 2038 次浏览 • 2017-12-04 00:02 • 来自相关话题

4. 自如寓打算门口用砖头围立一个蓄水池子,从上面看凹凸不平,凹的地方会有积水。那如果用数字代表每个砖头的高度,就形成一个二维数据(如示例),请问这个池子能存储多少单位的水?
例如二维数组为:
9 9 9 9<br>
3 0 0 9<br>
7 8 9 6<br>
时,答案是中间的0,0位置可以存储3(因为其外面最低是3)个单位的水,因此答案为3+3=6
示例:
输入:[1 1 1 1,1 0 0 1,1 1 1 1]
输出:2
 
【PHP实现】<?php
/**
* Class Pool
* 蓄水池蓄水量计算类
* User: 郑凯
* Date: 2017/12/2
* Time: 15:35
*/

class WaterPool {

//水池数组(二维数组)
public $poolArray = array();
//水池中凹的地方的数量
public $aoCounts = 0;
//水池中木桶效应的最低“短板”数量(决定单个凹的地方的储水量)
public $bottomCounts = 0;


private function getWater()
{

//遍历水池数组(二维数组)
$bottomTemp = array("height"=>9,"counts"=>0);
foreach($this->poolArray as $key => $value){

foreach($value as $k => $v){

//统计水池中凹的地方的数量
if($v == 0){
$this->aoCounts++;
}

//统计水池中木桶效应的最低“短板”数量
if($bottomTemp["height"] > $v && $v != 0){
$bottomTemp["height"] = $v;
$bottomTemp["counts"] = 1;

}elseif($bottomTemp["height"] == $v){
$bottomTemp["counts"]++;
}

}

}

//计算水池中的最多储水量
return $this->aoCounts * $bottomTemp["height"];

}

//得到结果
public function getResult()
{
return $this->getWater();
}

}

//获取输入变量
$params = explode(',', trim(fgets(STDIN), " \t\n\r\0\x0B"));
foreach($params as $k => $v){
$params[$k] = explode(' ',$v);
}

//实例化
$pool = new WaterPool();
$pool->poolArray = $params;
echo $pool->getResult(); 查看全部
4. 自如寓打算门口用砖头围立一个蓄水池子,从上面看凹凸不平,凹的地方会有积水。那如果用数字代表每个砖头的高度,就形成一个二维数据(如示例),请问这个池子能存储多少单位的水?
例如二维数组为:
9 9 9 9<br>
3 0 0 9<br>
7 8 9 6<br>
时,答案是中间的0,0位置可以存储3(因为其外面最低是3)个单位的水,因此答案为3+3=6
示例:
输入:[1 1 1 1,1 0 0 1,1 1 1 1]
输出:2
 
【PHP实现】
<?php
/**
* Class Pool
* 蓄水池蓄水量计算类
* User: 郑凯
* Date: 2017/12/2
* Time: 15:35
*/

class WaterPool {

//水池数组(二维数组)
public $poolArray = array();
//水池中凹的地方的数量
public $aoCounts = 0;
//水池中木桶效应的最低“短板”数量(决定单个凹的地方的储水量)
public $bottomCounts = 0;


private function getWater()
{

//遍历水池数组(二维数组)
$bottomTemp = array("height"=>9,"counts"=>0);
foreach($this->poolArray as $key => $value){

foreach($value as $k => $v){

//统计水池中凹的地方的数量
if($v == 0){
$this->aoCounts++;
}

//统计水池中木桶效应的最低“短板”数量
if($bottomTemp["height"] > $v && $v != 0){
$bottomTemp["height"] = $v;
$bottomTemp["counts"] = 1;

}elseif($bottomTemp["height"] == $v){
$bottomTemp["counts"]++;
}

}

}

//计算水池中的最多储水量
return $this->aoCounts * $bottomTemp["height"];

}

//得到结果
public function getResult()
{
return $this->getWater();
}

}

//获取输入变量
$params = explode(',', trim(fgets(STDIN), " \t\n\r\0\x0B"));
foreach($params as $k => $v){
$params[$k] = explode(' ',$v);
}

//实例化
$pool = new WaterPool();
$pool->poolArray = $params;
echo $pool->getResult();

2017自如技术大赛研发组题目三:给定数字连接最大数

PHPzkbhj 发表了文章 • 0 个评论 • 1588 次浏览 • 2017-12-04 00:01 • 来自相关话题

3. 给定一个数组,将数组中的数字连接起来,求最大数。
示例:
输入:4,94,9,14,1
输出:9944141
 
【PHP实现】
<?php
/**
* Class Max
* 计算最大数字
* User: 郑凯
* Date: 2017/12/2
* Time: 14:02
*/

class Max {

//要进行处理的原始数字数组
public $numbers = array();
//经过处理后排序好的数字数组
public $lastNumber = array();


/**
* 获取整数某个位置的数字值
* @param $number
* @param int $position
* @return bool
*/
public function getPositionNumber($number,$position = 0)
{
//将数字当成字符串,将数字分离个位数存到数组中
for($i=0;$i<strlen($number);$i++){
$array[] = substr($number,$i,1);
}
return isset($array[$position])?$array[$position]:false;
}


/**
* 比较两个数,决定谁优先排在前面(即下面注释中的“最大数”)
* @param $a
* @param $b
* @return null
*/
private function compare($a,$b){

//默认最大值是null
$max = null;

//按两个数中数学概念中的最小数的长度进行从高到低每一位数字的对比
for($i = 0; $i<min(array(strlen($a),strlen($b))); $i++){

//如果同一位上的两个数字相等,则继续向下一位比较
if($this->getPositionNumber($a, $i) == $this->getPositionNumber($b, $i)){
continue;
}else{

//如果两个数字不等,则“最大数”是比较大的那个整数,跳出循环,不再继续比较
$this->getPositionNumber($a, $i) > $this->getPositionNumber($b, $i) ? $max = $a:$max = $b;
break;
}
}

//如果走到这里,说明数学概念中最小数的长度的头几位两个整数一样,那么就需要比较较长数字的下一位和较短数字的第一位
//首先判断这两个整数中哪个是短的
//如果两个整数长度一致,说明两个整数一样,则返回谁都可以
//如果较长数字的下一位比较短整数的第一位大,则“最大数”应该是前者,否则,应该是后者
if($max == null){
if(strlen($a)<strlen($b)){
$this->getPositionNumber($b, $i) > $this->getPositionNumber($a,0) ? $max = $b:$max = $a;
}elseif(strlen($a)>strlen($b)){

$this->getPositionNumber($a, $i) > $this->getPositionNumber($b,0) ? $max = $a:$max = $b;
}else{
$max = $a;
}
}

//返回“最大数”
return $max;
}


//主体函数,获取最大数字
public function getMaxNumber()
{

//初始化最大数,赋值为给定数字数组中的第一个
$max = array("key"=> 0, "value"=>$this->numbers[0]);

//对数组中的每个数字进行循环,将其与Max中的数字进行比较
for($j=0 ;$j < count($this->numbers); $j++){

$tempMax = $this->compare($this->numbers[$j],$max['value']);

//如果得到的“最大数”应不是原来的“最大数”,那么进行最大数的更新(更新key和value),这里的key主要用于下面的unset
if($tempMax != $max['value']){
$max['value'] = $tempMax;
$max['key'] = $j;
}
}

//把这一轮比较中的“最大数”放入数组中
$this->lastNumber[] = $max;
//将这个比较出来的“最大数”从原始数组中删除,不再让他捣乱啦
unset($this->numbers[$max['key']]);
//将原始数组重建key
$this->numbers = array_values($this->numbers);
//如果原始数组中还有数组,继续递归调用
if(count($this->numbers) > 0){
$this->getMaxNumber();
}


}

//输出最后连起来最大的数字
public function getResult()
{

$this->getMaxNumber();
foreach($this->lastNumber as $key => $value){
echo $value['value'];
}
}
}

//获取输入变量
$numbers = explode(',', trim(fgets(STDIN), " \t\n\r\0\x0B[]"));

//实例化
$max = new Max();
$max->numbers = $numbers;
$max->getResult(); 查看全部
3. 给定一个数组,将数组中的数字连接起来,求最大数。
示例:
输入:4,94,9,14,1
输出:9944141
 
【PHP实现】
<?php
/**
* Class Max
* 计算最大数字
* User: 郑凯
* Date: 2017/12/2
* Time: 14:02
*/

class Max {

//要进行处理的原始数字数组
public $numbers = array();
//经过处理后排序好的数字数组
public $lastNumber = array();


/**
* 获取整数某个位置的数字值
* @param $number
* @param int $position
* @return bool
*/
public function getPositionNumber($number,$position = 0)
{
//将数字当成字符串,将数字分离个位数存到数组中
for($i=0;$i<strlen($number);$i++){
$array[] = substr($number,$i,1);
}
return isset($array[$position])?$array[$position]:false;
}


/**
* 比较两个数,决定谁优先排在前面(即下面注释中的“最大数”)
* @param $a
* @param $b
* @return null
*/
private function compare($a,$b){

//默认最大值是null
$max = null;

//按两个数中数学概念中的最小数的长度进行从高到低每一位数字的对比
for($i = 0; $i<min(array(strlen($a),strlen($b))); $i++){

//如果同一位上的两个数字相等,则继续向下一位比较
if($this->getPositionNumber($a, $i) == $this->getPositionNumber($b, $i)){
continue;
}else{

//如果两个数字不等,则“最大数”是比较大的那个整数,跳出循环,不再继续比较
$this->getPositionNumber($a, $i) > $this->getPositionNumber($b, $i) ? $max = $a:$max = $b;
break;
}
}

//如果走到这里,说明数学概念中最小数的长度的头几位两个整数一样,那么就需要比较较长数字的下一位和较短数字的第一位
//首先判断这两个整数中哪个是短的
//如果两个整数长度一致,说明两个整数一样,则返回谁都可以
//如果较长数字的下一位比较短整数的第一位大,则“最大数”应该是前者,否则,应该是后者
if($max == null){
if(strlen($a)<strlen($b)){
$this->getPositionNumber($b, $i) > $this->getPositionNumber($a,0) ? $max = $b:$max = $a;
}elseif(strlen($a)>strlen($b)){

$this->getPositionNumber($a, $i) > $this->getPositionNumber($b,0) ? $max = $a:$max = $b;
}else{
$max = $a;
}
}

//返回“最大数”
return $max;
}


//主体函数,获取最大数字
public function getMaxNumber()
{

//初始化最大数,赋值为给定数字数组中的第一个
$max = array("key"=> 0, "value"=>$this->numbers[0]);

//对数组中的每个数字进行循环,将其与Max中的数字进行比较
for($j=0 ;$j < count($this->numbers); $j++){

$tempMax = $this->compare($this->numbers[$j],$max['value']);

//如果得到的“最大数”应不是原来的“最大数”,那么进行最大数的更新(更新key和value),这里的key主要用于下面的unset
if($tempMax != $max['value']){
$max['value'] = $tempMax;
$max['key'] = $j;
}
}

//把这一轮比较中的“最大数”放入数组中
$this->lastNumber[] = $max;
//将这个比较出来的“最大数”从原始数组中删除,不再让他捣乱啦
unset($this->numbers[$max['key']]);
//将原始数组重建key
$this->numbers = array_values($this->numbers);
//如果原始数组中还有数组,继续递归调用
if(count($this->numbers) > 0){
$this->getMaxNumber();
}


}

//输出最后连起来最大的数字
public function getResult()
{

$this->getMaxNumber();
foreach($this->lastNumber as $key => $value){
echo $value['value'];
}
}
}

//获取输入变量
$numbers = explode(',', trim(fgets(STDIN), " \t\n\r\0\x0B[]"));

//实例化
$max = new Max();
$max->numbers = $numbers;
$max->getResult();

2017自如技术大赛研发组题目二:110的算式计算

PHPzkbhj 发表了文章 • 0 个评论 • 1434 次浏览 • 2017-12-04 00:00 • 来自相关话题

2. 2016年自如将品质管理中心升级为安全与品质管理中心,并开通了自如举报邮箱。请看下边的算式
1 2 3 4 5 6 7 8 9 = ?(举报邮箱前缀数字);(备注110)
为了使等式成立,需要在数字间填入加号或者减号(可以不填,但不能填入其它符号)。之间没有填入符号的数字组合成一个数,例如:12+34+56+7-8+9 就是一种合格的填法。
请大家帮忙计算出多少个可能组合吧,至少3个以上算有效
示例
输入:[1 2 3 4 5 6 7 8 9 ]
输出:12+34+56+7-8+9
…………………………
 
【PHP实现】
<?php

class Fire {

private $numberItems = array();

//用于存储所有可能的情况
private $situation = array();

//设计的最终结果
private $result = 110;

//用于存储计算结果和设计结果一直的情况
private $rightSituation = array();

//用0,1,2分别代表空格、加号和减号
private $symbol = array(0=>"",1=>"+",2=>"-");

private $temp = array();


//获取所有可能出现的情况
public function getSituation($index)
{

if ($index == 8) {
$this->situation[] = $this->temp;
return;
}
//每个空有三种可能,0,1,2
for ($i = 0; $i < 3; $i++) {
$this->temp[$index] = $i;
$this->getSituation($index + 1);
$this->temp[$index] = 0; //恢复原来的状态
}
}

//获得表达式和输出结果
public function calculate()
{

foreach($this->situation as $k => $v){
$number = 1;
$expression = 1;
foreach($v as $key => $value){

switch($value)
{
//空格
case 0:
{
$expression = $expression.$this->numberItems[$key+1];
break;
}
//加号
case 1:
{
$expression = $expression . "+" .$this->numberItems[$key+1];
break;
}
//减号
case 2:
{
$expression = $expression . "-" .$this->numberItems[$key+1];
break;
}
}
}

//判断是否等于预期结果
$expressionResult = $this->getExpressionResult($expression);

if($this->result == $expressionResult){
echo $expression . " = ". $expressionResult, PHP_EOL;
}


}

}

//根据字符串表达式计算结果
public function getExpressionResult($expression)
{

//将加减符号进行分解排序
$all = str_split($expression,1);
$flag_reduce = array_keys($all,'-');
$flag_add = array_keys($all,'+');
$flags = array_merge($flag_add,$flag_reduce);
asort($flags);

foreach($flags as $key => $value){
array_search($value,$flag_reduce) === false ? $flags[$key] = "+":$flags[$key] = "-";
}

//重建key
$flags = array_values($flags);

//按顺序获得运算数
$expression_reduce = str_replace("+","-",$expression);
$array_numbers = explode('-',$expression_reduce);

//逐个数字进行运算
$result = 0;
foreach($array_numbers as $k => $v){

if($k == 0){
$result = $v;
continue;
}

$flag = isset($flags[$k-1])?$flags[$k-1]:"";
switch($flag){
case "+":
{
$result = $result + $v;
break;
}
case "-":
{

$result = $result - $v;
break;
}
case "":
{
break;
}

}

}

return $result;

}


public function getResult($base)
{
$this->numberItems = $base;
$this->getSituation(0);
$this->calculate();
}
}

//获取输入变量
$base = explode(' ', trim(fgets(STDIN), " \t\n\r\0\x0B[]"));
$fire = new Fire();
$fire->getResult($base); 查看全部
2. 2016年自如将品质管理中心升级为安全与品质管理中心,并开通了自如举报邮箱。请看下边的算式
1 2 3 4 5 6 7 8 9 = ?(举报邮箱前缀数字);(备注110)
为了使等式成立,需要在数字间填入加号或者减号(可以不填,但不能填入其它符号)。之间没有填入符号的数字组合成一个数,例如:12+34+56+7-8+9 就是一种合格的填法。
请大家帮忙计算出多少个可能组合吧,至少3个以上算有效
示例
输入:[1 2 3 4 5 6 7 8 9 ]
输出:12+34+56+7-8+9
…………………………
 
【PHP实现】
<?php

class Fire {

private $numberItems = array();

//用于存储所有可能的情况
private $situation = array();

//设计的最终结果
private $result = 110;

//用于存储计算结果和设计结果一直的情况
private $rightSituation = array();

//用0,1,2分别代表空格、加号和减号
private $symbol = array(0=>"",1=>"+",2=>"-");

private $temp = array();


//获取所有可能出现的情况
public function getSituation($index)
{

if ($index == 8) {
$this->situation[] = $this->temp;
return;
}
//每个空有三种可能,0,1,2
for ($i = 0; $i < 3; $i++) {
$this->temp[$index] = $i;
$this->getSituation($index + 1);
$this->temp[$index] = 0; //恢复原来的状态
}
}

//获得表达式和输出结果
public function calculate()
{

foreach($this->situation as $k => $v){
$number = 1;
$expression = 1;
foreach($v as $key => $value){

switch($value)
{
//空格
case 0:
{
$expression = $expression.$this->numberItems[$key+1];
break;
}
//加号
case 1:
{
$expression = $expression . "+" .$this->numberItems[$key+1];
break;
}
//减号
case 2:
{
$expression = $expression . "-" .$this->numberItems[$key+1];
break;
}
}
}

//判断是否等于预期结果
$expressionResult = $this->getExpressionResult($expression);

if($this->result == $expressionResult){
echo $expression . " = ". $expressionResult, PHP_EOL;
}


}

}

//根据字符串表达式计算结果
public function getExpressionResult($expression)
{

//将加减符号进行分解排序
$all = str_split($expression,1);
$flag_reduce = array_keys($all,'-');
$flag_add = array_keys($all,'+');
$flags = array_merge($flag_add,$flag_reduce);
asort($flags);

foreach($flags as $key => $value){
array_search($value,$flag_reduce) === false ? $flags[$key] = "+":$flags[$key] = "-";
}

//重建key
$flags = array_values($flags);

//按顺序获得运算数
$expression_reduce = str_replace("+","-",$expression);
$array_numbers = explode('-',$expression_reduce);

//逐个数字进行运算
$result = 0;
foreach($array_numbers as $k => $v){

if($k == 0){
$result = $v;
continue;
}

$flag = isset($flags[$k-1])?$flags[$k-1]:"";
switch($flag){
case "+":
{
$result = $result + $v;
break;
}
case "-":
{

$result = $result - $v;
break;
}
case "":
{
break;
}

}

}

return $result;

}


public function getResult($base)
{
$this->numberItems = $base;
$this->getSituation(0);
$this->calculate();
}
}

//获取输入变量
$base = explode(' ', trim(fgets(STDIN), " \t\n\r\0\x0B[]"));
$fire = new Fire();
$fire->getResult($base);

2017自如技术大赛研发组题目一:租金卡大放送

PHPzkbhj 发表了文章 • 0 个评论 • 1527 次浏览 • 2017-12-03 23:58 • 来自相关话题

题目:“司庆大放送,一元即租房”,司庆当日,签约入住的客户,住满30天,返还(首月租金-1元)额度的租金卡。租金卡的面额遵循了类似人民币的固定面额(1000元、500元、100元、50元、20元、10元、5元、1元),请实现一个算法,给客户返还的租金卡张数是最少的。
示例:
输入(租金卡金额):54 输出:5
 
【PHP实现】
<?php

/**
* Class Money
* 租金卡张数计算类
* User: 郑凯
* Date: 2017/12/2
* Time: 12:15
*/
class Money {

//租金卡总金额
public $totalValue;

//租金卡面额列表
public $couponList = array(1000=>0,500=>0,100=>0,50=>0,20=>0,10=>0,5=>0,1=>0);

//算法主体,计算当前金额中可以整租的最大基础面额,并计数
private function getMaxValue($money)
{

//按基础面额从高到低依次循环,如果能够被当前金额“整除”,则计算张数并递归
//当金额最后为0时,结束循环,完成计算
foreach ($this->couponList as $key => $value){
if($money / $key >= 1 && $money > 0){
//对应金额数组的个数增加
$this->couponList[$key] = floor($money / $key);
//更新下次计算金额
$money-=$key*floor($money / $key);
//递归调用
$this->getMaxValue($money);
}
}

}

//获取完整列表结果
public function getResult()
{
//调用计算函数进行计算
$this->getMaxValue($this->totalValue);

//输出结果
echo "当总金额为 ".$this->totalValue." 时,需要";
foreach ($this->couponList as $key => $value){
if($value > 0){
echo $value . "张面额为 ".$key." 元的租金卡, ";
}
}
echo "总计 ".array_sum($this->couponList)." 张!";
}

//获取总张数
public function getTotalCounts()
{
return array_sum($this->couponList);
}
}

//获取输入变量
$params = trim(fgets(STDIN), " \t\n\r\0\x0B[]");
//实例化类并初始化类
$myCoupon = new Money();
$myCoupon->totalValue = $params;
$myCoupon->getResult();
//\n是为了让输出结果在anycodes.cn的输出界面可以换行
echo "\n" . $myCoupon->getTotalCounts(); 查看全部
题目:“司庆大放送,一元即租房”,司庆当日,签约入住的客户,住满30天,返还(首月租金-1元)额度的租金卡。租金卡的面额遵循了类似人民币的固定面额(1000元、500元、100元、50元、20元、10元、5元、1元),请实现一个算法,给客户返还的租金卡张数是最少的。
示例:
输入(租金卡金额):54 输出:5
 
【PHP实现】
<?php

/**
* Class Money
* 租金卡张数计算类
* User: 郑凯
* Date: 2017/12/2
* Time: 12:15
*/
class Money {

//租金卡总金额
public $totalValue;

//租金卡面额列表
public $couponList = array(1000=>0,500=>0,100=>0,50=>0,20=>0,10=>0,5=>0,1=>0);

//算法主体,计算当前金额中可以整租的最大基础面额,并计数
private function getMaxValue($money)
{

//按基础面额从高到低依次循环,如果能够被当前金额“整除”,则计算张数并递归
//当金额最后为0时,结束循环,完成计算
foreach ($this->couponList as $key => $value){
if($money / $key >= 1 && $money > 0){
//对应金额数组的个数增加
$this->couponList[$key] = floor($money / $key);
//更新下次计算金额
$money-=$key*floor($money / $key);
//递归调用
$this->getMaxValue($money);
}
}

}

//获取完整列表结果
public function getResult()
{
//调用计算函数进行计算
$this->getMaxValue($this->totalValue);

//输出结果
echo "当总金额为 ".$this->totalValue." 时,需要";
foreach ($this->couponList as $key => $value){
if($value > 0){
echo $value . "张面额为 ".$key." 元的租金卡, ";
}
}
echo "总计 ".array_sum($this->couponList)." 张!";
}

//获取总张数
public function getTotalCounts()
{
return array_sum($this->couponList);
}
}

//获取输入变量
$params = trim(fgets(STDIN), " \t\n\r\0\x0B[]");
//实例化类并初始化类
$myCoupon = new Money();
$myCoupon->totalValue = $params;
$myCoupon->getResult();
//\n是为了让输出结果在anycodes.cn的输出界面可以换行
echo "\n" . $myCoupon->getTotalCounts();

PHP中二维数组如何求和?

回复

PHPzkbhj 回复了问题 • 1 人关注 • 1 个回复 • 3756 次浏览 • 2017-12-02 17:58 • 来自相关话题

深入剖析PHP输入流 php://input 并理解Content-Type的不同

PHPzkbhj 发表了文章 • 0 个评论 • 1182 次浏览 • 2017-12-01 10:09 • 来自相关话题

对于php://input介绍,PHP官方手册文档有一段话对它进行了很明确地概述:

“php://input allows you to read raw POST data. It is a less memory intensive alternative to $HTTP_RAW_POST_DATA and does not need any special php.ini directives. php://input is not available with enctype=”multipart/form-data”.

翻译过来,是这样:

“php://input可以读取没有处理过的POST数据。相较于$HTTP_RAW_POST_DATA而言,它给内存带来的压力较小,并且不需要特殊的php.ini设置。php://input不能用于enctype=multipart/form-data”

我们应该怎么去理解这段概述呢?我把它划分为三部分,逐步去理解:
读取POST数据不能用于multipart/form-data类型php://input VS $HTTP_RAW_POST_DATA
 读取POST数据

PHPer们一定很熟悉$_POST这个内置变量。$_POST与php://input存在哪些关联与区别呢?另外,客户端向服务端交互数据,最常用的方法除了POST之外,还有GET。既然php://input作为PHP输入流,它能读取GET数据吗?这二个问题正是我们这节需要探讨的主要内容。

经验告诉我们,从测试与观察中总结,会是一个很凑效的方法。这里,我写了几个脚本来帮助我们测试。@file 192.168.0.6:/phpinput_server.php 打印出接收到的数据

@file 192.168.0.8:/phpinput_post.php 模拟以POST方法提交表单数据

@file 192.168.0.8:/phpinput_xmlrpc.php 模拟以POST方法发出xmlrpc请求.

@file 192.168.0.8:/phpinput_get.php 模拟以GET方法提交表单表数

phpinput_server.php与phpinput_post.php//@file phpinput_server.php
$raw_post_data = file_get_contents('php://input', 'r');
echo "-------\$_POST------------------\n";
echo var_dump($_POST) . "\n";
echo "-------php://input-------------\n";
echo $raw_post_data . "\n";

//@file phpinput_post.php
$http_entity_body = 'n=' . urldecode('perfgeeks') . '&p=' . urldecode('7788');
$http_entity_type = 'application/x-www-form-urlencoded';
$http_entity_length = strlen($http_entity_body);
$host = '192.168.0.6';
$port = 80;
$path = '/phpinput_server.php';
$fp = fsockopen($host, $port, $error_no, $error_desc, 30);
if ($fp) {
fputs($fp, "POST {$path} HTTP/1.1\r\n");
fputs($fp, "Host: {$host}\r\n");
fputs($fp, "Content-Type: {$http_entity_type}\r\n");
fputs($fp, "Content-Length: {$http_entity_length}\r\n");
fputs($fp, "Connection: close\r\n\r\n");
fputs($fp, $http_entity_body . "\r\n\r\n");

while (!feof($fp)) {
$d .= fgets($fp, 4096);
}
fclose($fp);
echo $d;
}

我们可以通过使用工具ngrep抓取http请求包(因为我们需要探知的是php://input,所以我们这里只抓取http Request数据包)。我们来执行测试脚本phpinput_post.php@php /phpinput_post.php
HTTP/1.1 200 OK
Date: Thu, 08 Apr 2010 03:23:36 GMT
Server: Apache/2.2.3 (CentOS)
X-Powered-By: PHP/5.1.6
Content-Length: 160
Connection: close
Content-Type: text/html; charset=UTF-8
-------$_POST------------------
array(2) {
["n"]=> string(9) "perfgeeks"
["p"]=> string(4) "7788"
}
-------php://input-------------
n=perfgeeks&p=7788

通过ngrep抓到的http请求包如下: 192.168.0.8:57846 -> 192.168.0.6:80 [AP]
POST /phpinput_server.php HTTP/1.1..
Host: 192.168.0.6..Content-Type: application/x-www-form-urlencoded..Co
ntent-Length: 18..Connection: close....n=perfgeeks&p=7788....

仔细观察,我们不难发现:
$_POST数据,php://input 数据与httpd entity body数据是“一致”的。http请求中的Content-Type是application/x-www-form-urlencoded ,它表示http请求body中的数据是使用http的post方法提交的表单数据,并且进行了urlencode()处理。

(注:注意加粗部分内容,下文不再提示)。

我们再来看看脚本phpinput_xmlrpc.php的原文件内容,它模拟了一个POST方法提交的xml-rpc请求。//@file phpinput_xmlrpc.php
$http_entity_body = "\n\n jt_userinfo\n";
$http_entity_type = 'text/html';
$http_entity_length = strlen($http_entity_body);
$host = '192.168.0.6';
$port = 80;
$path = '/phpinput_server.php';
$fp = fsockopen($host, $port, $error_no, $error_desc, 30);
if ($fp) {
fputs($fp, "POST {$path} HTTP/1.1\r\n");
fputs($fp, "Host: {$host}\r\n");
fputs($fp, "Content-Type: {$http_entity_type}\r\n");
fputs($fp, "Content-Length: {$http_entity_length}\r\n");
fputs($fp, "Connection: close\r\n\r\n");
fputs($fp, $http_entity_body . "\r\n\r\n");
while (!feof($fp)) {
$d .= fgets($fp, 4096);
}

fclose($fp);
echo $d;
}

同样地,让我们来执行这个测试脚本:@php /phpinput_xmlrcp.php
HTTP/1.1 200 OK
Date: Thu, 08 Apr 2010 03:47:18 GMT
Server: Apache/2.2.3 (CentOS)
X-Powered-By: PHP/5.1.6
Content-Length: 154
Connection: close
Content-Type: text/html; charset=UTF-8

-------$_POST------------------
array(0) {
}

-------php://input-------------
<?xml version="1.0">
<methodcall>
<name>jt_userinfo</name>
</methodcall>

执行这个脚本的时候,我们通过ngrep抓取的http请求数据包如下:T 192.168.0.8:45570 -> 192.168.0.6:80 [AP]
POST /phpinput_server.php HTTP/1.1..
Host: 192.168.0.6..Content-Type: text/html..Content-Length: 75..Connec
tion: close....<?xml version="1.0">.<methodcall>. <name>jt_userinfo<
/name>.</methodcall>....

同样,我样也可以很容易地发现:
http请求中的Content-Type是text/xml。它表示http请求中的body数据是xml数据格式。服务端$_POST打印出来的是一个空数组,即与http entity body不一致了。这跟上个例子不一样了,这里的Content-Type是text/xml,而不是application/x-www-form-urlencoded而php://input数据还是跟http entity body数据一致。也就是php://input数据和$_POST数据不一致了。

我们再来看看通过GET方法提交表单数据的情况,php://input能不能读取到GET方法的表单数据?在这里,我们稍加改动一下phpinput_server.php文件,将$_POST改成$_GET。//@file phpinput_server.php
$raw_post_data = file_get_contents('php://input', 'r');
echo "-------\$_GET------------------\n";
echo var_dump($_GET) . "\n";
echo "-------php://input-------------\n";
echo $raw_post_data . "\n";

//@file phpinput_get.php
$query_path = 'n=' . urldecode('perfgeeks') . '&p=' . urldecode('7788');
$host = '192.168.0.6';
$port = 80;
$path = '/phpinput_server.php';
$d = '';
$fp = fsockopen($host, $port, $error_no, $error_desc, 30);
if ($fp) {
fputs($fp, "GET {$path}?{$query_path} HTTP/1.1\r\n");
fputs($fp, "Host: {$host}\r\n");
fputs($fp, "Connection: close\r\n\r\n");

while (!feof($fp)) {
$d .= fgets($fp, 4096);
}
fclose($fp);
echo $d;
}

同样,我们执行下一phpinput_get.php测试脚本,它模拟了一个通常情况下的GET方法提交表单数据。@php /phpinput_get.php
HTTP/1.1 200 OK
Date: Thu, 08 Apr 2010 07:38:15 GMT
Server: Apache/2.2.3 (CentOS)
X-Powered-By: PHP/5.1.6
Content-Length: 141
Connection: close
Content-Type: text/html; charset=UTF-8

-------$_GET------------------
array(2) {
["n"]=>
string(9) "perfgeeks"
["p"]=>
string(4) "7788"
}

-------php://input-------------

在这个时候,使用ngrep工具,捕获的相应的http请求数据包如下:T 192.168.0.8:36775 -> 192.168.0.6:80 [AP]
GET /phpinput_server.php?n=perfgeeks&p=7788 HTTP/1.1..
Host: 192.168.0.6..Connection: close....

比较POST方法提交的http请求,通常GET方法提交的请求中,entity body为空。同时,不会指定Content-Type和Content-Length。但是,如果强硬数据http entity body,并指明正确地Content-Type和Content-Length,那么php://input还可是读取得到http entity body数据,但不是$_GET数据。

所根据,上面几个探测,我们可以作出以下总结:
Content-Type取值为application/x-www-form-urlencoded时,php会将http请求body相应数据会填入到数组$_POST,填入到$_POST数组中的数据是进行urldecode()解析的结果。(其实,除了该Content-Type,还有multipart/form-data表示数据是表单数据,稍后我们介绍)php://input数据,只要Content-Type不为multipart/form-data(该条件限制稍后会介绍)。那么php://input数据与http entity body部分数据是一致的。该部分相一致的数据的长度由Content-Length指定。仅当Content-Type为application/x-www-form-urlencoded且提交方法是POST方法时,$_POST数据与php://input数据才是”一致”(打上引号,表示它们格式不一致,内容一致)的。其它情况,它们都不一致。php://input读取不到$_GET数据。是因为$_GET数据作为query_path写在http请求头部(header)的PATH字段,而不是写在http请求的body部分。

这也帮助我们理解了,为什么xml_rpc服务端读取数据都是通过file_get_contents(‘php://input’, ‘r’)。而不是从$_POST中读取,正是因为xml_rpc数据规格是xml,它的Content-Type是text/xml。

php://input碰到了multipart/form-data

上传文件的时候,表单的写法是这样的:<form enctype="multipart/form-data" action="phpinput_server.php" method="POST" >
<input type="text" name="n" />
<input type="file" name="f" />
<input type="submit" value="upload now" />
</form>

那么,enctype=multipart/form-data这里的意义,就是将该次http请求头部(head)中的Content-Type设置为multipart/form-data。请查阅RFC1867对它的描述。multipart/form-data也表示以POST方法提交表单数据,它还伴随了文件上传,所以会跟application/x-www-form-urlencoded数据格式不一样。它会以一更种更合理的,更高效的数据格式传递给服务端。我们提交该表单数据,并且打印出响应结果,如下:-------$_POST------------------
array(1) { ["n"]=> string(9) "perfgeeks" }
-------php://input-------------

同时,我们通过ngrep抓取的相应的http请求数据包如下:########
T 192.168.0.8:3981 -> 192.168.0.6:80 [AP]
POST /phpinput_server.php HTTP/1.1..Host: 192.168.0.6..Connection: kee
p-alive..User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) A
ppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.3 Safari/533.2..Re
ferer: http://192.168.0.6/phpinput_se ... ngth: 306..Ca
che-Control: max-age=0..Origin: http://192.168.0.6..Content-Type: mult
ipart/form-data; boundary=----WebKitFormBoundarybLQwkp4opIEZn1fA..Acce
pt: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q
=0.8,image/png,*/*;q=0.5..Accept-Encoding: gzip,deflate,sdch..Accept-L
anguage: zh-CN,zh;q=0.8..Accept-Charset: GBK,utf-8;q=0.7,*;q=0.3..Cook
ie: SESS3b0e658f87cf58240de13ab43a399df6=lju6o5bg8u04lv1ojugm2ccic6...
.
##
T 192.168.0.8:3981 -> 192.168.0.6:80 [AP]
------WebKitFormBoundarybLQwkp4opIEZn1fA..Content-Disposition: form-da
ta; name="n"....perfgeeks..------WebKitFormBoundarybLQwkp4opIEZn1fA..C
ontent-Disposition: form-data; name="f"; filename="test.txt"..Content-
Type: text/plain....i am file..multipart/form-data..------WebKitFormBo
undarybLQwkp4opIEZn1fA--..
##

从响应输出来比对,$_POST数据跟请求提交数据相符,即$_POST = array(‘n’ => ‘perfgeeks’)。这也跟http请求body中的数据相呼应,同时说明PHP把相应的数据填入$_POST全局变量。而php://input输出为空,没有输出任何东西,尽管http请求数据包中body不为空。这表示,当Content-Type为multipart/form-data的时候,即便http请求body中存在数据,php://input也为空,PHP此时,不会把数据填入php://input流。所以,可以确定: php://input不能用于读取enctype=multipart/form-data数据。

我们再比较这次通过ngrep抓取的http请求数据包,我们会发现,最大不同的一点是Content-Type后面跟了boundary定义了数据的分界符,bounday是随机生成的。另外一个大不一样的,就是http entity body中的数据组织结构不一样了。

上一节,我们概述了,当Content-Type为application/x-www-form-urlencoded时,php://input和$_POST数据是“一致”的,为其它Content-Type的时候,php://input和$_POST数据数据是不一致的。因为只有在Content-Type为application/x-www-form-urlencoded或者为multipart/form-data的时候,PHP才会将http请求数据包中的body相应部分数据填入$_POST全局变量中,其它情况PHP都忽略。而php://input除了在数据类型为multipart/form-data之外为空外,其它情况都可能不为空。通过这一节,我们更加明白了php://input与$_POST的区别与联系。所以,再次确认,php://input无法读取enctype=multipart/form-data数据,当php://input遇到它时,永远为空,即便http entity body有数据。

php://input VS $http_raw_post_data

相信大家对php://input已经有一定深度地了解了。那么$http_raw_post_data是什么呢?$http_raw_post_data是PHP内置的一个全局变量。它用于,PHP在无法识别的Content-Type的情况下,将POST过来的数据原样地填入变量$http_raw_post_data。它同样无法读取Content-Type为multipart/form-data的POST数据。需要设置php.ini中的always_populate_raw_post_data值为On,PHP才会总把POST数据填入变量$http_raw_post_data。

把脚本phpinput_server.php改变一下,可以验证上述内容:$raw_post_data = file_get_contents('php://input', 'r');
$rtn = ($raw_post_data == $HTTP_RAW_POST_DATA) ? 1 : 0;
echo $rtn;

执行测试脚本:@php phpinput_post.php
@php phpinput_get.php
@php phpinput_xmlrpc.php

得出的结果输出都是一样的,即都为1,表示php://input和$HTTP_RAW_POST_DATA是相同的。至于对内存的压力,我们这里就不做细致地测试了。有兴趣的,可以通过xhprof进行测试和观察。

以此,我们这节可以总结如下:
php://input 可以读取http entity body中指定长度的值,由Content-Length指定长度,不管是POST方式或者GET方法提交过来的数据。但是,一般GET方法提交数据时,http request entity body部分都为空。php://input 与$HTTP_RAW_POST_DATA读取的数据是一样的,都只读取Content-Type不为multipart/form-data的数据。

小结
Coentent-Type仅在取值为application/x-www-data-urlencoded和multipart/form-data两种情况下,PHP才会将http请求数据包中相应的数据填入全局变量$_POSTPHP不能识别的Content-Type类型的时候,会将http请求包中相应的数据填入变量$HTTP_RAW_POST_DATA只有Coentent-Type不为multipart/form-data的时候,PHP不会将http请求数据包中的相应数据填入php://input,否则其它情况都会。填入的长度,由Coentent-Length指定。只有Content-Type为application/x-www-data-urlencoded时,php://input数据才跟$_POST数据相一致。php://input数据总是跟$HTTP_RAW_POST_DATA相同,但是php://input比$HTTP_RAW_POST_DATA更凑效,且不需要特殊设置php.iniPHP会将PATH字段的query_path部分,填入全局变量$_GET。通常情况下,GET方法提交的http请求,body为空。
 
原文地址:http://www.nowamagic.net/academy/detail/12220520 查看全部
对于php://input介绍,PHP官方手册文档有一段话对它进行了很明确地概述:


“php://input allows you to read raw POST data. It is a less memory intensive alternative to $HTTP_RAW_POST_DATA and does not need any special php.ini directives. php://input is not available with enctype=”multipart/form-data”.


翻译过来,是这样:


“php://input可以读取没有处理过的POST数据。相较于$HTTP_RAW_POST_DATA而言,它给内存带来的压力较小,并且不需要特殊的php.ini设置。php://input不能用于enctype=multipart/form-data”


我们应该怎么去理解这段概述呢?我把它划分为三部分,逐步去理解:
  1. 读取POST数据
  2. 不能用于multipart/form-data类型
  3. php://input VS $HTTP_RAW_POST_DATA

 读取POST数据

PHPer们一定很熟悉$_POST这个内置变量。$_POST与php://input存在哪些关联与区别呢?另外,客户端向服务端交互数据,最常用的方法除了POST之外,还有GET。既然php://input作为PHP输入流,它能读取GET数据吗?这二个问题正是我们这节需要探讨的主要内容。

经验告诉我们,从测试与观察中总结,会是一个很凑效的方法。这里,我写了几个脚本来帮助我们测试。
@file 192.168.0.6:/phpinput_server.php 打印出接收到的数据

@file 192.168.0.8:/phpinput_post.php 模拟以POST方法提交表单数据

@file 192.168.0.8:/phpinput_xmlrpc.php 模拟以POST方法发出xmlrpc请求.

@file 192.168.0.8:/phpinput_get.php 模拟以GET方法提交表单表数

phpinput_server.php与phpinput_post.php
//@file phpinput_server.php
$raw_post_data = file_get_contents('php://input', 'r');
echo "-------\$_POST------------------\n";
echo var_dump($_POST) . "\n";
echo "-------php://input-------------\n";
echo $raw_post_data . "\n";

//@file phpinput_post.php
$http_entity_body = 'n=' . urldecode('perfgeeks') . '&p=' . urldecode('7788');
$http_entity_type = 'application/x-www-form-urlencoded';
$http_entity_length = strlen($http_entity_body);
$host = '192.168.0.6';
$port = 80;
$path = '/phpinput_server.php';
$fp = fsockopen($host, $port, $error_no, $error_desc, 30);
if ($fp) {
fputs($fp, "POST {$path} HTTP/1.1\r\n");
fputs($fp, "Host: {$host}\r\n");
fputs($fp, "Content-Type: {$http_entity_type}\r\n");
fputs($fp, "Content-Length: {$http_entity_length}\r\n");
fputs($fp, "Connection: close\r\n\r\n");
fputs($fp, $http_entity_body . "\r\n\r\n");

while (!feof($fp)) {
$d .= fgets($fp, 4096);
}
fclose($fp);
echo $d;
}

我们可以通过使用工具ngrep抓取http请求包(因为我们需要探知的是php://input,所以我们这里只抓取http Request数据包)。我们来执行测试脚本phpinput_post.php
@php /phpinput_post.php
HTTP/1.1 200 OK
Date: Thu, 08 Apr 2010 03:23:36 GMT
Server: Apache/2.2.3 (CentOS)
X-Powered-By: PHP/5.1.6
Content-Length: 160
Connection: close
Content-Type: text/html; charset=UTF-8
-------$_POST------------------
array(2) {
["n"]=> string(9) "perfgeeks"
["p"]=> string(4) "7788"
}
-------php://input-------------
n=perfgeeks&p=7788

通过ngrep抓到的http请求包如下:
  192.168.0.8:57846 -> 192.168.0.6:80 [AP]
POST /phpinput_server.php HTTP/1.1..
Host: 192.168.0.6..Content-Type: application/x-www-form-urlencoded..Co
ntent-Length: 18..Connection: close....n=perfgeeks&p=7788....

仔细观察,我们不难发现:
  1. $_POST数据,php://input 数据与httpd entity body数据是“一致”的。
  2. http请求中的Content-Type是application/x-www-form-urlencoded ,它表示http请求body中的数据是使用http的post方法提交的表单数据,并且进行了urlencode()处理。


(注:注意加粗部分内容,下文不再提示)。

我们再来看看脚本phpinput_xmlrpc.php的原文件内容,它模拟了一个POST方法提交的xml-rpc请求。
//@file phpinput_xmlrpc.php
$http_entity_body = "\n\n jt_userinfo\n";
$http_entity_type = 'text/html';
$http_entity_length = strlen($http_entity_body);
$host = '192.168.0.6';
$port = 80;
$path = '/phpinput_server.php';
$fp = fsockopen($host, $port, $error_no, $error_desc, 30);
if ($fp) {
fputs($fp, "POST {$path} HTTP/1.1\r\n");
fputs($fp, "Host: {$host}\r\n");
fputs($fp, "Content-Type: {$http_entity_type}\r\n");
fputs($fp, "Content-Length: {$http_entity_length}\r\n");
fputs($fp, "Connection: close\r\n\r\n");
fputs($fp, $http_entity_body . "\r\n\r\n");
while (!feof($fp)) {
$d .= fgets($fp, 4096);
}

fclose($fp);
echo $d;
}

同样地,让我们来执行这个测试脚本:
@php /phpinput_xmlrcp.php
HTTP/1.1 200 OK
Date: Thu, 08 Apr 2010 03:47:18 GMT
Server: Apache/2.2.3 (CentOS)
X-Powered-By: PHP/5.1.6
Content-Length: 154
Connection: close
Content-Type: text/html; charset=UTF-8

-------$_POST------------------
array(0) {
}

-------php://input-------------
<?xml version="1.0">
<methodcall>
<name>jt_userinfo</name>
</methodcall>

执行这个脚本的时候,我们通过ngrep抓取的http请求数据包如下:
T 192.168.0.8:45570 -> 192.168.0.6:80 [AP]
POST /phpinput_server.php HTTP/1.1..
Host: 192.168.0.6..Content-Type: text/html..Content-Length: 75..Connec
tion: close....<?xml version="1.0">.<methodcall>. <name>jt_userinfo<
/name>.</methodcall>....

同样,我样也可以很容易地发现:
  1. http请求中的Content-Type是text/xml。它表示http请求中的body数据是xml数据格式。
  2. 服务端$_POST打印出来的是一个空数组,即与http entity body不一致了。这跟上个例子不一样了,这里的Content-Type是text/xml,而不是application/x-www-form-urlencoded
  3. 而php://input数据还是跟http entity body数据一致。也就是php://input数据和$_POST数据不一致了。


我们再来看看通过GET方法提交表单数据的情况,php://input能不能读取到GET方法的表单数据?在这里,我们稍加改动一下phpinput_server.php文件,将$_POST改成$_GET。
//@file phpinput_server.php
$raw_post_data = file_get_contents('php://input', 'r');
echo "-------\$_GET------------------\n";
echo var_dump($_GET) . "\n";
echo "-------php://input-------------\n";
echo $raw_post_data . "\n";

//@file phpinput_get.php
$query_path = 'n=' . urldecode('perfgeeks') . '&p=' . urldecode('7788');
$host = '192.168.0.6';
$port = 80;
$path = '/phpinput_server.php';
$d = '';
$fp = fsockopen($host, $port, $error_no, $error_desc, 30);
if ($fp) {
fputs($fp, "GET {$path}?{$query_path} HTTP/1.1\r\n");
fputs($fp, "Host: {$host}\r\n");
fputs($fp, "Connection: close\r\n\r\n");

while (!feof($fp)) {
$d .= fgets($fp, 4096);
}
fclose($fp);
echo $d;
}

同样,我们执行下一phpinput_get.php测试脚本,它模拟了一个通常情况下的GET方法提交表单数据。
@php /phpinput_get.php
HTTP/1.1 200 OK
Date: Thu, 08 Apr 2010 07:38:15 GMT
Server: Apache/2.2.3 (CentOS)
X-Powered-By: PHP/5.1.6
Content-Length: 141
Connection: close
Content-Type: text/html; charset=UTF-8

-------$_GET------------------
array(2) {
["n"]=>
string(9) "perfgeeks"
["p"]=>
string(4) "7788"
}

-------php://input-------------

在这个时候,使用ngrep工具,捕获的相应的http请求数据包如下:
T 192.168.0.8:36775 -> 192.168.0.6:80 [AP]
GET /phpinput_server.php?n=perfgeeks&p=7788 HTTP/1.1..
Host: 192.168.0.6..Connection: close....

比较POST方法提交的http请求,通常GET方法提交的请求中,entity body为空。同时,不会指定Content-Type和Content-Length。但是,如果强硬数据http entity body,并指明正确地Content-Type和Content-Length,那么php://input还可是读取得到http entity body数据,但不是$_GET数据。

所根据,上面几个探测,我们可以作出以下总结:
  1. Content-Type取值为application/x-www-form-urlencoded时,php会将http请求body相应数据会填入到数组$_POST,填入到$_POST数组中的数据是进行urldecode()解析的结果。(其实,除了该Content-Type,还有multipart/form-data表示数据是表单数据,稍后我们介绍)
  2. php://input数据,只要Content-Type不为multipart/form-data(该条件限制稍后会介绍)。那么php://input数据与http entity body部分数据是一致的。该部分相一致的数据的长度由Content-Length指定。
  3. 仅当Content-Type为application/x-www-form-urlencoded且提交方法是POST方法时,$_POST数据与php://input数据才是”一致”(打上引号,表示它们格式不一致,内容一致)的。其它情况,它们都不一致。
  4. php://input读取不到$_GET数据。是因为$_GET数据作为query_path写在http请求头部(header)的PATH字段,而不是写在http请求的body部分。


这也帮助我们理解了,为什么xml_rpc服务端读取数据都是通过file_get_contents(‘php://input’, ‘r’)。而不是从$_POST中读取,正是因为xml_rpc数据规格是xml,它的Content-Type是text/xml。

php://input碰到了multipart/form-data

上传文件的时候,表单的写法是这样的:
<form enctype="multipart/form-data" action="phpinput_server.php" method="POST" >
<input type="text" name="n" />
<input type="file" name="f" />
<input type="submit" value="upload now" />
</form>

那么,enctype=multipart/form-data这里的意义,就是将该次http请求头部(head)中的Content-Type设置为multipart/form-data。请查阅RFC1867对它的描述。multipart/form-data也表示以POST方法提交表单数据,它还伴随了文件上传,所以会跟application/x-www-form-urlencoded数据格式不一样。它会以一更种更合理的,更高效的数据格式传递给服务端。我们提交该表单数据,并且打印出响应结果,如下:
-------$_POST------------------
array(1) { ["n"]=> string(9) "perfgeeks" }
-------php://input-------------

同时,我们通过ngrep抓取的相应的http请求数据包如下:
########
T 192.168.0.8:3981 -> 192.168.0.6:80 [AP]
POST /phpinput_server.php HTTP/1.1..Host: 192.168.0.6..Connection: kee
p-alive..User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) A
ppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.3 Safari/533.2..Re
ferer: http://192.168.0.6/phpinput_se ... ngth: 306..Ca
che-Control: max-age=0..Origin: http://192.168.0.6..Content-Type: mult
ipart/form-data; boundary=----WebKitFormBoundarybLQwkp4opIEZn1fA..Acce
pt: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q
=0.8,image/png,*/*;q=0.5..Accept-Encoding: gzip,deflate,sdch..Accept-L
anguage: zh-CN,zh;q=0.8..Accept-Charset: GBK,utf-8;q=0.7,*;q=0.3..Cook
ie: SESS3b0e658f87cf58240de13ab43a399df6=lju6o5bg8u04lv1ojugm2ccic6...
.
##
T 192.168.0.8:3981 -> 192.168.0.6:80 [AP]
------WebKitFormBoundarybLQwkp4opIEZn1fA..Content-Disposition: form-da
ta; name="n"....perfgeeks..------WebKitFormBoundarybLQwkp4opIEZn1fA..C
ontent-Disposition: form-data; name="f"; filename="test.txt"..Content-
Type: text/plain....i am file..multipart/form-data..------WebKitFormBo
undarybLQwkp4opIEZn1fA--..
##

从响应输出来比对,$_POST数据跟请求提交数据相符,即$_POST = array(‘n’ => ‘perfgeeks’)。这也跟http请求body中的数据相呼应,同时说明PHP把相应的数据填入$_POST全局变量。而php://input输出为空,没有输出任何东西,尽管http请求数据包中body不为空。这表示,当Content-Type为multipart/form-data的时候,即便http请求body中存在数据,php://input也为空,PHP此时,不会把数据填入php://input流。所以,可以确定: php://input不能用于读取enctype=multipart/form-data数据。

我们再比较这次通过ngrep抓取的http请求数据包,我们会发现,最大不同的一点是Content-Type后面跟了boundary定义了数据的分界符,bounday是随机生成的。另外一个大不一样的,就是http entity body中的数据组织结构不一样了。

上一节,我们概述了,当Content-Type为application/x-www-form-urlencoded时,php://input和$_POST数据是“一致”的,为其它Content-Type的时候,php://input和$_POST数据数据是不一致的。因为只有在Content-Type为application/x-www-form-urlencoded或者为multipart/form-data的时候,PHP才会将http请求数据包中的body相应部分数据填入$_POST全局变量中,其它情况PHP都忽略。而php://input除了在数据类型为multipart/form-data之外为空外,其它情况都可能不为空。通过这一节,我们更加明白了php://input与$_POST的区别与联系。所以,再次确认,php://input无法读取enctype=multipart/form-data数据,当php://input遇到它时,永远为空,即便http entity body有数据。

php://input VS $http_raw_post_data

相信大家对php://input已经有一定深度地了解了。那么$http_raw_post_data是什么呢?$http_raw_post_data是PHP内置的一个全局变量。它用于,PHP在无法识别的Content-Type的情况下,将POST过来的数据原样地填入变量$http_raw_post_data。它同样无法读取Content-Type为multipart/form-data的POST数据。需要设置php.ini中的always_populate_raw_post_data值为On,PHP才会总把POST数据填入变量$http_raw_post_data。

把脚本phpinput_server.php改变一下,可以验证上述内容:
$raw_post_data = file_get_contents('php://input', 'r');
$rtn = ($raw_post_data == $HTTP_RAW_POST_DATA) ? 1 : 0;
echo $rtn;

执行测试脚本:
@php phpinput_post.php
@php phpinput_get.php
@php phpinput_xmlrpc.php

得出的结果输出都是一样的,即都为1,表示php://input和$HTTP_RAW_POST_DATA是相同的。至于对内存的压力,我们这里就不做细致地测试了。有兴趣的,可以通过xhprof进行测试和观察。

以此,我们这节可以总结如下:
  1. php://input 可以读取http entity body中指定长度的值,由Content-Length指定长度,不管是POST方式或者GET方法提交过来的数据。但是,一般GET方法提交数据时,http request entity body部分都为空。
  2. php://input 与$HTTP_RAW_POST_DATA读取的数据是一样的,都只读取Content-Type不为multipart/form-data的数据。


小结
  1. Coentent-Type仅在取值为application/x-www-data-urlencoded和multipart/form-data两种情况下,PHP才会将http请求数据包中相应的数据填入全局变量$_POST
  2. PHP不能识别的Content-Type类型的时候,会将http请求包中相应的数据填入变量$HTTP_RAW_POST_DATA
  3. 只有Coentent-Type不为multipart/form-data的时候,PHP不会将http请求数据包中的相应数据填入php://input,否则其它情况都会。填入的长度,由Coentent-Length指定。
  4. 只有Content-Type为application/x-www-data-urlencoded时,php://input数据才跟$_POST数据相一致。
  5. php://input数据总是跟$HTTP_RAW_POST_DATA相同,但是php://input比$HTTP_RAW_POST_DATA更凑效,且不需要特殊设置php.ini
  6. PHP会将PATH字段的query_path部分,填入全局变量$_GET。通常情况下,GET方法提交的http请求,body为空。

 
原文地址:http://www.nowamagic.net/academy/detail/12220520