This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Are PHP Associative Arrays ordered?
If I add items to associative array with different keys, does order of addition conserved? How can I access "previous" and "next" elements of given element?
Yes, php arrays have an implicit order. Use reset, next, prev and current - or just a foreach loop - to inspect it.
Yes, it does preserve the order. you can think of php arrays as ordered hash maps.
You can think of the elements as being ordered by "index creation time". For example
$a = array();
$a['x'] = 1;
$a['y'] = 1;
var_dump($a); // x, y
$a = array();
$a['x'] = 1;
$a['y'] = 1;
$a['x'] = 2;
var_dump($a); // still x, y even though we changed the value associated with the x index.
$a = array();
$a['x'] = 1;
$a['y'] = 1;
unset($a['x']);
$a['x'] = 1;
var_dump($a); // y, x now! we deleted the 'x' index, so its position was discarded, and then recreated
To summarize, if you're adding an entry where a key doesnt currently exist in the array, the position of the entry will be the end of the list. If you're updating an entry for an existing key, the position is unchanged.
foreach loops over arrays using the natural order demonstrated above. You can also use next() current() prev() reset() and friends, if you like, although they're rarely used since foreach was introduced into the language.
also, print_r() and var_dump() output their results using the natural array order too.
If you're familiar with java, LinkedHashMap is the most similar data structure.
Related
Hey everyone I have a question about arrays and loops in PHP.
For a game I'm making, I need to write a function that generates a stack of crystal_id's based on a given size and ratio.
The ratio is the ratio between black crystals and different colored crystal (so a ratio of 0,25 (1:4) and a stack of 50 would yield a stack with 40 black crystals and 10 colored crystals).
I have all the math to calculate the amount of crystals per color and stuff figured out, but I can't figure out how to create an array with the right amount of colored crystals where each color is represented equally.
For reference, the array the code gets to choose from is a variable called $crystals_array, which is an array filled with integers where each integer represents a different colored crystal (e.g. [2,3,4,5,6]).
In the above case we have 5 different colored crystals and we needed a total of 10 colored crystals where each crystal is represented equally. So I need to create an array that looks a little something like this:
[2,2,3,3,4,4,5,5,6,6].
The code I have so far is this:
for($i = 0; $i <= count($amount_crystals_color) - 1; $i++)
{
$array = array_fill(0, $amount_crystals_per_color_stack, $crystals_array[$i]);
$i++;
}
Using the above example $amount_crystals_per_color_stack is equal to 2 and amount_crystals_color is equal to 5.
When executing this code it outputs an array: [2,2] which is how many 2's we need, but I can't figure out how to add the remaining crystals to this array.
Can anyone help me out?
Your code has several problems and i will address each of them individually.
Using the for loop to iterate over an array
You are using a for loop in your code, that has the following loop head:
for($i = 0; $i <= count($crystals_array) - 1; $i++)
The loop had consists of three parts, each separated by a semicolon (;).
The first part $i = 0 is the initialization part. Here you initialize a variable, that is later used as an iterator, which shall change in each loop iteration. You could name this the start point as well.
The second part $i <= count($crystals_array) - 1 is the condition part. A condition is formed, that shall express for how long the loop shall iterate. As long as the expression evaluates as true, the loop will run again. Therefore this condition is evaluated at the start of each iteration. As soon as the condition evaluates as false the loop will end. Therefore this part can be named endpoint as well.
The third part $i++ is the step size. This is executed at the end of each iteration and determines how the iterator (the variable you defined in the first part) shall change. $i++ is in this context equal to $i = $i + 1 and represents a step size of 1. Therefore the variable $i gets increased by 1 for each run of the loop.
This said, you can improve and fix your code regarding the for loop with two changes:
Save functions, that are executed in your condition part into an variable, if they return a constant result for each iteration. You use the count() function, which will then count your array again for each iteration of the for loop. By saving it in a variable $count = count($crystals_array); before the for loop and changing the condition to $i < $count, the function is only called once and your code gets faster.
Do not change the iterator variable $i outside of your loop header. This is really bad code style. You added the line $i++; to the end of your loop, but that is already done in the step size part of the for header. Because that is executed at the and of each iteration as well you increased the step size to two, meaning that you only run the for loop with $i = 0, $i = 2 and $i = 4 instead of for each element.
For your code the usage of the $i iterator is only to address the elements of the initial array. Even though you should understand the for loop for the future, you should use a foreach loop for this case instead. The following code would be equivalent to your for loop.
//This code still contains another major bug and is jsut a partial improvvement
foreach($crystals_array as $crystal) {
$array = array_fill(0, $amount_crystals_per_color_stack, $crystal);
}
As you can see, you neither need to worry about counting the initial array, nor in which index the current value is. Instead the variable $crystal will automatically contain the next element for each iteration.
Appending elements to an array
You used the following line to save the newly generated elements in your array:
$array = array_fill(0, $amount_crystals_per_color_stack, $crystal);
If you look closely, you use a standard assignment with $array = at the beginning of your line. This means, that (like with each variable assignment) the previous value of the variable gets overwritten by the new value provided from the right side of the assignment. What you do not want yet is to overwrite the array, but to append something to it.
This can be done by adding two squared brackets to the end of the variable name: $array[] = .... Now, if the variable $array is really an array, what ever value is on the right side of the assignment will be appended to the array, instead of overwriting it.
Managing result types the right way
The following line still contains a major problem:
$array[] = array_fill(0, $amount_crystals_per_color_stack, $crystal);
The result type of array_fill() is an array itself. By appending it to the previous array, you would get the following structure:
$array = [
[2, 2],
[3, 3],
[4, 4],
[5, 5],
[6, 6],
];
As you can see, the code did exactly what it should but not what you wanted. Each result (array) was appended to the array. The result is therefore an array or arrays (or a multidimensional array). What you want instead, is that the values of the result are appended to the existing array.
PHP offers a function for that, named array_merge(). This function takes all elements for one (or more) array(s) and appends them to the end of the first array, that was given to the function. You can use it as followed:
$newCrystals = array_fill(0, $amount_crystals_per_color_stack, $crystal);
$array = array_merge($array, $newCrystals);
As you can see the latter line contains a normal assignment again. ($array =) This is because array_merge() does not modify the first array given to it, but creates a new array with the merged fields. Therefore the new array contains all values from the old one and it is safe to overwrite the old one with it.
The complete code would therefore be:
$array = [];
foreach($crystals_array as $crystal) {
$newCrystals = array_fill(0, $amount_crystals_per_color_stack, $crystal);
$array = array_merge($array, $newCrystals);
}
As I understood the problem
$crystals_array = [2,3,4,5,6];
$amount_crystals_per_color_stack = 2;
$res = [];
foreach($crystals_array as $v) {
// repeat each item from array $amount_crystals_per_color_stack times
$res = array_merge($res, array_fill(0, $amount_crystals_per_color_stack, $v));
}
print_r($res); // [2,2,3,3,4,4,5,5,6,6]
You need to merge your array at each iteration of the loop (repl online) or you lose the result each time.
Like:
$array = array();
for($i = 0; $i < count($amount_crystals_color); $i++)
{
$array = array_merge($array, array_fill(0, $amount_crystals_per_color_stack, $crystals_array[$i]);
}
Also you don't need the $i++ in the loop, because it iterate twice otherwise, and you don't need count(..)-1 if the condition is < instead of <=.
You could use simple foreach() to achieve this-
$amount_crystals_per_color_stack = 2;
$array = [2,3,4,5,6];
$result = array();
foreach($array as $a){
for($i=1;$i<=$amount_crystals_per_color_stack;$i++){
array_push($result, $a);
}
}
print_r($result);
Edit1: The problem: I want to convert in php a associative array to a indexed one. So I can return it via json_encode as an array and not as an object. For this I try to fill the missing keys. Here the description:
Got a small problem, I need to transfer a json_encoded array as an array to js. At the moment it returns an Object. I´m working with Angular so I really need an Array. I try to explain it as much as possible.
$arrNew[0][5][0][0][1]["id"] = 1;
//$arrNew[0][0][0][0][1] = "";
//$arrNew[0][1][0][0][1] = "";
//$arrNew[0][2][0][0][1] = "";
//$arrNew[0][3][0][0][1] = "";
//$arrNew[0][4][0][0][1] = "";
$arrNew[0][5][0][0][1]["name"] = 'Test';
var_dump($arrNew);
So if I return it now It returns the second element as object cause of the missing index 0-4 and the 4th element cause of the missing index 0 (associative array -> object)
So if I uncomment the block it works like a charm. Now I have the problem its not every time the element 5 sometime 3, 4 or something else so I build a function which adds them automaticly:
$objSorted = cleanArray($arrNew);
function cleanArray($array){
end($array);
$max = key($array) + 1; //Get the final key as max!
for($i = 0; $i < $max; $i++) {
if(!isset($array[$i])) {
$array[$i] = '';
} else {
end($array[$i]);
$max2 = key($array[$i]) + 1;
for($i2 = 0; $i2 < $max2; $i2++) {
.... same code repeats here for every index
So if I vardump it it returns:
The problem:
On js side its still an object, what I also see is that the elements are not sorted. So I think somehow PHP sees it still as an associative array. Any clue why this happens ? The key is set with the index of the loop and has to be a integer value.
PS: I know reworking it in JS is possible but would have be done nearly on every request with a huge load of loops
If I understand your problem, you create a sparse multidimensional array of objects. Because the arrays have gaps in the keys, json_encode() produces objects on some levels but you need it to produce arrays for all but the most inner level.
The following function fills the missing keys (starting from 0 until the maximum value used as numeric key in an array) on all array levels. It then sorts each array by their keys to make sure json_encode() encodes it as array and not object.
The sorting is needed, otherwise json_encode() generates an object; this behaviour is explained in a note on the json_encode() documentation page:
When encoding an array, if the keys are not a continuous numeric sequence starting from 0, all keys are encoded as strings, and specified explicitly for each key-value pair.
// If $arr has numeric keys (not all keys are tested!) then returns
// an array whose keys are a continuous numeric sequence starting from 0.
// Operate recursively for array values of $arr
function fillKeys(array $arr)
{
// Fill the numeric keys of all values that are arrays
foreach ($arr as $key => $value) {
if (is_array($value)) {
$arr[$key] = fillKeys($value);
}
}
$max = max(array_keys($arr));
// Sloppy detection of numeric keys; it may fail you for mixed type keys!
if (is_int($max)) {
// Fill the missing keys; use NULL as value
$arr = $arr + array_fill(0, $max, NULL);
// Sort by keys to have a continuous sequence
ksort($arr);
}
return $arr;
}
// Some array to test
$arrNew[0][5][0][0][1]["id"] = 1;
$arrNew[0][3][0][2][1]["id"] = 2;
$arrNew[0][5][0][0][1]["name"] = 'Test';
echo("============= Before ==============\n");
echo(json_encode($arrNew)."\n");
$normal = fillKeys($arrNew);
echo("============= After ==============\n");
echo(json_encode($normal)."\n");
The output:
============= Before ==============
[{"5":[[{"1":{"id":1,"name":"Test"}}]],"3":[{"2":{"1":{"id":2}}}]}]
============= After ==============
[[null,null,null,[[null,null,[null,{"id":2}]]],null,[[[null,{"id":1,"name":"Test"}]]]]]
The line $arr = $arr + array_fill(0, $max, NULL); uses NULL as values for the missing keys. This is, I think, the best for the Javascript code that parses the array (you can use if (! arr[0]) to detect the dummy values).
You can use the empty string ('') instead of NULL to get a shorter JSON:
[["","","",[["","",["",{"id":2}]]],"",[[["",{"id":1,"name":"Test"}]]]]]
but it requires slightly longer code on the JS side to detect the dummy values (if (arr[0] != '')).
This question already has answers here:
How to combine two arrays together?
(3 answers)
Closed 8 years ago.
To start off, let's look at the arrays -
$array1 = array('user#email.com','user2#email.com','user3#email.com'); // Imagine this has over a million users
$array2 = array('domain1.com','domain2.com'); // This may have between 10-20 domains
What I want to do is loop through the users and continuously assign a domain in the second array to a user in the first array. It should look like this when completed -
$finished = array('user#email.com' => 'domain1.com', 'user2#email.com' => 'domain2.com', 'user3#email.com' => 'domain1.com');
How can I loop through $array1 and sequentially assign a domain from $array2 to each user?
This is mind boggling me right now.
Just FYI array_combine() from the "Duplicate Answer" is incorrect to this answer. The correct answer is below. If $array1 has 5,000,000 emails in it and $array2 has 10 domains in it, the finished array would only give the first 10 items in the array a corresponding domain. Those who marked it duplicate did not read the full description, or do not understand PHP.
Similar to Barmar's, but using the key:
$count = count($array2);
foreach($array1 as $key => $value) {
$finished[$value] = $array2[$key % $count];
}
This works with your posted arrays, however if you have lets say all even or all odd keys in $array1 this would bomb, also with an associative array.
Increment an index in $array2 modulo its size as you assign values.
$index = 0;
$finished = array();
foreach ($array1 as $email) {
$finished[$email] = $array2[$index];
$index = ($index + 1) % count($array2);
}
Make a loop over the larger set. Keep a secondary index variable, and each iteration, increment it, then set it to its value modulo the size of the smaller set. Use this secondary index as the index into your second set to assign to the element from the first set.
Let's say I have an array like so:
array(
[0]=>1
[1]=>3
[3]=>5
[15]=>6
);
Arbitrarily I want array[15] to be the first:
array(
[15]=>6
[0]=>1
[1]=>3
[3]=>5
);
What is the fastest and most painless way to do this?
Here are the things I've tried:
array_unshift - Unfortunately, my keys are numeric and I need to keep the order (sort of like uasort) this messes up the keys.
uasort - seems too much overhead - the reason I want to make my element the first in my array is to specifically avoid uasort! (Swapping elements on the fly instead of sorting when I need them)
Assuming you know the key of the element you want to shift, and that element could be in any position in the array (not necessarily the last element):
$shift_key = 15;
$shift = array($shift_key => $arr[$shift_key]);
$arr = $shift + $arr;
See demo
Updated - unset() not necessary. Pointed out by #FuzzyTree
You can try this using a slice and a union operator:
// get last element (preserving keys)
$last = array_slice($array, -1, 1, true);
// put it back with union operator
$array = $last + $array;
Update: as mentioned below, this answer takes the last key and puts it at the front. If you want to arbitrarily move any element to the front:
$array = array($your_desired_key => $array[$your_desired_key]) + $array;
Union operators take from the right and add to the left (so the original value gets overwritten).
If #15 is always last you can do
$last = array_pop($array); //remove from end
array_unshift($last); //push on front
To reorder the keys for sorting simply add
$array = array_values($array); //reindex array
#Edit - if we don't assume its always last then I would go with ( if we always know wwhat the key is, then most likely we will know its position or it's not a numerically indexed array but an associative one with numeric keys, as op did state "arbitrarily" so one has to assume the structure of the array is known before hand. )
I also dont see the need to reindex them as the op stated that it was to avoid sorting. So why would you then sort?
$item = $array[15];
unset($array[15]); //....etc.
Update: My original intention for this question was to determine if PHP actually has this feature. This has been lost in the answers' focus on the scalar issue. Please see this new question instead: "Does PHP have autovivification?" This question is left here for reference.
According to Wikipedia, PHP doesn't have autovivification, yet this code works:
$test['a']['b'] = 1;
$test['a']['c'] = 1;
$test['b']['b'] = 1;
$test['b']['c'] = 1;
var_dump($test);
Output:
array
'a' =>
array
'b' => int 1
'c' => int 1
'b' =>
array
'b' => int 1
'c' => int 1
I found that this code works too:
$test['a'][4] = 1;
$test['b'][4]['f'] = 3;
But adding this line causes an warning ("Warning: Cannot use a scalar value as an array")
$test['a'][4]['f'] = 3;
What's going on here? Why does it fail when I add the associative element after the index? Is this 'true' Perl-like autovivification, or some variation of it, or something else?
Edit: oh, I see the error with the scalar now, oops! These work as expected:
$test['a'][4]['a'] = 1;
$test['a'][4]['b'] = 2;
$test['a'][5]['c'] = 3;
$test['a'][8]['d'] = 4;
So, php does have autovivification? Searching Google for "php autovivification" doesn't bring up a canonical answer or example of it.
From the PHP manual on the square bracket syntax:
$arr[] = value;
If $arr doesn't exist yet, it will be created, so this is also an alternative way to create an array
With your example:
$test['a'][4] = 1;
Since $test and $test['a'] don't currently exist; they are both created as arrays.
$test['b'][4]['f'] = 3;
$test['b'] and $test['b'][4] don't currently exist; they are both created as arrays.
$test['a'][4]['f'] = 3;
$test['a'][4] does exist, but it is an integer (1). This is the "scalar value" that cannot be used as an array. You can't use the square bracket [] syntax on number values; it doesn't convert an existing value to an array.
According to the results, PHP has autovivification. The error comes from the way it works.
When you say: $a[1][2] = 55, PHP wants to insert the 55 into $a[1] as [2]=>55. Since the $a[1] is non-existent, PHP is creating automatically an empty node, cca. $a[1] = Array(). But when the node already exists, PHP does not create $a[1], just performs the [2]=>55, which is an error, if $a[1] is not array-like (array, object).
The last language I've seen, where nodes may have value and children too, is MUMPS. There were also a function, called $DATA(), which told wheter a node has any child (10), value (1) or both (11), or it's non-existent (0). I think, that's the correct handling of associative arrays.
(Anyway, I like this behavior of PHP.)
With:
$test['b'][4]['f'] = 3;
You're not adding an element to
$test['a'][4]
bacause it's not initialized as an array.
If you'd write:
$test['a'][4] = array(1);
Then it would work.
With:
$test['a']['b'] = 1;
$test['a']['c'] = 1;
$test['b']['b'] = 1;
$test['b']['c'] = 1;
You're implicitly initializing $test['a'] and $test['b'] as an array. But $test['a']['b'] (and so on) as an int