3

TL;DR

Given an array of arrays, I need to make sure that every array has the same length of the first.

The original problem

I have a function that should transpose a matrix lines to columns.

 Original     Result
  1,2,3       1,4,7
  4,5,6       2,5,8
  7,8,9       3,6,9

How is the current implementation

I implemented it using array_map:

<?php
$items = array_map(function (...$items) {
    return $items;
}, ...$values);

What I need to achieve

When an array is shorter than the first I need to throw a LengthException.

The ideal solution

The ideal solution would be storing the length of the first element outside the array_map and just compare with the length of the current item being iterated by array_map, like this:

<?php
$expectedLength = count(reset($values));
$items = array_map(function (...$items) {
    $length = count($items);
    if ($length !== $expectedLength) {
        throw new LengthException("Element size differs ({$length} should be {$expectedLength})");
    }
    return $items;
}, ...$values);

Why the ideal solution doesn't work

From the PHP manual:

Usually when using two or more arrays, they should be of equal length because the callback function is applied in parallel to the corresponding elements. If the arrays are of unequal length, shorter ones will be extended with empty elements to match the length of the longest.

Sadly, the shorter arrays will be filled with empty elements.

Since the original array could have empty elements, I'm left with no other way to test if the array was originally shorter than the first.

My first and cumbersome solution

I have to test it before array_map and I'm not convinced that this is a good solution:

<?php
$expectedLength = count(reset($values));
$diffLength = count(array_intersect_key(...$values));

if ($diffLength !== $expectedLength) {
    throw new LengthException("Element size differs ({$diffLength} should be {$expectedLength})");
}

$items = array_map(function (...$items) {
    return $items;
}, ...$values);

Would you be kind enough to help me find a better way? I would like to do it inside the iteration, but if you find a better way to find if we have an element with a different length (I don't like to be using array_intersect_key) before the iteration, that's OK too.

Álvaro Guimarães
  • 2,154
  • 2
  • 16
  • 24
  • 1
    *"shorter ones will be extended with empty elements"* – Testing for "empty elements" won't work...? – deceze Aug 11 '16 at 13:23
  • The original element could be empty, testing for emptiness would not tell me if was array_map filling the gaps or the original value. – Álvaro Guimarães Aug 11 '16 at 13:24
  • 2
    Then doing a sanity test *before* the actual operator would indeed be the right thing to do. It also makes the actual `array_map` operation simpler and sticks better to the single-responsibility principle. – deceze Aug 11 '16 at 13:26
  • Why do you exactly need it to be inside `array_map()`? Wouldn't it be better to know the length before you transpose the array?! Also take a look at `array_reduce()` with which you can go through each array, get the length and as soon as one is shorter or longer you can detect it. – Rizier123 Aug 11 '16 at 13:26
  • Can't you just take `count()` of first elment and compare with the rest using `foreach`? – u_mulder Aug 11 '16 at 13:28
  • 1
    You could also use a [`MultipleIterator`](http://php.net/manual/en/class.multipleiterator.php) and attach all your subArrays as array iterator and then you can simple transpose your arrays just for the amount of elements each subArray has. – Rizier123 Aug 11 '16 at 13:31
  • @Rizier123 I didn't know that class, but I'm afraid I can't use it because of context that I didn't cover on my question. Sorry! – Álvaro Guimarães Aug 11 '16 at 13:39
  • @deceze but what is your opinion on using `array_intersect_key` to check for inconsistencies on elements length? – Álvaro Guimarães Aug 11 '16 at 13:46
  • http://stackoverflow.com/questions/797251/transposing-multidimensional-arrays-in-php – epascarello Aug 11 '16 at 14:24

2 Answers2

1

You can keep your ideal solution with a small adjustment:

array_map(function (...$items) {
    $nonNullItems = array_filter(
        $items,
        function ($item) { return $item !== null }
    );
    $length = count($nonNullItems);
    if ($length !== $expectedLength) {
        throw new LengthException(
            "Element size differs ({$length} should be {$expectedLength})"
        );
    }
    return $items;
}

Note this addition:

array_filter($items, function ($item) { return $item !== null});

It will filter out all elements in items that are null (but not 0 or .0).

edit: I'm not sure but array_filter($items) might work as well, but I'm not sure if without a callback empty or null items are dismissed so the above is making sure only null is dismissed.

dbrumann
  • 16,803
  • 2
  • 42
  • 58
0

Codes: (Demo)

$arrays=[
    [['a','b','c'],['d','e','f'],['g','h','i']],   // test array #1: no missing elements
    [['a','b','c'],['d','e','f'],['g','h']],       // test array #2: missing element i
    [['a','b','c'],['d','e'],['g','h','i']],       // test array #3: missing element f
    [['a','b'],['d','e','f'],['g','h','i']],       // test array #4: missing element c
    [['a','b','c'],['d','e','f'],['g','h',NULL]],  // test array #5: NULL on i
];

// find non-specific imbalance using COUNT_RECURSIVE
foreach($arrays as $i=>$a){  // running 5 separate tests
    echo "Test #$i:\n";
    if(($originalsize=sizeof($a,1))===($rotatedsize=sizeof(array_map(function(){return func_get_args();},...$a),1))){
        echo "@$i originalsize ($originalsize) = rotatedsize ($rotatedsize)\n";
    }else{
        echo "@$i originalsize ($originalsize) DOES NOT EQUAL rotatedsize ($rotatedsize)\n";
    }
}

echo "\n\n---\n\n";

// Find specific subarray imbalances using COUNT
foreach($arrays as $i1=>$a){  // running 5 separate tests
    echo "Test #$i1:\n";
    $firstsize=sizeof($a[0]);
    foreach(array_slice($a,1,null,true) as $i2=>$others){
        if($firstsize==($othersize=sizeof($others))){
            echo "The first size ($firstsize) = subarray $i2 size ($othersize)\n";
        }else{
            echo "The first size ($firstsize) DOES NOT EQUAL subarray $i2 size ($othersize)\n";
        }
    }
    echo "\n";
}

Output:

Test #0:
@0 originalsize (12) = rotatedsize (12)
Test #1:
@1 originalsize (11) DOES NOT EQUAL rotatedsize (12)
Test #2:
@2 originalsize (11) DOES NOT EQUAL rotatedsize (12)
Test #3:
@3 originalsize (11) DOES NOT EQUAL rotatedsize (12)
Test #4:
@4 originalsize (12) = rotatedsize (12)


---

Test #0:
The first size (3) = subarray 1 size (3)
The first size (3) = subarray 2 size (3)

Test #1:
The first size (3) = subarray 1 size (3)
The first size (3) DOES NOT EQUAL subarray 2 size (2)

Test #2:
The first size (3) DOES NOT EQUAL subarray 1 size (2)
The first size (3) = subarray 2 size (3)

Test #3:
The first size (2) DOES NOT EQUAL subarray 1 size (3)
The first size (2) DOES NOT EQUAL subarray 2 size (3)

Test #4:
The first size (3) = subarray 1 size (3)
The first size (3) = subarray 2 size (3)
mickmackusa
  • 43,625
  • 12
  • 83
  • 136
  • @ÁlvaroGuimarães I am interested in this question, but perhaps I am misunderstanding the goal. Please have a look at my two methods and let me know if I am achieving your desired result or if something isn't quite meeting the brief. – mickmackusa Aug 25 '17 at 14:26