skip to Main Content

I am passing an object with 5 arrays to my function. One of the properties is called ‘Indexes’. What this function is doing is filling in the array with zeros in ascending order, all other arrays follow the Indexes array as their index so if the Indexes array is [1,3] it will fill it with [1,2,3] and all arrays will have 0 in position 2.

My problem is when Indexes contain .5 (work-related, so I have to roll with it), if I have [43, 44.5, 45] I want to get [43, 44, 44.5, 45]. But the way I wrote my code seems to skip with whole numbers completely, and I think I am making it more complicated for myself than it needs to be.

Here’s the function


function fillMissingValues(csv) {
    const indexes = csv.Indexes;
    const allReads = csv['All reads'];
    const trimmedByAdapterOrQuality = csv['Trimmed by Adapter or Quality'];
    const trimmedByAdapter = csv['Trimmed by adapter'];
    const trimmedByQuality = csv['Trimmed by quality'];

    const filledIndexes = [];
    const filledAllReads = [];
    const filledTrimmedByAdapterOrQuality = [];
    const filledTrimmedByAdapter = [];
    const filledTrimmedByQuality = [];

    let lastIndex = 0;
    for (let i = 0; i < indexes.length; i++) {
        const index = indexes[i];
        const value = allReads[i];
        const taq = trimmedByAdapterOrQuality[i];
        const ta = trimmedByAdapter[i];
        const tq = trimmedByQuality[i];

        while (lastIndex < index) {
            filledIndexes.push(lastIndex);
            filledAllReads.push(0);
            filledTrimmedByAdapterOrQuality.push(0);
            filledTrimmedByAdapter.push(0);
            filledTrimmedByQuality.push(0);
            lastIndex++;
        }

        filledIndexes.push(index);
        filledAllReads.push(value);
        filledTrimmedByAdapterOrQuality.push(taq);
        filledTrimmedByAdapter.push(ta);
        filledTrimmedByQuality.push(tq);
        lastIndex++;
    }

    // Fill the remaining indexes with zeros
    const maxIndex = Math.max(...indexes);
    while (lastIndex <= maxIndex) {
        filledIndexes.push(lastIndex);
        filledAllReads.push(0);
        filledTrimmedByAdapterOrQuality.push(0);
        filledTrimmedByAdapter.push(0);
        filledTrimmedByQuality.push(0);
        lastIndex++;
    }

    const filledCSV = {
        Indexes: filledIndexes,
        'All reads': filledAllReads,
        'Trimmed by Adapter or Quality': filledTrimmedByAdapterOrQuality,
        'Trimmed by adapter': filledTrimmedByAdapter,
        'Trimmed by quality': filledTrimmedByQuality
    };

    return filledCSV;
}

Example of passing data,

csv = {
        Indexes:[ 3, 4, 6.5, 8]
        'All reads': [1, 2, 3, 4],
        'Trimmed by Adapter or Quality': [1, 2, 3, 4],
        'Trimmed by adapter': [1, 2, 3, 4],
        'Trimmed by quality': [1, 2, 3, 4],
}

so output should be

filledCSV = {
        Indexes:[0, 1, 2, 3, 4, 5, 6, 6.5, 7, 8]
        'All reads': [0, 0, 0, 1, 2, 0, 0, 3, 0, 4],
        'Trimmed by Adapter or Quality': [0, 0, 0, 1, 2, 0, 0, 3, 0, 4],
        'Trimmed by adapter': [0, 0, 0, 1, 2, 0, 0, 3, 0, 4],
        'Trimmed by quality': [0, 0, 0, 1, 2, 0, 0, 3, 0, 4],
}

2

Answers


  1. Create an array with all the integers from 0 to Math.max(...indexes).

    Merge indexes into this array, remove duplicates, and sort it.

    Loop over this combined array. If the element is in indexes, push the corresponding elements of all the other properties into the corresponding property in filledCSV. If not, push 0.

    const csv = {
      Indexes: [3, 4, 6.5, 8],
      'All reads': [1, 2, 3, 4],
      'Trimmed by Adapter or Quality': [5, 4, 3, 2],
      'Trimmed by adapter': [5, 10, 15, 20],
      'Trimmed by quality': [10, 15, 18, 30],
    }
    
    const filledCSV = Object.fromEntries(Object.keys(csv).map(key => [key, []]));
    
    let maxIndex = Math.max(...csv.Indexes);
    let origIndexMap = Object.fromEntries(csv.Indexes.map((index, i) => [index, i]));
    let filledIndexes = [...new Set([...Array(maxIndex).keys(), ...csv.Indexes])];
    filledIndexes.sort((a, b) => a - b);
    filledCSV.Indexes = filledIndexes;
    
    filledIndexes.forEach(index => {
      let origIndex = origIndexMap[index];
      Object.entries(filledCSV).forEach(([key, array]) => {
        if (key != 'Indexes') {
          array.push(origIndex === undefined ? 0 : csv[key][origIndex]);
        }
      });
    });
    
    console.log(filledCSV);
    Login or Signup to reply.
    1. Make a copy of the original object
    2. Loop over Indexes with an incremented value from 0 and fill all the arrays accordingly
    const csv = {
      Indexes: [3, 4, 6.5, 8],
      'All reads': [1, 2, 3, 4],
      'Trimmed by Adapter or Quality': [5, 4, 3, 2],
      'Trimmed by adapter': [5, 10, 15, 20],
      'Trimmed by quality': [10, 15, 18, 30],
    }
    
    const rest = [];
    const filledCSV = Object.keys(csv).reduce((r, k) => (r[k] = csv[k].slice(), k !== 'Indexes' && rest.push(r[k]), r), {});
    const {Indexes} = filledCSV;
    
    let idx = -1, i = -1;
    while(++idx < Indexes.at(-1)){
      if(idx === Indexes[++i]) continue;
      while((Indexes[i]|0) < idx) i++; // if the next values are not integers, jump over them
      Indexes.splice(i, 0, idx);
      rest.forEach(r => r.splice(i, 0, 0));
    }
    
    console.log(JSON.stringify(filledCSV));

    For a more performant version pre-allocate the arrays:

    const csv = {
      Indexes: [3, 4, 6.5, 8],
      'All reads': [1, 2, 3, 4],
      'Trimmed by Adapter or Quality': [5, 4, 3, 2],
      'Trimmed by adapter': [5, 10, 15, 20],
      'Trimmed by quality': [10, 15, 18, 30],
    }
    
    const found = []; // collect existing indices
    const Indexes = csv.Indexes.slice();
    
    let idx = -1, i = -1;
    while(++idx < Indexes.at(-1)){
      if(idx === Indexes[++i]) {
        found.push(i);
        continue;
      }
      while((Indexes[i]|0) < idx) found.push(i++); // if the next values are not integers, jump over them
      Indexes.splice(i, 0, idx);
    }
    found.push(++i);
     
    const filledCSV = {Indexes};
    for(const k in csv){
      if(k === 'Indexes') continue;
      const arr = filledCSV[k] ??= Array(Indexes.length).fill(0);
      found.forEach((i, j) => arr[i] = csv[k][j]);
    }
    
    console.log(JSON.stringify(filledCSV));
    ` Chrome/122
    ---------------------------------------------------------
    Alexander optimized  1.00x | x1000000 515 520 539 539 670
    Alexander            1.51x | x1000000 779 780 781 788 822
    Barmar               6.66x |  x100000 343 346 353 356 362
    ---------------------------------------------------------
    https://github.com/silentmantra/benchmark `
    
    ` Firefox/123
    ---------------------------------------------------------
    Alexander optimized  1.00x | x1000000 718 748 771 787 789
    Alexander            2.31x |  x100000 166 168 168 171 173
    Barmar               5.79x |  x100000 416 421 421 435 449
    ---------------------------------------------------------
    https://github.com/silentmantra/benchmark `
    
    const csv = {
      Indexes: [3, 4, 6.5, 8],
      'All reads': [1, 2, 3, 4],
      'Trimmed by Adapter or Quality': [5, 4, 3, 2],
      'Trimmed by adapter': [5, 10, 15, 20],
      'Trimmed by quality': [10, 15, 18, 30],
    }
    
    // @benchmark Barmar
    {
    const filledCSV = Object.fromEntries(Object.keys(csv).map(key => [key, []]));
    
    let maxIndex = Math.max(...csv.Indexes);
    let origIndexMap = Object.fromEntries(csv.Indexes.map((index, i) => [index, i]));
    let filledIndexes = [...new Set([...Array(maxIndex).keys(), ...csv.Indexes])];
    filledIndexes.sort((a, b) => a - b);
    filledCSV.Indexes = filledIndexes;
    
    filledIndexes.forEach(index => {
      let origIndex = origIndexMap[index];
      Object.entries(filledCSV).forEach(([key, array]) => {
        if (key != 'Indexes') {
          array.push(origIndex === undefined ? 0 : csv[key][origIndex]);
        }
      });
    });
    filledCSV;
    }
    
    // @benchmark Alexander
    {
    const rest = [];
    const filledCSV = Object.keys(csv).reduce((r, k) => (r[k] = csv[k].slice(), k !== 'Indexes' && rest.push(r[k]), r), {});
    const {Indexes} = filledCSV;
    
    let idx = -1, i = -1;
    while(++idx < Indexes.at(-1)){
      if(idx === Indexes[++i]) continue;
      while((Indexes[i]|0) < idx) i++; // if the next values are not integers, jump over them
      Indexes.splice(i, 0, idx);
      rest.forEach(r => r.splice(i, 0, 0));
    }
    filledCSV;
    }
    // @benchmark Alexander optimized
    {
    const found = [];
    const Indexes = csv.Indexes.slice();
    
    let idx = -1, i = -1;
    while(++idx < Indexes.at(-1)){
      if(idx === Indexes[++i]) {
        found.push(i);
        continue;
      }
      while((Indexes[i]|0) < idx) found.push(i++); // if the next values are not integers, jump over them
      Indexes.splice(i, 0, idx);
    }
    found.push(++i);
     
    const filledCSV = {Indexes};
    for(const k in csv){
      if(k === 'Indexes') continue;
      const arr = filledCSV[k] ??= Array(Indexes.length).fill(0);
      found.forEach((i, j) => arr[i] = csv[k][j]);
    }
    filledCSV;
    }
    
    /*@skip*/ fetch('https://cdn.jsdelivr.net/gh/silentmantra/benchmark/loader.js').then(r => r.text().then(eval));
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search