2

I have an array of objects. These objects have a property id. I need a function which returns the next available id (which is not used by an object).

array = [ { id: 1 }, { id: 2 }, { id: 5 }, { id: 3 } ]

I would like to have a function which takes an array as an input and returns a number (which is the next free id).

In the example case:

findFreeId(array){ magic happens }

result --> 4

Daniel Widdis
  • 8,424
  • 13
  • 41
  • 63

3 Answers3

5

How about something like this?

function findFreeId (array) {
  const sortedArray = array
    .slice() // Make a copy of the array.
    .sort(function (a, b) {return a.id - b.id}); // Sort it.
  let previousId = 0;
  for (let element of sortedArray) {
    if (element.id != (previousId + 1)) {
      // Found a gap.
      return previousId + 1;
    }
    previousId = element.id;
  }
  // Found no gaps.
  return previousId + 1;
}

// Tests.
let withGap = [{id: 1}, {id: 2}, {id: 5}, {id: 3}];
let noGap = [{id: 1}, {id: 2}];
let empty = [];

console.log(findFreeId(withGap)); // 4
console.log(findFreeId(noGap)); // 3
console.log(findFreeId(empty)); // 1
Morgan Wilde
  • 16,795
  • 10
  • 53
  • 99
  • Code–only answers aren't particularly helpful. You should explain where the issues are in the OP's code and how your answer addresses them. `for (const element of ...)` is the opposite of semantic— a *const* that has a different value on each iteration. I don't see why the entire array needs to be sorted when only the ID is of interest. – RobG Jan 25 '19 at 00:42
  • 1
    @RobG I respectfully disagree with most of your statements. OP didn't submit any *code* to comment on, he was looking for a solution. Furthermore, good and well written code that's self explanatory is always preferable to a wall of text. Sorting an array, of IDs or full objects, has absolutely not performance penalty, so why make the code less readable? Finally, I do agree that `const` in the for loop is a mistake, thank you for bringing that up. – Morgan Wilde Jan 25 '19 at 18:00
  • Thank you @MorganWilde, your solution is just what I was looking for and I really appreciate you sharing this! I hope you have yourself a wonderful day good sir! – Bartekus Jan 08 '21 at 02:19
  • For making this code work with ID starting with 0, change starting value of previousId to -1. – Guy with jewels' names Aug 06 '23 at 18:03
1

A simple approach is to get all the ID values, sort them, then starting at 0 look for the first missing number in the sequence. That may be OK where efficiency doesn't matter, but a more efficient method is to:

  1. Get the IDs
  2. Sort them
  3. Step through the values to get the next available number
  4. Insert the value in the list of IDs
  5. Store the value so next time it starts at #3 from the previous value + 1

E.g.

class IDStore {
  constructor(dataArray) {
    if (!Array.isArray(dataArray)) {
      return null;
    }
    this.previousIndex = 0;
    this.indexes = dataArray.map(obj => obj.id).sort();
  }
  
  get nextIndex() {
    while (this.indexes[this.previousIndex] == this.previousIndex) {
      this.previousIndex++;
    }
    return this.previousIndex;
  }
  
  addIndex(index) {
    if (!Number.isInteger(index) || this.indexes.find[index]) {
      return null;
    }
    this.indexes.push(index);
    this.indexes.sort();
    return index;
  }
}

var data = [ { id: 1 }, { id: 2 }, { id: 5 }, { id: 3 } ];

// Create an ID store
var idStore = new IDStore(data);

// Insert more objects in the array with unique IDs
for (var i=0, next; i<4; i++) {
  // Current list of indexes
  console.log('Indexes: ' + idStore.indexes);
  // Get the next available index
  next = idStore.nextIndex;
  console.log('Next available: ' + next);
  // Calling nextIndex doesn't affect next index
  next = idStore.nextIndex;
  console.log('Next available: ' + next);

  // Use next index
  data.push({id: next});
  // Adding next index is manual
  idStore.addIndex(next);
  console.log('Added: ' + next);
}

// Data structure is independent
console.log('End: ' + JSON.stringify(data));

This is somewhat simplistic in that it assumes the IDs are sequential integers starting at 0 and doesn't have much validation or error handling.

Maintaining the id is separate from adding new members to the data array. It would be much better to combine the operations, so an "add object" method gets the next available ID, adds it to the object, adds the object to the array, updates the index and returns the new ID.

RobG
  • 142,382
  • 31
  • 172
  • 209
  • "Premature optimization is the root of all evil", a wise man once said... – Morgan Wilde Jan 25 '19 at 18:03
  • @MorganWilde—compared to copying, sorting and looping over every element of the array using *for..of* on every call? – RobG Jan 26 '19 at 10:53
  • In this case? Absolutely. But I'm sure you're too smug to see problems with your approach, as is all too apparent from your tone. Good luck. – Morgan Wilde Jan 26 '19 at 11:10
-1
const findFreeId = (ids) => {
let id = 0;
for (id; ; id++) {
    let isFree = true;
    for (let i = 0; i < array.length; i++) {
        const e = ids[i];
        if (e === id) {
            isFree = false;
            break;
        }
    }
    if (isFree) break;
}
return id;
}
Shima Guds
  • 1
  • 1
  • 1
  • 1
    As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Feb 01 '23 at 09:27