0

Suppose I have these 2 arrays of objects:

A:

[
  [0] => (stdClass) {
    id => '0',
    ....
  },
  [1] => (stdClass) {
    id => '1',
    ....
  },
  ,
  [2] => (stdClass) {
    id => '2',
    ....
  }
]

B:

[
  [0] => (stdClass) {
    id => '0',
    name => 'Hello',
    a_id => '2'
    ....
  },
  [1] => (stdClass) {
    id => '1',
    name => 'World',
    a_id => '2'
    ....
  },
  [2] => (stdClass) {
    id => '2',
    name => 'foo',
    a_id => '0'
    ....
  }
]

When B.a_id = A.id, the object from B belongs to A, and I want to add the object in an array to A, like this:

A:

[
  ...,
  [2] => (stdClass) {
    id => '2',
    mapped_objs => [
       [0] => (stdClass) {
       id => '0',
       name => 'Hello',
       a_id => '2'
       ....
       },
      [1] => (stdClass) {
        id => '1',
        name => 'World',
        a_id => '2'
        ....
      },
    ]
  }
  ...,
]

Is there an efficient algorithms or functions in PHP that will solve this problem? Do I have to do this in O(n^2)?

Joshua Leung
  • 2,219
  • 7
  • 29
  • 52
  • If you insure that your top level array keys match the id property of the class the map lookup by key will be very fast and you will not need to search through A. You simply foreach through B and assign `if (isset(array1[B.id])` – gview Sep 26 '17 at 22:01
  • https://stackoverflow.com/questions/14842272/php-array-merge-two-arrays-on-same-key – kyle Sep 26 '17 at 22:06
  • the important question here is: does the `id` value (property) always match the key? – Jakumi Sep 26 '17 at 23:23

1 Answers1

1

O(n^2)? Depends

If this structure (and the stdClass definitions) are beyond your control, probably. The only alternative in this case, is to lookup B.a_id in A whenever accessing that property. Additionally using array_filter to match a collection from B when needing a list for A. (lots of code duplication) Such a solution will only be more efficient than an O(n^2) if you only need one side of that relationship, and/or won't access all instances of those relationships.

An important component of that solutions is the "whenever accessing that property" piece, which applies to both sides of the one-to-many data-relationship. This leads me to a (correct?) better solution: OOP.

Define the objects as objects, especially for APIs.

Usually when I hear a dev say "I can't change the object or data-structure," what they really mean is that they don't want to. (Or they don't know it can be done easily).

Rather than allowing these objects to be defined as stdClass, make them a real class.

Looping through each set once, and converting them into their own objects, will solve your problem without an nested loop, as well as other problems you haven't yet thought of. The key to solving this problem without a nested loop lies in proper use of the primary-key, A.id <-- B.a_id. If you use A.id as the index for your array of A objects, you need no loop to find a match for B.

That little bit of data-structure basics aside. OOP will save your life if you use it, so that'll be the code sample I provide.

$arrayA = []; // your source for A
$arrayB = []; // your source for B

class A {
    public $id, $bList;

    public function __construct(stdClass $aOrig)
    {
        $this->id = $aOrig->id;
    }
    
    public function addB(B $b) {
        $this->bList[$b->id] = $b;
    }
}

class B {
    public $id, $name, $a_id, $a;
    
    public function __construct(stdClass $bOrig)
    {
        $this->id = $bOrig->id;
        $this->name = $bOrig->name;
        $this->a_id = $bOrig->a_id;
    }
}

$aList = [];
foreach ($arrayA as $aO) {
    // since .id is your data's primary-key, use it like one and make it your array-index
    $aList[$aO->id] = new A($aO); 
}

$bList = []; // not necessary, as you'll these objects in A->bList 
foreach ($arrayB as $bO) {
    // same primary-key trick (can skip creating $bList)
    $b = $bList[$bO->id] = new B($bO);
    // get A by its key (and array-index) AND assign it to B
    $b->a = $a = $aList[$b->a_id];
    // add B to A's bList and you're done.
    $a->addB($b);
}
Community
  • 1
  • 1
Tony Chiboucas
  • 5,505
  • 1
  • 29
  • 37
  • It sets the parent relationship of B. A:B is a one-to-many relationship. A::bList = [B,B,B] is one side of that relationship, while B::a = A is the other. It stores a reference to B's parent A (matching a_id) in B's `a` property. Then, with any B, you can `$b->a->bList` or any other property or method of A. – Tony Chiboucas Sep 27 '17 at 07:49
  • More specifically, with OOP you will have _other_ objects/functions which accept only a `B` object . . . `function getLongDescr(B $b): string { return $b->a->name . $b->name; }` – Tony Chiboucas Sep 28 '17 at 16:12