Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.javascript > #31918

Re: Get object from one of its values

From Thomas 'PointedEars' Lahn <PointedEars@web.de>
Newsgroups comp.lang.javascript
Subject Re: Get object from one of its values
Date 2016-12-20 13:33 +0100
Organization PointedEars Software (PES)
Message-ID <2298053.9Mp67QZiUf@PointedEars.de> (permalink)
References <W9ednbBeYoFENcXFnZ2dnUU7-RfNnZ2d@westnet.com.au>

Show all headers | View raw


Andrew Poulos wrote:

> If there are one or more objects each of which has a property called
> childArray whose value is always a one dimensional array and elements of
> the array are unique (ie an element cannot also occur in the childArray
> of another object) then if I know the value of one element of a
> childArray how do I find the object it's associated with?
> 
> For example if I have:
> 
> var a = {};
>      a.childArray = [1,2,3,a,b,c,..];
> var b = {};
>      b.childArray = [4,5,6,d,e,f,...];
> var c = {};
>      c.childArray = [7,8,9,g,h,i,...];
> ...
> 
> and I'm given the value "5" how do I find that it's associated with "b"?
> 
> The code is dynamically created so I don't know beforehand what or how
> many objects will be created.

As Jake said correctly, you need to check the objects sequentially (but see 
bottom).  This means that you need to check the variables or properties 
sequentially that hold references to those objects.

If you know the variables or properties, you can create an array of their 
values.  With ECMAScript 2016 methods and syntax:

  var objects = [a, b, c];
  objects.find(obj =>
    obj.hasOwnProperty("childArray") && obj.childArray.indexOf(5) > -1);

It might also be necessary to check whether obj.childArray holds a reference 
to an Array instance, using Array.isArray().


But suppose the less trivial case that there are variables “a” to at most 
“z” to refer to objects, and you do not know how many, then you will have to 
use a loop that is checking whether the variable exists and its value is an 
object reference.  For example:

  var obj;  

  for (var i = "a".charCodeAt(0), end = "z".charCodeAt(0);
       i <= end; ++i)
  {
    var name = String.fromCharCode(i);

    /*
     * NOTE:
     * It is not possible to write a function that determines if a
     * variable was declared in a specified execution context as
     * there is no way to refer to a calling execution context.
     * It is only possible to define such a function in the execution
     * context that is to be searched.  (this != [[Scope]])
     */
    try
    {
      obj = eval(name);
    }
    catch (e)
    {
      if (e instanceof ReferenceError) continue;
    }

    if (typeof obj != "object" || obj == null) continue;

    if (!obj.hasOwnProperty("childArray") || obj.childArray.indexOf(5) < 0)
    {
      obj = null;
    }
    else
    {
      break;
    }
  }

  if (typeof obj == "object" && obj != null)
  {
    /* use obj */
  }

This can be written in a slightly shorter way using an array of identifiers:
 
  /* ["a", "b", "c", …, "z"]; a useful pattern to remember */
  var identifiers = Array.apply(null, {length: 26}).map(
    (() => {
      var offset = "a".charCodeAt(0);
      return ((e, i) => String.fromCharCode(i + offset));
    })()
  );

  var matchingIdentifier = identifiers.find(name => {
    var obj;

    try
    {
      obj = eval(name);
    }
    catch (e)
    {
      if (e instanceof ReferenceError) return false;
    }

    if (typeof obj != "object" || obj == null) return false;

    if (!obj.hasOwnProperty("childArray") || obj.childArray.indexOf(5) < 0)
    {
      return false;
    }

    return true;
  });

  if (typeof matchingIdentifier == "string")
  {
    var obj = eval(matchingIdentifier);
  }

With variables, you can only avoid eval() if you declare potentially missing 
variables in advance:

   var a, b, …, z;

Then you can generate the array containing the values to be searched as 
follows:

   var objects = [a, b, …, z];

And proceed as if you knew how many variables there are in the original 
code.

The caveat here is that you must not use variable names that are already 
used by the original code.  The only way to be certain that this does not 
happen is declaring variables with “let” (ECMAScript 2015+) instead of “var” 
within a Block-like statement of your code, and then only the missing ones.

If possible, access properties instead of variables in this situation; then 
you can use Object.property.hasOwnProperty(), or simply “"propertyName" in 
object” if inheritance is not an issue.


Finally, if you need to do this search several times, and the referred 
objects are not changing in-between, it will improve efficiency greatly if 
you maintain a map of values to objects instead:

  var v2o = new Map();
  objects.forEach(obj => obj.childArray.forEach(el => v2o.set(el, obj)));

Determining the object whose “childArray” property refers to an Array 
instance that contains the Number value “5” will then be as simple as

  var needle = v2o.get(5);

whereas “needle” will either hold a reference to that object, or “undefined” 
if there is no matching object in the indexed haystack referred to by “v2o”.

[Note that this way a value will map to a reference to the last searched 
object in which it was found.  It is also possible to map a value either to 
an array of references to matching objects, or to a reference to the first 
searched matching object.

Note also that to know the identifier/name of the variable(s) or 
property/properties that referred to that object you need to keep an array 
(also) of names, not (only) object references.  Objects have identity, not 
name.]

The Map object is specified as a constructor property of the global object 
since ECMAScript 2015 as well (ES 2015, § 18.3.4)  A simplified polyfill of 
it is easily written:

  if (typeof Map != "function")
  {
    var Map = function () {
      /* 
       * NOTE:
       * Assigns reference to object with empty prototype chain,
       * to avoid name collisions; a polyfill may be necessary.
       */
      this.items = Object.create(null);
    };

    Map.prototype.get = function (key) {
      return this.items[key];
    };

    Map.prototype.set = function (key, value) {
      this.items[key] = value;
    };
  }

For a more elaborate version, that is however not yet written to be 
ECMAScript 2015 compliant as it was based on the Java implementation (e.g., 
.put = .set), see 
<https://github.com/PointedEars/JSX/blob/517e2fa56831a3dff8eb2bb7c7e7cbe45c5d0a86/map.js>.

-- 
PointedEars
FAQ: <http://PointedEars.de/faq> | <http://PointedEars.de/es-matrix>
<https://github.com/PointedEars> | <http://PointedEars.de/wsvn/>
Twitter: @PointedEars2 | Please do not cc me./Bitte keine Kopien per E-Mail.

Back to comp.lang.javascript | Previous | NextPrevious in thread | Next in thread | Find similar | Unroll thread


Thread

Get object from one of its values Andrew Poulos <ap_prog@hotmail.com> - 2016-12-20 14:43 +1100
  Re: Get object from one of its values Jake Jarvis <pig_in_shoes@yahoo.com> - 2016-12-20 10:42 +0100
    Re: Get object from one of its values Andrew Poulos <ap_prog@hotmail.com> - 2016-12-21 07:25 +1100
  Re: Get object from one of its values Thomas 'PointedEars' Lahn <PointedEars@web.de> - 2016-12-20 13:33 +0100
    Re: Get object from one of its values Thomas 'PointedEars' Lahn <PointedEars@web.de> - 2016-12-20 23:10 +0100
  Re: Get object from one of its values John Harris <niam@jghnorth.org.uk.invalid> - 2016-12-20 18:21 +0000

csiph-web