0

I have the following code :

$json = json_decode(URL, true);
foreach($json as $var)
{
    if($var[id] == $valdefined)
    {
        $number = $var[count];
    }
}

With json it looks like this :

 [{"id":"1","count":"77937"},
 {"id":"2","count":"20"},
 {"id":"4","count":"25"},
 {"id":"5","count":"11365"}]

This is what the array ($json) looks like after jsondecode

 Array ( [0] => Array ( [id] => 1 [count] => 77937 ) [1] => Array ( [id] => 2 [count] => 20 ) [2] => Array ( [id] => 4 [count] => 25 ) [3] => Array ( [id] => 5 [count] => 11365) )

is there a way to say what is $json[count] where $json[id] = 3 for example

David
  • 3,927
  • 6
  • 30
  • 48

3 Answers3

0

I'm not sure about a better way, but this is also fine, provided the JSON object is not huge. php is pretty fast when looping through JSON. If the object is huge, then you may want to split it. What I personally do is make my JSON into an array of normal objects, sort them, and then searching is faster on sorted items.

EDIT

Do json_decode($your_thing, true); set it true to make it an associative array, and then the id would be key and and the count would be value. After you do this, getting the value with the ID should really be easy and far more efficient.

J D
  • 1,768
  • 2
  • 18
  • 20
  • as i have json decoded I think it is now already a std array, how would I access count in the array for id 3 – David Aug 12 '13 at 14:05
  • If it's an array, then you can do something like `array_multisort` or `sort` and then access using $array[3]. Sorting is really fast, you don't have to worry about that I believe. – J D Aug 12 '13 at 14:08
  • 1
    @JohnSmith: Sorting isn't slow, but I do think that a `foreach` loop is faster still. You needn't sort the entire array, if all you're after is a single id. Just loop through the array, until the id is the expected value, and break, whereas sorting is going to traverse the entire array. Always. – Elias Van Ootegem Aug 12 '13 at 14:10
  • I need something like: $json['count'] where $json['id'] = 3, can you please update you answer if you know how i can do that – David Aug 12 '13 at 14:10
  • @Elias Van Ootegem: This script is pretty intense, I will prob have like 160 loops then when it would be much better is i could just say what is $json[count] where $json[id] = 2 – David Aug 12 '13 at 14:13
  • @David: _You can't always get what you want_. Sometimes, in fact: most of the times, an out-of-the-box sollution just isn't going to cut it. there's nothing wrong with few loops. 160 does sound like an awful lot. Perhaps even more than you need. Anyway, even if there were an out of the box answer to your question, that function would translate to a loop internally anyway. Just keep your loop. It's fine. When you reach `$var['id'] === 3` in your sample code, you can just access `$var['count']`, that's it. Don't forget the quotes, btw – Elias Van Ootegem Aug 12 '13 at 14:17
  • @EliasVanOotegem Yes, but if it is unsorted, then foreach loop can be very expensive. It can take as much as O(n) time, while the php sorting is O(nlogn) and then getting one single item is O(1), with O(nlogn) of total, and you may know already, O(nlogn) is much much better than O(n). @ David Okay. – J D Aug 12 '13 at 14:19
  • its not so much the the fact that looping or not might affect speed as the complexity of the code is going to be greatly reduced if I can access the count based off the id in an associative array – David Aug 12 '13 at 14:20
  • @David, you're decoding a URL constant? is that a self-defined constant? and does it really contain urls? there is such a thing as `parse_url` and _for God Sake_, if you control the data _Sort it to begin with_, don't try to sort it afterwards. – Elias Van Ootegem Aug 12 '13 at 14:21
  • @David: What makes you say that `foreach`-ing is _O(n)_, and getting a single value _from an instance of `Stdclass`, or from the result array_, will be _O(1)_. That's just not true. Getting the `id` and `count` properties will, in theory be _O(n)_. Please mention your source – Elias Van Ootegem Aug 12 '13 at 14:24
  • @John Smith: With regards to your answer, how would i reference the count for id 3 assuming $json has now become a std array: $json['count'] where $json['id'] = 3 How would I code that – David Aug 12 '13 at 14:29
  • @EliasVanOotegem Lol. Foreach is always O(n) haha. "You are looping through every element". And getting a single item is always one task, O(1). You know how bad is O(n) for any program, while O(nlogn) is most optimal. – J D Aug 12 '13 at 14:29
  • @David `echo $your_var[id]`. Please see http://php.net/manual/en/language.types.array.php This always give the value from the key. This is how associative arrays work. – J D Aug 12 '13 at 14:30
  • Please see the edit to the question above, furthermore echo $json[id] is just going to give me 1,2,3,5 – David Aug 12 '13 at 14:37
  • @David Ohh man, why do you have arrays inside arrays? Just have one array. The encoded JSON received by your is not correct or something. – J D Aug 12 '13 at 14:38
  • @john: can you tell me, is there a way in a multidimensional associative array like above, to get the count where id = 2 without having to loop through the array – David Aug 12 '13 at 14:40
  • @JohnSmith: Saying that getting a single item [is always _O(1)_ is simply not true](http://stackoverflow.com/questions/17696289/pre-declare-all-private-local-variables/17696356#17696356). Foreach loops over every element in the array _in the worst case_, if the first element of the array is the element you want, you `break;`. Sorting an array applies a callback _function_ to each element of the array, then you get a single item from _the resulting array_. That's iterating an entire array + function call each time, compared to >= 100% iteration of array in a language construct. – Elias Van Ootegem Aug 12 '13 at 15:24
  • @JohnSmith: I've done a bit of googling on linear loops being _O(n)_. As it turns out, that's pure BS: a linear search (loop) is `((n+2)(n-1))/2n`, and its time-complexity therefore varies from _O(1)_ to just under _O(n)_ (asymptotically). also check [the non-uniform probablilities](http://en.wikipedia.org/wiki/Linear_search). linear searches can, in theory, be faster than an _O(log n)_ binary search. – Elias Van Ootegem Aug 13 '13 at 12:46
0

If you change the way you build your json object to look like this :-

{"1":77937,"2":20,"4":25,"5":11365}

And then use the json_decode() parameter 2 set to TRUE i.e. turn the json into an array.

Then you have a usable assoc array with the ID as the key like so:

<?php
    $json = '{"1":77937,"2":20,"4":25,"5":11365}';
    $json_array = json_decode($json, TRUE);
    print_r( $json_array);
?>

Resulting in this array

Array
(
    [1] => 77937
    [2] => 20
    [4] => 25
    [5] => 11365
)

Which you can do a simple

 $number = json_array( $valdefined );

Or better still

if ( array_key_exists( $valdefined, $json_array ) ) {
   $number = json_array( $valdefined );
} else {
   $number = NULL;   // or whatever value indicates its NON-EXISTANCE
}
RiggsFolly
  • 93,638
  • 21
  • 103
  • 149
0

Short answer to your initial question: why can't you write $json['count'] where $json['id'] = 3? Simply because PHP isn't a query language. The way you formulated the question reads like a simple SQL select query. SQL will traverse its indexes, and (if needs must) will perform a full table scan, too, its Structured Query Language merely enables you not to bother writing out the loops the DB will perform.
It's not that, because you don't write a loop, there is no loop (the absence of evidence is not the evidence of absence). I'm not going to go all Turing on you, but there's only so many things we can do on a machine level. On the lower levels, you just have to take it one step at a time. Often, this means incrementing, checking and incrementing again... AKA recursing and traversing.
PHP will think it understands what you mean by $json['id'], and it'll think you mean for it to return the value that is referenced by id, in the array $json, whereas you actually want $json[n]['id'] to be fetched. To determine n, you'll have to write a loop of sorts. Some have suggested sorting the array. That, too, like any other array_* function that maps/filters/merges means looping over the entire array. There is just no way around that. Since there is no out-of-the-box core function that does exactly what you need to do, you're going to have to write the loop yourself.
If performance is important to you, you can write a more efficient loop. Below, you can find a slightly less brute loop, a semi Interpolation search. You could use ternary search here, too, implementing that is something you can work on.

for ($i = 1, $j = count($bar), $h = round($j/2);$i<$j;$i+= $h)
{
    if ($bar[++$i]->id === $search || $bar[--$i]->id === $search || $bar[--$i]->id === $search)
    {//thans to short-circuit evaluation, we can check 3 offsets in one go
        $found = $bar[$i];
        break;
    }//++$i, --$i, --$i ==> $i === $i -1, increment again:
    if ($bar[++$i]->id > $search)
    {// too far
        $i -= $h;//return to previous offset, step will be halved
    }
    else
    {//not far enough
        $h = $j - $i;//set step the remaining length, will be halved
    }
    $h = round($h/2);//halve step, and round, in case $h%2 === 1
    //optional:
    if(($i + $h + 1) === $j)
    {//avoid overflow
       $h -= 1;
    }
}

Where $bar is your json-decoded array.
How this works exactly is explained below, as are the downsides of this approach, but for now, more relevant to your question: how to implement:

function lookup(array $arr, $p, $val)
{
    $j = count($arr);
    if ($arr[$j-1]->{$p} < $val)
    {//highest id is still less value is still less than $val:
        return (object) array($p => $val, 'count' => 0, 'error' => 'out of bounds');
    }
    if ($arr[$j-1]->{$p} === $val)
    {//the last element is the one we're looking for?
        return $end;
    }
    if ($arr[0]->{$p} > $val)
    {//the lowest value is still higher than the requested value?
        return (object) array($p => $val, 'count' => 0, 'error' => 'underflow');
    }
    for ($i = 1, $h = round($j/2);$i<$j;$i+= $h)
    {
        if ($arr[++$i]->{$p} === $val || $arr[--$i]->{$p} === $val || $arr[--$i]->{$p} === $val)
        {//checks offsets 2, 1, 0 respectively on first iteration
            return $arr[$i];
        }
        if ($arr[$i++]->{$p} < $val && $arr[$i]->{$p} > $val)
        {//requested value is in between? don't bother, it won't exist, then
            return (object)array($p => $val, 'count' => 0, 'error' => 'does not exist');
        }
        if ($arr[++$i]->{$p} > $val)
        {
            $i -= $h;
        }
        else
        {
            $h = ($j - $i);
        }
        $h = round($h/2);
    }
}
$count = lookup($json, 'id', 3);
echo $count['count'];
//or if you have the latest version of php
$count = (lookup($json, 'id', 3))['count'];//you'll have to return default value for this one

Personally, I wouldn't return a default-object if the property-value pair wasn't found, I'd either return null or throw a RuntimeException, but that's for you to decide.


The loop basically works like this:

  1. On each iteration, the objects at offset $i, $i+1 and $i-1 are checked.
    If the object is found, a reference to it is assigned to $found and the loop ends
  2. The object isn't found. Do either one of these two steps:
    • ID at offset is greater than the one we're looking for, subtract step ($h) from offset $i, and halve the step. Loop again
    • ID is smaller than search (we're not there yet): change step to half of the remaining length of the array

A diagram will show why this is a more "clever" way of looping:

|==========x=============================|//suppose x is what we need, offset 11 of a total length 40:

//iteration 1:
 012 //checked offsets, not found
|==========x=============================|
//offset + 40/2 == 21
//iteration 2:
                     012//offsets 20, 21 and 22, not found, too far
|==========x=============================|
//offset - 21 + round(21/2)~>11 === 12
//iteration 3:
           123 //checks offsets 11, 12, 13) ==> FOUND
|==========x=============================|
assign offset-1
break;

Instead of 11 iterations, we've managed to find the object we needed after a mere 3 iterations! Though this loop is somewhat more expensive (there's more computation involved), the downsides rarely outweigh the benefits.
This loop, as it stands, though, has a few blind-spots, so in rare cases it will be slower, but on average it performs pretty well. I've tested this loop a couple of times, with an array containing 100,000 objects, looking for id random(1,99999) and I haven't seen it take more time than .08ms, on average, it manages .0018ms, which is not bad at all.
Of course, you can improve on the loop by using the difference between the id at the offset, and the searched id, or break if id at offset $i is greater than the search value and the id at offset $i-1 is less than the search-value to avoid infinite loops. On the whole, though, this is the most scalable and performant loopup algorithm provided here so far.

Check the basic codepad in action here

Codepad with loop wrapped in a function

Community
  • 1
  • 1
Elias Van Ootegem
  • 74,482
  • 9
  • 111
  • 149