10

This may be some sort of weird longer shortcut, and please correct me if I'm mistaken in this train of thought...

I have a matrix of data that looks like:

unique_id | url | other random data...
unique_id | url | other random data...
unique_id | url | other random data...

I want to be able to reference an item by either it's url, or it's unique_id - is there a fancy way to do this?

I suppose the cheating solution would be to just make two arrays, but I was wondering if there is a better way.

Jane Panda
  • 1,591
  • 4
  • 23
  • 51
  • 2
    Not really, no. Just make two arrays. The overhead is minimal (well, depending on how big your arrays are). – El Yobo Dec 10 '10 at 03:12
  • Do you need to modify the "random data" once it's in the array? And it it possible for a unique_id and a URL to collide? – Matthew Flaschen Dec 10 '10 at 03:12
  • Don't *duplicate* "other random data" ("DRY" concept). The cleanest solution is to have one array that maps `unique_id` to `other random data`, then a second array that maps `url` to `unique_id`. That is the purpose of `unique_id`s: to provide a concise and efficient "handle" that represents a given record (row) of data. – ToolmakerSteve Dec 23 '20 at 20:24

4 Answers4

14

Only way I can think of that doesn't involve iterating the array for each search (see Jacob's answer) is to store references to each item in two arrays.

Edit: As the URLs and IDs cannot collide, they may be stored in the same reference array (thanks Matthew)

$items; // array of item objects
        // Use objects so they're implicitly passed by ref

$itemRef = array();

foreach ($items as $item) {
    $itemRef[$item->unique_id] = $item;
    $itemRef[$item->url] = $item;
}

// find by id
$byId = $itemRef[$id];

// find by url
$byUrl = $itemRef[$url];

You could probably encapsulate this nicely using a collection class that implements getById() and getByUrl(). Internally, it could store the references in as many arrays as is necessary.

Of course, what you're essentially doing here is creating indexed result sets, something best left to database management systems.

Phil
  • 157,677
  • 23
  • 242
  • 245
  • 1
    Also, you can put them in the same array (e.g. `$items`) if you choose. It might be slightly confusing, but Bob said the keys can't collide. – Matthew Flaschen Dec 10 '10 at 03:20
  • Oh that is interesting. They don't collide, but it seems a little scary from a vulnerability stand point (for example a lookup that was forced using a url instead of a unique_id). Nice technique though – Jane Panda Dec 10 '10 at 03:24
  • @Bob It's similar to when you retrieve database records using a `fetch_both` method where the result contains both numeric and string indexes. Speaking of indexes, if this data does come from a DB, you're probably better off applying indexes to the ID and url fields and doing single fetches to retrieve nominated records. – Phil Dec 10 '10 at 03:36
  • @MatthewFlaschen - you *could* put them in the same array, but its cleaner to have a second array that maps `url` to `unique_id`. Keep separate concepts separate. – ToolmakerSteve Dec 23 '20 at 20:18
3

Try something like this:

function selectByIdOrURL($array, $data) {
    foreach($array as $row) {
       if($row['unique_id'] == $data || $row['url'] == $data) return $row;
    }
    return NULL;
}

$array = array(
           array('unique_id' => 5, 'url' => 'http://blah.com'),
           array('unique_id' => 3, 'url' => 'http://somewhere_else.com')
         );
$found = selectByIdOrURL($array, 5); //array('unique_id' => 5, 'url' => 'http://blah.com')
$nfound = selectByIdOrURL($array, 10); //NULL
Jacob Relkin
  • 161,348
  • 33
  • 346
  • 320
  • 1
    Is the only way to iterate the data? I suppose it's not a huge deal, but I'm really curious if you can have multiple indexes for arrays (generally speaking) – Jane Panda Dec 10 '10 at 03:10
  • 2
    @Bob, no, there is no way to have multiple indexes for arrays. – Jacob Relkin Dec 10 '10 at 03:12
  • 3
    Generally speaking, no. The fastest approach (for access times) is to have two arrays, one with the unique_id as the key, the other with the URL, and both with all the data as the value. Jacob's answer will use slightly less memory, but will need to iterate the whole array each time you reference something; my approach will use slightly more memory, but will retrieve the results faster. – El Yobo Dec 10 '10 at 03:14
  • 1
    That's a shame... I guess just iterating it isn't so bad – Jane Panda Dec 10 '10 at 03:15
  • Any fancy solution would involve iteration behind the scenes anyway. – Isaac Lubow Oct 15 '20 at 22:05
1

It appears your fancy solution was only available as of PHP 5.5. You can combine the use of array_search and array_column to fetch your entry in a single line of code:

$items = [
    [
     'unique_id' => 42,
     'url' => 'http://foo.com'
    ],
    [
     'unique_id' => 57,
     'url' => 'http://bar.com'
    ],
    [
     'unique_id' => 36,
     'url' => 'http://example.com'
    ],

];

$bar = $entries[array_search(57, array_column($items, 'unique_id'))];

var_dump($bar);

//outputs
array (size=2)
    'unique_id' => int 57
    'url' => string 'http://bar.com' (length=14)
Pierre Roudaut
  • 1,013
  • 1
  • 18
  • 32
0

Surely an object would be the easy way?

class Item {
    public $unique_url;
    public $url;
    public $other_data;

    public function __construct($unique_url, $url, $other_data)
    {
        $this->unique_url = $unique_url;
        $this->url = $url;
        $this->other_data = $other_data;
    }
}



class ItemArray {
    private $items = array();

    public function __construct()
    {
    }

    public function push(Item $item)
    {
        array_push($items, $item); //These may need to be reversed
    }

    public function getByURL($url)
    {
        foreach($items as $item)
        {
            if($item->url = $url)
            {
                return $item;
            }
        }
    }

    public function getByUniqueURL($url)
    {
        foreach($items as $item)
        {
            if($item->unique_url = $unique_url)
            {
                return $item;
            }
        }
    }

}

Then use it with

$itemArray = new ItemArray();
$item = new Item("someURL", "someUniqueURL","some other crap");
$itemArray->push($item);

$retrievedItem = $itemArray->getItemByURL("someURL");

This technique has a little extra overhead due to object creation, but unless you're doing insane numbers of rows it would be fine.

Jon Story
  • 2,881
  • 2
  • 25
  • 41