Найти один элемент в массиве без перебора массива

У меня есть массив с объектами, хранящимися в нем. Я хочу захватить объект в массиве с именем test-song-poll-02. Я знаю, что могу пройтись по нему и выполнить условное условие, чтобы проверить имя внутри каждого объекта, но мне было интересно, есть ли функция php массива/объекта, которая может вернуть объект, где имя = 'test-song-poll-03'

Array
(
    [0] => stdClass Object
        (
            [name] => test-song-poll-01
            [description] => test-song-poll-01
            [created_at] => 2014-05-02T23:07:59Z
            [count] => stdClass Object
                (
                    [approved] => 63787
                    [pending] => 341
                    [rejected] => 78962
                    [total] => 143090
                )

            [tpm] => 12
            [approved_tpm] => 3
            [pct] => 4
        )

    [1] => stdClass Object
        (
            [name] => test-song-poll-02
            [description] => test-song-poll-02
            [created_at] => 2014-05-02T23:17:20Z
            [count] => stdClass Object
                (
                    [approved] => 9587
                    [pending] => 0
                    [rejected] => 9780
                    [total] => 19367
                )

            [tpm] => 5
            [approved_tpm] => 3
            [pct] => 1
        )

    [2] => stdClass Object
        (
            [name] => test-song-poll-03
            [description] => test-song-poll-03
            [created_at] => 2014-05-02T23:19:06Z
            [count] => stdClass Object
                (
                    [approved] => 26442
                    [pending] => 0
                    [rejected] => 36242
                    [total] => 62684
                )

            [tpm] => 25
            [approved_tpm] => 9
            [pct] => 2
        )
)

ОБНОВИЛ мой код, чтобы показать, как я хочу передать переменную:

function get_results()
{
    $hashtag = "test-song-poll-03";
    $this->load->model('artist_model');
    $data['results'] = $this->artist_model->get_results();

    $myobject = array_filter($data['results']->streams, function($e, $hashtag) {
      return strcmp($e->name, $hashtag) == 0;
    });

    print_r($myobject);
}

person Brad    schedule 04.05.2014    source источник
comment
У вас есть контроль над тем, как строится массив?   -  person vascowhite    schedule 04.05.2014
comment
Нет, я извлекаю данные JSON из API.   -  person Brad    schedule 04.05.2014


Ответы (2)


Вы можете использовать array_filter.

$myobjects = array_filter($objects, function($e) use($hashtag) {
  return strcmp($e->name, "test-song-poll-03") == 0;
});

Из-за анонимной функции это будет работать только с PHP >= 5.3.

Вы можете использовать свою собственную функцию, если у вас более старая версия.

person Dávid Szabó    schedule 04.05.2014
comment
Можете ли вы сделать test-song-poll-03 переменной для передачи туда? Я попытался передать его в качестве параметра в функцию, в которую вы передаете $e, но это дало мне ошибку: Предупреждение: отсутствует аргумент 2 для Home::{closure}() в и Примечание: Неопределенная переменная: хэштег. Переменная, которую я пытаюсь передать это $хэштег. Я обновил код, чтобы показать вам, как я использую ваш код. - person Brad; 04.05.2014
comment
Я понятия не имею об этом, но вы можете попробовать это с помощью 'global $hashtag;' может быть. Собираюсь проверить. - person Dávid Szabó; 04.05.2014
comment
Я отредактировал свой ответ, вы можете использовать «use ($ hashtag)» для передачи переменных в закрытие. - person Dávid Szabó; 04.05.2014
comment
Этот код немного вводит в заблуждение, он возвращает массив, а не один объект. Этот массив содержит все(!) объекты с соответствующим именем, но таких значений также может быть ноль или несколько. Помимо этого, он не позволяет избежать циклического обхода массива, что может потребовать значительных вычислительных ресурсов. Если это важно, вам нужен массив, который сопоставляет искомое имя с соответствующим объектом (или объектами?), подобно индексу базы данных. - person Ulrich Eckhardt; 05.05.2014
comment
Он извлекает данные из API, не может изменить массив, так что по-другому нельзя. - person Dávid Szabó; 05.05.2014
comment
Это будет перебирать весь массив, даже если он найдет совпадение в первой итерации? С циклом foreach вы можете выйти из игры, как только найдете совпадение. Я не уверен, что это ваш лучший вариант. - person vascowhite; 05.05.2014
comment
Вы настоящий vascowhite, ОП не спрашивал ЛУЧШИЙ вариант, он просил функцию в PHP. Если вы перечитаете его вопрос, вы увидите. - person Dávid Szabó; 06.05.2014

Тот факт, что вы получаете данные из API в формате json, не означает, что вы должны хранить их в заданном формате. Есть несколько стратегий, которые будут более эффективными, чем ваш принятый ответ.

Проблема с текущим выбранным ответом заключается в том, что он будет перебирать полный набор данных каждый раз, когда вы его используете, даже если совпадение найдено на первой итерации. Я не знаю, насколько велик ваш набор данных, но я предполагаю, что он значителен, иначе вы бы не задавали этот вопрос.

Я также не знаю, сколько раз вы хотите получить доступ к набору данных, вы, вероятно, тоже не знаете, но я предполагаю, что этого количества достаточно, чтобы заставить вас задуматься, иначе, опять же, вы бы не задавали этот вопрос.

Предположим, у вас есть набор данных, состоящий из 1000 ваших stdClass объектов, и вы запрашиваете каждый из этих объектов один раз, поэтому вы обращаетесь к нему 1000 раз.

Теперь предложенный вам метод 'array_filter()' должен обращаться ко всем 1000 элементам (это O(n)) каждый раз, то есть всего 1 000 000 итераций.

//Access every element once using array_filter()
$objectArray = [];
$objectNames = [];
for($i = 0; $i < 1000; $i ++){
    $objName = 'object_name_' . ($i + 1);
    $objectNames[] = $objName;
    $obj = new stdClass();
    $obj->name = $objName;
    $obj->description = 'test description';
    $obj->accessed = 0;
    $objectArray[] = $obj;
}
$start = microtime(true);
foreach($objectNames as $name){
    $iterations = getObjectWithArray_Filter($name, $objectArray);
}
$end = microtime(true);
$taken = $end - $start;
echo $iterations . " iterations using array_filter() in $taken seconds<br/>\n";

посмотрите, как это работает.

Первая альтернатива, которая приходит на ум, — это старый добрый foreach()loop, это тоже O(n), но цикл можно записать в залог, как только он найдет совпадение. Таким образом, если мы получим доступ к каждому элементу массива, когда у нас будет 500 500 итераций, экономия составит около 50%. Это может или не может применяться в реальном мире, вы будете лучшим судьей об этом.

//Access every element once using foreach(){}
$objectArray = [];
$objectNames = [];
for($i = 0; $i < 1000; $i ++){
    $objName = 'object_name_' . ($i + 1);
    $objectNames[] = $objName;
    $obj = new stdClass();
    $obj->name = $objName;
    $obj->description = 'test description';
    $obj->accessed = 0;
    $objectArray[] = $obj;
}

$start = microtime(true);
foreach($objectNames as $name){
    $iterations = getObjectWithForeach($name, $objectArray);
}
$end = microtime(true);
$taken = $end - $start;
echo $iterations . " iterations using foreach(){} in $taken seconds<br/>\n";

посмотрите, как это работает.

Второй вариант, который приходит мне в голову, — это один раз пройтись по массиву и записать его в ассоциативный массив. Первый проход будет O(n), 1000 итераций, и после этого мы сможем получить доступ к нужному элементу напрямую, вообще не проходя через массив, что равно O(1). Давая нам 2000 итераций для доступа к каждому элементу массива один раз.

//Access every element once using Associative array
$objectArray = [];
$objectNames = [];
for($i = 0; $i < 1000; $i ++){
    $objName = 'object_name_' . ($i + 1);
    $objectNames[] = $objName;
    $obj = new stdClass();
    $obj->name = $objName;
    $obj->description = 'test description';
    $obj->accessed = 0;
    $objectArray[] = $obj;
}

$associativeArray = [];
$start = microtime(true);
foreach($objectArray as $object){
    $associativeArray[$object->name] = $object;
    $object->accessed ++;
}

foreach($objectNames as $name){
    $iterations = getObjectFromAssociativeArray($objName, $associativeArray);
}
$end = microtime(true);
$taken = $end - $start;
echo $iterations . " iterations using associative array{} in $taken seconds<br/>\n";

посмотрите, как это работает.

Вот остальная часть моего тестового кода: -

//=================================================================
function getObjectWithArray_Filter($objectName, array $objectArray){
    $myobjects = array_filter($objectArray, function($e) use($objectName) {
            $e->accessed ++;
            return strcmp($e->name, $objectName) == 0;
        });
    $iterations = 0;
    foreach($objectArray as $object){
        $iterations += $object->accessed;
    }
    return $iterations;
}

function getObjectWithForeach($objectName, array $objectArray){
    $iterations = 0;
    $found = false;
    $count = 0;
    while(!$found){
        $objectArray[$count]->accessed ++;
        if($objectArray[$count]->name === $objectName){
            $found = true;
        }
        $count ++;
    }
    foreach($objectArray as $object){
        $iterations += $object->accessed;
    }
    return $iterations;
}

function getObjectFromAssociativeArray($objectName, array $objectArray){
    $iterations = 0;
    if($objectName === $objectArray[$objectName]->name){
        $objectArray[$objectName]->accessed ++;
    }
    foreach($objectArray as $object){
        $iterations += $object->accessed;
    }
    return $iterations;
}

tl;dr
Вывод на 3v4l.org: –

Accessing 1000 elements once took 1000000 iterations using array_filter() in 0.5374710559845 seconds
Accessing 1000 elements once took 500500 iterations using foreach(){} in 0.2077169418335 seconds
Accessing 1000 elements once took 2000 iterations using associative array{} in 0.1438410282135 seconds

посмотрите, как это работает.

Разница во времени тоже интересна. Вам может понадобиться или не потребоваться оптимизация для такой скорости, но я бы предположил, что это полезное изменение для вашего кода. В любом случае я думаю, что отсутствие итераций после первой намного лучше, чем 1000 или в среднем 500,5 каждый раз.

Я надеюсь, что вы сочтете это полезным упражнением, ваш вопрос пробудил во мне интерес, и я был убежден, что принятый вами ответ был не лучшим решением для вас. Вы можете все еще думать, что это так, но я предлагаю это как альтернативу.

Самый простой способ реализовать это - использовать какое-то хранилище/фабрику объектов: -

class ObjectStore
{
    private $decoded;
    private $asssocArray;

    public function __construct($jsonEncodedObjects)
    {
        $this->decoded = json_decode($jsonEncodedObjects);
    }

    public function getObject($objectName)
    {
        if(!$this->asssocArray){
            foreach($this->decoded as $object){
                $this->asssocArray[$object->name] = $object;
            }
        }
        return $this->asssocArray[$objectName];
    }
}

Таким образом, ваш первый запрос на объект будет O(n), а последующие запросы будут O(1).

Чтобы использовать это с вашим кодом: -

$objectStore = new ObjectStore(getJsonEncodedData());

$hashtag = "test-song-poll-03";
$myObject = $objectStore->getObject($hashtag);
person vascowhite    schedule 05.05.2014
comment
Вы правы, ОП не спрашивал ЛУЧШИЙ вариант, он просил функцию в PHP. Если вы перечитаете его вопрос, вы увидите. - person Dávid Szabó; 06.05.2014
comment
@newboyhun То, что люди просят, не всегда то, что им нужно, ваше решение неплохое, просто не подходит для этого случая. Это вызывает у OP дальнейшие проблемы. - person vascowhite; 06.05.2014