在PHP中将数据操作为类似矩阵的格式

时间:2011-08-16 07:48:22

标签: php

给定输入,显示图像的标签分配,如下所示(从php:// stdin逐行读取,因为输入可能变得相当大)

image_a tag_lorem
image_a tag_ipsum
image_a tag_amit
image_b tag_sit
image_b tag_dolor
image_b tag_ipsum
... (there are more lines, may get up to a million)

输入的输出如下所示。基本上它与另一个条目的格式相同,表示图像标签组合是否存在于输入中。请注意,对于每个图像,它将列出所有可用标记,并通过在每行末尾使用1/0来显示是否将标记分配给图像。

image_a tag_sit 0
image_a tag_lorem 1
image_a tag_dolor 0
image_a tag_ipsum 1
image_a tag_amit 1
image_b tag_sit 1
image_b tag_lorem 0
image_b tag_dolor 1
image_b tag_ipsum 1
image_b tag_amit 0
... (more)

我已经在那里发布了我那不那么有效的解决方案。为了更好地了解输入和输出,我通过stdin将745行(这解释了10个图像的标记分配)输入到脚本中,并且在执行脚本后使用大约0.4MB的内存接收555025行。但是,由于磁盘I / O活动繁重(写入/读取临时列缓存文件时),它可能会更快地终止硬盘。

还有其他办法吗?我有另一个脚本可以将stdin转换成这样的东西(不确定这是否有用)

image_foo tag_lorem tag_ipsum tag_amit
image_bar tag_sit tag_dolor tag_ipsum

p / s:tag_ *的顺序并不重要,但它必须对所有行都相同,即这不是我想要的(注意tag_ *的顺序对于tag_a和tag_b都是不一致的)

image_foo tag_lorem 1
image_foo tag_ipsum 1
image_foo tag_dolor 0
image_foo tag_sit 0
image_foo tag_amit 1
image_bar tag_sit 1
image_bar tag_lorem 0
image_bar tag_dolor 1
image_bar tag_ipsum 1
image_bar tag_amit 0

p / s2:直到我读完标准时才知道tag_ *的范围

p / s3:我不明白为什么我要投票,如果需要澄清我很乐意提供他们,我不是想取笑或在这里发布废话。我再次重写了这个问题,使它听起来更像是一个真正的问题(?)。但是,该脚本实际上并不关心输入到底是什么或者是否使用了数据库(好吧,如果你必须知道,则从RDF数据存储中检索数据)因为我希望脚本可用于其他类型数据只要输入格式正确(因此这个问题的原始版本非常普遍)。

p / s4:我试图避免使用数组,因为我想尽可能避免内存不足错误(如果745行只显示10个图像将扩展到550k行,想象一下我有100,1000,甚至10000多张图片。)

p / s5:如果您有其他语言的答案,请随时在此处发布。我曾想过用clojure解决这个问题,但仍然找不到合适的方法。

4 个答案:

答案 0 :(得分:1)

抱歉,maby我误解了你 - 这看起来太容易了:

$stdin = fopen('php://stdin', 'r');
$columns_arr=array();
$rows_arr=array();
function set_empty_vals(&$value,$key,$columns_arr) {
    $value=array_merge($columns_arr,$value);
    ksort($value);
    foreach($value AS $val_name => $flag) {
        echo $key.' '.$val_name.' '.$flag.PHP_EOL;
    }
    $value=NULL;
}
while ($line = fgets($stdin)) {
    $line=trim($line);
    list($row,$column)=explode(' ',$line);
    $row=trim($row);
    $colum=trim($column);
    if(!isset($rows_arr[$row]))
        $rows_arr[$row]=array();
    $rows_arr[$row][$column]=1;
    $columns_arr[$column]=0;
}
array_walk($rows_arr,'set_empty_vals',$columns_arr);

UPD:

PHP容易100万行:

$columns_arr = array();
$rows_arr = array();

function set_null_arr(&$value, $key, $columns_arr) {
    $value = array_merge($columns_arr, $value);
    ksort($value);
    foreach($value AS $val_name => $flag) {
        //echo $key.' '.$val_name.' '.$flag.PHP_EOL;
    }
    $value=NULL;
}

for ($i = 0; $i < 100000; $i++) {
    for ($j = 0; $j < 10; $j++) {
        $row='row_foo'.$i;
        $column='column_ipsum'.$j;
        if (!isset($rows_arr[$row]))
            $rows_arr[$row] = array();
        $rows_arr[$row][$column] = 1;
        $columns_arr[$column] = 0;
    }
}
array_walk($rows_arr, 'set_null_arr', $columns_arr);

echo memory_get_peak_usage();
对我来说是147Mb。

Last UPD - 这就是我看到内存使用率低(但速度很快)的原因:

//Approximate stdin buffer size, 1Mb should be good
define('MY_STDIN_READ_BUFF_LEN', 1048576);
//Approximate tmpfile buffer size, 1Mb should be good
define('MY_TMPFILE_READ_BUFF_LEN', 1048576);
//Custom stdin line delimiter(\r\n, \n, \r etc.)
define('MY_STDIN_LINE_DELIM', PHP_EOL);
//Custom stmfile line delimiter - chose smallset possible
define('MY_TMPFILE_LINE_DELIM', "\n");
//Custom stmfile line delimiter - chose smallset possible
define('MY_OUTPUT_LINE_DELIM', "\n");

function my_output_arr($field_name,$columns_data) {
    ksort($columns_data);
    foreach($columns_data AS $column_name => $column_flag) {
        echo $field_name.' '.$column_name.' '.$column_flag.MY_OUTPUT_LINE_DELIM;
    }
}

$tmpfile=tmpfile() OR die('Can\'t create/open temporary file!');
$buffer_len = 0;
$buffer='';
//I don't think there is a point to save columns array in file -
//it should be small enough to hold in memory.
$columns_array=array();

//Open stdin for reading
$stdin = fopen('php://stdin', 'r') OR die('Failed to open stdin!');

//Main stdin reading and tmp file writing loop
//Using fread + explode + big buffer showed great performance boost
//in comparison with fgets();
while ($read_buffer = fread($stdin, MY_STDIN_READ_BUFF_LEN)) {
    $lines_arr=explode(MY_STDIN_LINE_DELIM,$buffer.$read_buffer);
    $read_buffer='';
    $lines_arr_size=count($lines_arr)-1;
    $buffer=$lines_arr[$lines_arr_size];
    for($i=0;$i<$lines_arr_size;$i++) {
        $line=trim($lines_arr[$i]);
        //There must be a space in each line - we break in it
        if(!strpos($line,' '))
            continue;
        list($row,$column)=explode(' ',$line,2);
        $columns_array[$column]=0;
        //Save line in temporary file
        fwrite($tmpfile,$row.' '.$column.MY_TMPFILE_LINE_DELIM);
    }
}
fseek($tmpfile,0);

$cur_row=NULL;
$row_data=array();
while ($read_buffer = fread($tmpfile, MY_TMPFILE_READ_BUFF_LEN)) {
    $lines_arr=explode(MY_TMPFILE_LINE_DELIM,$buffer.$read_buffer);
    $read_buffer='';
    $lines_arr_size=count($lines_arr)-1;
    $buffer=$lines_arr[$lines_arr_size];
    for($i=0;$i<$lines_arr_size;$i++) {
        list($row,$column)=explode(' ',$lines_arr[$i],2);
        if($row!==$cur_row) {
            //Output array
            if($cur_row!==NULL)
                my_output_arr($cur_row,array_merge($columns_array,$row_data));
            $cur_row=$row;
            $row_data=array();
        }
        $row_data[$column]=1;
    }
}

if(count($row_data)&&$cur_row!==NULL) {
    my_output_arr($cur_row,array_merge($columns_array,$row_data));
}

答案 1 :(得分:1)

这是一个适用于您提供的测试数据的MySQL示例:

CREATE TABLE `url` (
  `url1` varchar(255) DEFAULT NULL,
  `url2` varchar(255) DEFAULT NULL,
  KEY `url1` (`url1`),
  KEY `url2` (`url2`)
);

INSERT INTO url (url1, url2) VALUES
('image_a', 'tag_lorem'),
('image_a', 'tag_ipsum'),
('image_a', 'tag_amit'),
('image_b', 'tag_sit'),
('image_b', 'tag_dolor'),
('image_b', 'tag_ipsum');

  SELECT url1, url2, assigned FROM (
  SELECT t1.url1, t1.url2, 1 AS assigned
    FROM url t1
   UNION
  SELECT t1.url1, t2.url2, 0 AS assigned
    FROM url t1
    JOIN url t2
      ON t1.url1 != t2.url1
    JOIN url t3
      ON t1.url1 != t3.url1
     AND t1.url2  = t3.url2
     AND t2.url2 != t3.url2 ) tmp
ORDER BY url1, url2;

结果:

+---------+-----------+----------+
| url1    | url2      | assigned |
+---------+-----------+----------+
| image_a | tag_amit  |        1 |
| image_a | tag_dolor |        0 |
| image_a | tag_ipsum |        1 |
| image_a | tag_lorem |        1 |
| image_a | tag_sit   |        0 |
| image_b | tag_amit  |        0 |
| image_b | tag_dolor |        1 |
| image_b | tag_ipsum |        1 |
| image_b | tag_lorem |        0 |
| image_b | tag_sit   |        1 |
+---------+-----------+----------+

这应该很简单,可以转换为SQLite,因此如果需要,您可以使用PHP将数据读入临时SQLite数据库,然后提取结果。

答案 2 :(得分:0)

将输入数据放入数组中,然后使用usort对它们进行排序,定义比较函数,按行值比较数组元素,如果行值相等,则比较列值。

答案 3 :(得分:0)

这是我当前的实现,我不喜欢它,但它现在可以完成这项工作。

#!/usr/bin/env php
<?php

define('CACHE_MATCH', 0);
define('CACHE_COLUMN', 1);

define('INPUT_ROW', 0);
define('INPUT_COLUMN', 1);
define('INPUT_COUNT', 2);

output_expanded_entries(
    cache_input(array(tmpfile(), tmpfile()), STDIN, fgets(STDIN))
);
echo memory_get_peak_usage();

function cache_input(Array $cache_files, $input_pointer, $input) {
    if(count($cache_files) != 2) {
        throw new Exception('$cache_files requires 2 file pointers');
    }

    if(feof($input_pointer) == FALSE) {
        cache_match($cache_files[CACHE_MATCH], trim($input));
        cache_column($cache_files[CACHE_COLUMN], process_line($input));

        cache_input(
            $cache_files,
            $input_pointer,
            fgets($input_pointer)
        );
    }

    return $cache_files;
}

function cache_column($cache_column, $input) {
    if(empty($input) === FALSE) {
        rewind($cache_column);

        $column = get_field($input, INPUT_COLUMN);

        if(column_cached_in_memory($column) === FALSE && column_cached_in_file($cache_column, fgets($cache_column), $column) === FALSE) {
            fputs($cache_column, $column . PHP_EOL);
        }
    }
}

function cache_match($cache_match, $input) {
    if(empty($input) === FALSE) {
        fputs($cache_match, $input . PHP_EOL);
    }
}

function column_cached_in_file($cache_column, $current, $column, $result = FALSE) {
    return $result === FALSE && feof($cache_column) === FALSE ?
        column_cached_in_file($cache_column, fgets($cache_column), $column, $column == $current)
        : $result;
}

function column_cached_in_memory($column) {
    static $local_cache = array(), $index = 0, $count = 500;

    $result = TRUE;

    if(in_array($column, $local_cache) === FALSE) {
        $result = FALSE;

        $local_cache[$index++ % $count] = $column;
    }

    return $result;
}

function output_expanded_entries(Array $cache_files) {
    array_map('rewind', $cache_files);

    for($current_row = NULL, $cache = array(); feof($cache_files[CACHE_MATCH]) === FALSE;) {
        $input = process_line(fgets($cache_files[CACHE_MATCH]));

        if(empty($input) === FALSE) {
            if($current_row !== get_field($input, INPUT_ROW)) {
                output_cache($current_row, $cache);

                $cache = read_columns($cache_files[CACHE_COLUMN]);
                $current_row = get_field($input, INPUT_ROW);
            }

            $cache = array_merge(
                $cache,
                array(get_field($input, INPUT_COLUMN) => get_field($input, INPUT_COUNT))
            );
        }
    }

    output_cache($current_row, $cache);
}

function output_cache($row, $column_count_list) {
    if(count($column_count_list) != 0) {
        printf(
            '%s %s %s%s',
            $row,
            key(array_slice($column_count_list, 0, 1)),
            current(array_slice($column_count_list, 0, 1)),
            PHP_EOL
        );

        output_cache($row, array_slice($column_count_list, 1));
    }
}

function get_field(Array $input, $field) {
    $result = NULL;

    if(in_array($field, array_keys($input))) {
        $result = $input[$field];
    } elseif($field == INPUT_COUNT) {
        $result = 1;
    }

    return $result;
}

function process_line($input) {
    $result = trim($input);

    return empty($result) === FALSE && strpos($result, ' ') !== FALSE ?
        explode(' ', $result)
        : NULL;
}

function push_column($input, Array $result) {
    return empty($input) === FALSE && is_array($input) ?
        array_merge(
            $result,
            array(get_field($input, INPUT_COLUMN))
        )
        : $result;
}

function read_columns($cache_columns) {
    rewind($cache_columns);

    $result = array();

    while(feof($cache_columns) === FALSE) {
        $column = trim(fgets($cache_columns));

        if(empty($column) === FALSE) {
            $result[$column] = 0;
        }
    }

    return $result;
}

编辑:昨天的版本被窃听:/