5

I know that there is a similar question already answered, but mine is different in a way that I just don't want to find it, I just want to update it

I have a JS array of objects like this:

let data = [
  {
    contact_id: 15,
    use_id: 16,
    name: 'ABC',
    phoneNo: '0092123456789',
    checked: false,
  },
  {
    contact_id: 16,
    use_id: 22,
    name: 'DEF',
    phoneNo: '0092123456788',
    checked: false,
  },
  {
    contact_id: 17,
    use_id: 24,
    name: 'XYZ',
    phoneNo: '0092123456787',
    checked: false,
  }
];

Now with this in mind, I have a property named phoneNo which guaranteed unique values.

Now what I want is to toggle the value of checked for a given number.

What I have tried:

for (let i = 0; i < data.length; i++) {
    if (data[i]['phoneNo'] === item.phoneNo) {
        data[i]['checked'] = !data[i]['checked'];
        break; // to make it a little efficent :-)
    }
}

I wrote this code and it worked fine for a while but now I have realized that the list is growing larger and larger and this code iterates over and over again, which is very inefficient.

What I want?

Is there a way that I could directly toggle the value of the property checked given the value of phoneNo without looping over every single item?

zsubzwary
  • 1,196
  • 1
  • 14
  • 22
  • Look into [`filter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter) to get only those items that have the correct phone number. One way or another, you're going to need to iterate. – Heretic Monkey Jun 11 '19 at 14:26
  • Possible duplicate of [Find object by id in an array of JavaScript objects](https://stackoverflow.com/questions/7364150/find-object-by-id-in-an-array-of-javascript-objects) – Heretic Monkey Jun 11 '19 at 14:28
  • 1
    "which is very inefficient" - it actually isn't. Like, in a typical Web application, your version costs $1, an "optimized" version would cost $0.70, while one single network request costs $15,000. Are these 30 cents really worth caring about? – georg Jun 11 '19 at 14:29
  • @HereticMonkey - That's still a loop. – T.J. Crowder Jun 11 '19 at 14:31
  • @T.J.Crowder As is your answer, as it loops to create the Map and then again to get the values into an array. The only way to not loop is to change the original structure. Also, the question says `Is there a way that I could directly toggle the value of the property checked given the value of phoneNo without looping over every single item?` With the `filter` solution, it doesn't toggle the checked property while looping over every single item, only over those items with the id found. – Heretic Monkey Jun 11 '19 at 14:35
  • @HereticMonkey - If you read more closely and perhaps look at the live example, I'm saying *replace* the array with a Map. No population loop. Just a lookup in the Map (which will be sublinear). *"With the filter solution, it doesn't toggle the checked property while looping over every single item, only over those items with the id found."* Right. Just like the OP's code. In any case, if you're looking for a single item, `filter` is the wrong tool. Use `find`. – T.J. Crowder Jun 11 '19 at 14:37
  • @T.J.Crowder... so we agree? You can't eliminate the loop without changing the structure. I mean, if you want to go on picking apart *a comment*, please feel free... – Heretic Monkey Jun 11 '19 at 14:40
  • @HereticMonkey - Again, see the answer, for instance where I said "Not with your current structure." Yes, people comment on comments all the time. `filter` offers nothing whatsoever in regard to what the OP asked for. – T.J. Crowder Jun 11 '19 at 14:43

7 Answers7

4

Another option is to change the way your data is stored. If phoneNo is unique you could make data with phone numbers being the keys.

let data2={
  '0092123456789':{
    contact_id: 15,
    use_id: 16,
    name: 'ABC',
    checked: false,
  },
  '0092123456788':{
    contact_id: 16,
    use_id: 22,
    name: 'DEF',
    checked: false,
  },
  '0092123456787':{
    contact_id: 17,
    use_id: 24,
    name: 'XYZ',
    checked: false,
  }};
myPhoneNo = '0092123456787';
data2[myPhoneNo].checked = true;
console.log(data2);
depperm
  • 10,606
  • 4
  • 43
  • 67
3

Is there a way that I could directly toggle the value of the property checked given the value of phoneNo without looping over every single item?

Not with your current structure. But you can use a Map instead of (or in addition to) the array, and then it's trivial:

const entry = mappedData.get(item.phoneNo);
entry.checked = !entry.checked;

If you need to loop through the map's values, you can use the iterator from its values method, for instance:

for (const entry of mappedData) {
    // ...
}

If you have just a map and need to get an array of values for some reason, you can use values() with spread notation:

const data = [...mappedData.values()];

Here's an example using just a map, without the array, and looping through the map's values:

// ONE TIME, add and remove entries as needed
let mappedData = new Map([
  {
    contact_id: 15,
    use_id: 16,
    name: 'ABC',
    phoneNo: '0092123456789',
    checked: false,
  },
  {
    contact_id: 16,
    use_id: 22,
    name: 'DEF',
    phoneNo: '0092123456788',
    checked: false,
  },
  {
    contact_id: 17,
    use_id: 24,
    name: 'XYZ',
    phoneNo: '0092123456787',
    checked: false,
  }
].map(entry => [entry.phoneNo, entry]));

console.log("Before:");
for (const {phoneNo, checked} of mappedData.values()) {
    console.log(`${phoneNo}: ${checked}`);
}

// Each time you want to toggle
let phoneNo = '0092123456788';
console.log(`Checking ${phoneNo}...`);
let entry = mappedData.get(phoneNo);
entry.checked = !entry.checked;

console.log("After:");
for (const {phoneNo, checked} of mappedData.values()) {
    console.log(`${phoneNo}: ${checked}`);
}

Unlike searching an array, where the access time is linear, access time to a Map's entries is required to be sublinear (achieved by using hash maps or similar).

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
3

You can create an object whose keys will be the phone numbers and values will be object that will refer the object in the array data

const obj = {}

let data = [ { contact_id: 15, use_id: 16, name: 'ABC', phoneNo: '0092123456789', checked: false, }, { contact_id: 16, use_id: 22, name: 'DEF', phoneNo: '0092123456788', checked: false, }, { contact_id: 17, use_id: 24, name: 'XYZ', phoneNo: '0092123456787', checked: false, } ];

data.forEach(x => {
  obj[x.phoneNo] = x;
})

let given = '0092123456788';
obj[given].checked = !obj[given].checked

console.log(data)
Maheer Ali
  • 35,834
  • 5
  • 42
  • 73
2

If you know the index of the object then you can just change it in 1 line. How ever, if the array changes the index of the object then you have to loop.

russell
  • 61
  • 6
1

No, you must know the index of the element you want to change in the array or loop through all items to find the one that matches what you're looking for.

anacampesan
  • 105
  • 10
1

You have to iterate the array. But you can shorten your loop by only finding the matching index first. You can find the object with the given phoneNo first and then toggle the value. Try the following code:

''' const matchData = data.find(x => x.phoneNo === item.phoneNo) matchData.checked = !matchData.checked '''

1

Very simple and Very fast

var data = [{
    contact_id: 15,
    use_id: 16,
    name: 'ABC',
    phoneNo: '0092123456789',
    checked: false,
  },
  {
    contact_id: 16,
    use_id: 22,
    name: 'DEF',
    phoneNo: '0092123456788',
    checked: false,
  },
  {
    contact_id: 17,
    use_id: 24,
    name: 'XYZ',
    phoneNo: '0092123456787',
    checked: false,
  }],
    result = data.some(function(x){return '0092123456787'==x.phoneNo ? true:false});
    console.log(result);
Shakti S
  • 351
  • 1
  • 3
  • 13