Skip to content

array_unique: add way to compare items with an identity check (===) #10526

Closed as not planned

Description

@wernerm

Description

The following code:

<?php
var_dump(array_unique([1, "1"], SORT_REGULAR));

Resulted in this output:

array(1) {
  [0]=>
  int(1)
}

But I expected this output instead:

array(2) {
  [0]=>
  int(1)
  [1]=>
  string(1) "1"
}

Adding the SORT_REGULAR flag should result in items being compared normally (not changing item types).

PHP Version

PHP 8.0.20

Operating System

No response

Activity

  1. Girgias commented on Feb 7, 2023

    @Girgias
    Member

    This is the expected behaviour, as SORT_REGULAR uses == under the hood.

    From the sort() page (which has a better description then array_unique())

    SORT_REGULAR - compare items normally; the details are described in the comparison operators section

    Therefore, changing this to a feature request, by possibly introducing a new flag.

  2. changed the title [-]array_unique: Implicit type-casting is performed in spite of the `SORT_REGULAR` flag[/-] [+]array_unique: add way to compare items with an identity check (``===``)[/+] on Feb 7, 2023
  3. ndossche commented on Feb 7, 2023

    @ndossche
    Member

    I just wonder what the behaviour should be in case someone decides to use this new flag in sorting and the values aren't comparable because they are of different types. An exception maybe? But then again that's probably a bit strange because array_unique wouldn't have this issue.

  4. blar commented on Feb 7, 2023

    @blar
    Contributor

    Should the default behaviour of array_uniq depend on declare(strict_types = 1)?

  5. damianwadley commented on Feb 7, 2023

    @damianwadley
    Member

    Should the default behaviour of array_uniq depend on declare(strict_types = 1)?

    strict_types is about function arguments at call time. It shouldn't control runtime behavior this way.

  6. KapitanOczywisty commented on Feb 8, 2023

    @KapitanOczywisty

    I just wonder what the behaviour should be in case someone decides to use this new flag in sorting and the values aren't comparable because they are of different types. An exception maybe? But then again that's probably a bit strange because array_unique wouldn't have this issue.

    New sorting method have to sort by type first, because array_unique is relaying on consistent comparisons for optimization, so if used in sort it would sort by types then values - probably useless but would work fine.

    Thus new method would look something like this:

    function cmp_strict(mixed $a, mixed $b): int
    {
    	if ($a === $b) return 0;
    
    	$typeA = gettype($a);
    	$typeB = gettype($b);
    	if ($typeA === $typeB) {
    		// abstract function to compare values without type juggling
    		return cmp_values_strict($a, $b);
    	} else {
    		return $typeA <=> $typeB;
    	}
    }

    Therefore, changing this to a feature request, by possibly introducing a new flag.

    As with sort functions, array_unique could allow use of custom comparison function to cover more complex cases (e.g. when multiple values have same meaning), though flag would be more convenient in almost every case.

  7. ndossche commented on Feb 8, 2023

    @ndossche
    Member

    Thanks for pointing that out, it looks indeed like a logical comparison function. I can spend some time this week to try and implement a PoC.

  8. ndossche commented on Feb 10, 2023

    @ndossche
    Member

    Err wait... The example code of a comparator posted here wouldn't work too... For example cmp_strict(0, '0') would cause the branch with the spaceship operator to be executed, which results in a return value of 0. This means that array_unique would remove on of the two, which is the exact problem in this issue report.

    The main problem is that there seems to be no total order relation on elements of all sorts of different type combinations. For example: is '0'<0, or is 0<'0'? If both answers are yes then we have an inconsistency in sorting. If both answers are no then they must be equal, which causes OP's problem. In other cases the ordering is arbitrary.

    So although this issue proposes to add a new flag, we cannot use that flag in a consistent way for sorting. The flag would be unique to array_unique and must be handled in a special way AFAICT. Or we have to come up with an ordering scheme that works for all type combinations.

  9. KapitanOczywisty commented on Feb 10, 2023

    @KapitanOczywisty

    For example cmp_strict(0, '0') would cause the branch with the spaceship operator to be executed, which results in a return value of 0.

    @nielsdos Branch with spaceship operator is only comparing types, which are always string and always starting with letter, so it's safe comparison. https://3v4l.org/jnLno

    For example: is '0'<0, or is 0<'0'?

    "integer" < "string" thus 0<'0'.

    So although this issue proposes to add a new flag, we cannot use that flag in a consistent way for sorting.

    Sorting by type first is consistent way of sorting, not sure if should be allowed in sort, but won't hurt anything.

    Or we have to come up with an ordering scheme that works for all type combinations.

    Comparing types first should be faster for mixed arrays, which is ideal for array_unique.

    This is a bit closer to proper implementation: https://3v4l.org/2oRQt

  10. ndossche commented on Feb 10, 2023

    @ndossche
    Member

    @KapitanOczywisty Sorry, my bad, I misread the code. So the solution here is the "arbitrary order" option I listed which seems not super arbitrary after all since it seems to be consistent with zend_compare. I'll try this tomorrow.

  11. added a commit that references this issue on Feb 11, 2023
  12. iluuu1994 commented on Mar 23, 2023

    @iluuu1994
    Member

    This is essentially the same as #9775, so I'm closing this as a duplicate. Please continue discussion there if you have anything to add.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions