skip to Main Content

I have an array of strings, which are either an asterisk by itself ("*") or something else.

I want to "merge" consecutive asterisks as follows:

If there are one or more asterisks in a row, remove them from the array, and prepend a single asterisk to the next non-asterisk element. If the asterisks are at the end of the array, simply remove them.

Some smaller examples:

[..., "*", "4", ...]      => [..., "*4", ...] // concat asterisk with string at the right
[..., "*", "*", "4", ...] => [..., "*4", ...] // remove first asterisk
[..., "*", "4", "*"]      => [..., "*4"]      // remove last asterisk
[..., "4", "*", "*", "*"] => [..., "4" ]      // remove all asterisk which have no alphanumeric at its right

For this full-sized input:

let data = ["0", "1", "2", "3", "4", "5", "6", "*", "*", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "*", "34", "*", "36", "37", "*", "39", "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "50", "51", "52", "*", "54", "*", "56", "57", "58", "59", "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "70", "71", "72", "73", "*", "*", "*", "*", "78", "79", "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "*", "91", "92", "93", "94", "95", "*", "97", "*", "*"];

The result should be:

["0", "1", "2", "3", "4", "5", "6", "*9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "*34", "*36", "37", "*39", "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "50", "51", "52", "*54", "*56", "57", "58", "59", "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "70", "71", "72", "73", "*78", "79", "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "*91", "92", "93", "94", "95", "*97"]

How can I implement this? Here is my attempt:

let data = ["0", "1", "2", "3", "4", "5", "6", "*", "*", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "*", "34", "*", "36", "37", "*", "39", "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "50", "51", "52", "*", "54", "*", "56", "57", "58", "59", "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "70", "71", "72", "73", "*", "*", "*", "*", "78", "79", "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "*", "91", "92", "93", "94", "95", "*", "97", "*", "*"];
let output = data.map((d, index, arr) => arr[index - 1] === '*' ? '*'+d : d).filter(d => d !== '*');
console.log(output);

7

Answers


  1. You are almost there! The one missing feature is testing for successive *s?

    const datasets = [
      ["1", "*", "4", "5"],
      ["1", "*", "*", "4", "5"],
      ["1", "*", "4", "*"],
      ["1", "4", "*", "*", "*"],
    ]
    
    const output = datasets.map(
      dataset => dataset
      .map((d, index, arr) => arr[index - 1] === '*' && d !== '*' ? '*' + d : d)
      .filter(d => d !== '*')
    )
    
    console.log(output);
    Login or Signup to reply.
  2. Here is a solution.

    let data = ["0", "7", "*", "9", "33", "34", "*", "*", "36","54", "*", "89", "*","97", "98", "*"];
    
    
    let newArr = [];
    let isAstrictFound = false;
    data.map(val =>{
      if(val !== '*'){
        let newVal = isAstrictFound ? '*'+val.toString() : val.toString();
        newArr.push(newVal);
        isAstrictFound = false;
      }else{
        isAstrictFound = true;
      }
    });
    console.log(newArr);
    
    Login or Signup to reply.
  3. function myFunc(data) {
      let result = [];
      for (let index = 0; index < data.length - 1; index++) {
        let current = data[index];
        let next = data[index + 1];
        // if current and next create a pair like *<number>
        if (current == '*' && next != '*') {
          result.push(current + next)
        }
        // if current is not star and not pushed already
        if (current != '*' && !result.includes(`*${current}`)) {
          result.push(current);
        }
      }
      return result;
    }
    
    
    // Ex: 1
    console.log(myFunc(["0", "1", "2", "3", "4", "5", "6", "*", "*", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "*", "34", "*", "36", "37", "*", "39", "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "50", "51", "52", "*", "54", "*", "56", "57", "58", "59", "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "70", "71", "72", "73", "*", "*", "*", "*", "78", "79", "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "*", "91", "92", "93", "94", "95", "*", "97", "*", "*"]))
    // Ex: 2
    console.log(myFunc(["1", "*", "4", "5"]))
    // Ex: 3
    console.log(myFunc(["1", "*", "*", "4", "5"]))
    // Ex: 4
    console.log(myFunc(["1", "*", "4", "*"]))
    // Ex: 5
    console.log(myFunc(["1", "4", "*", "*", "*"]))

    Explanation-

    1. Loop over the elements.
    2. Check the current item’s and next item’s order, if it’s *<non_star> then merge both and push them into the new array.
    3. If the current item is not a star and not present already as *<curent_item> then push it too.
    Login or Signup to reply.
  4. As for the OP’s use case I propose an array join, regex-based string replace and string split approach where the regular expressions patterns is as follows …

    /(?:,*)+(?:,(?<value>[^,*]+))?/g
    

    … and its description is provided with the pattern’s test/playground page.

    The advantage comes with the expressiveness of dealing with the provided array data as one big string (hence joining the array) and processing it with a single regex-based replace task, where the intermediate replacement result is split again into the final array structure.

    const sampleData = ["0", "1", "2", "3", "4", "5", "6", "*", "*", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "*", "34", "*", "36", "37", "*", "39", "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "50", "51", "52", "*", "54", "*", "56", "57", "58", "59", "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "70", "71", "72", "73", "*", "*", "*", "*", "78", "79", "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "*", "91", "92", "93", "94", "95", "*", "97", "*", "*"];
    
    // see ... [https://regex101.com/r/I3XdKM/1]
    const regXAsteriskSequences = /(?:,*)+(?:,(?<value>[^,*]+))?/g;
    
    console.log(
      'array `join` and regex-based `replace` only ...',
      sampleData
        .join(',')
        .replace(regXAsteriskSequences, (_, value = null) =>
          (value !== null) && `,*${ value }` || ''
        )
    );
    console.log(
      'array `join`, regex-based `replace` and string `split` ...',
      sampleData
        .join(',')
        .replace(regXAsteriskSequences, (_, value = null) =>
          (value !== null) && `,*${ value }` || ''
        )
        .split(',')
    );
    .as-console-wrapper { min-height: 100%!important; top: 0; }

    Edit … in order to provide a "more to the ground" approach.

    const sampleData = ["0", "1", "2", "3", "4", "5", "6", "*", "*", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "*", "34", "*", "36", "37", "*", "39", "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "50", "51", "52", "*", "54", "*", "56", "57", "58", "59", "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "70", "71", "72", "73", "*", "*", "*", "*", "78", "79", "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "*", "91", "92", "93", "94", "95", "*", "97", "*", "*"];
    
    function trimAsteriskSequences(arr) {
      let result = [], idx = -1, currentItem, nextItem;
    
      while (currentItem = arr[++idx]) {
        if (currentItem !== '*') {
    
          result.push(currentItem)
        } else if (
          (currentItem === '*') &&
          (nextItem = arr[idx + 1]) &&
          (nextItem !== '*')
        ) {
          result.push(['*', nextItem].join(''))
          ++idx;
        }
      }
      return result;
    }
    
    console.log(
      trimAsteriskSequences(sampleData)
    );
    .as-console-wrapper { min-height: 100%!important; top: 0; }

    Final Note I myself find the regex based variant still more elegant and expressive though the later provided above solution might be easier to read/maintain for beginners.

    Login or Signup to reply.
  5. You can do this in a single loop by simply checking if the current element is an *, if not then push(), concatenating if the previous element was an *.

    function collapse(array) {
      const result = [];
    
      for (const [i, n] of array.entries()) {
        if (n !== '*') {
          result.push(array[i - 1] === '*' ? `*${n}` : n);
        }
      }
    
      return result;
    }
    
    console.log(...collapse(['*', 1, 2, '*', 3]));
    console.log(...collapse(['*', '*', 2, '*', 3]));
    console.log(...collapse(['*', '*', 2, '*', '*']));
    console.log(...collapse(["0", "1", "2", "3", "4", "5", "6", "*", "*", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "*", "34", "*", "36", "37", "*", "39", "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "50", "51", "52", "*", "54", "*", "56", "57", "58", "59", "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "70", "71", "72", "73", "*", "*", "*", "*", "78", "79", "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "*", "91", "92", "93", "94", "95", "*", "97", "*", "*"]));

    Or the same logic in a reduce() call if you prefer

    const input = ["0", "1", "2", "3", "4", "5", "6", "*", "*", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "*", "34", "*", "36", "37", "*", "39", "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "50", "51", "52", "*", "54", "*", "56", "57", "58", "59", "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "70", "71", "72", "73", "*", "*", "*", "*", "78", "79", "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "*", "91", "92", "93", "94", "95", "*", "97", "*", "*"];
    
    const result = input.reduce((a, n, i, { [i - 1]: prev }) => {
      if (n !== '*') {
        a.push(prev === '*' ? `*${n}` : n);
      }
      return a;
    }, []);
    
    console.log(...result);

    Alternatively you can avoid the second condition (and thus the second array access) in the concatenation by storing the previous value on each iteration. This is similar to the state machine answer in function, but avoids the repeated destructuring and object creation.

    function collapse(array) {
      const result = [];
    
      let prev = '';
      for (const n of array) {
        if (n !== '*') {
          result.push(prev + n);
          prev = '';
        } else {
          prev = '*';
        }
      }
    
      return result;
    }
    
    console.log(...collapse(['*', 1, 2, '*', 3]));
    console.log(...collapse(['*', '*', 2, '*', 3]));
    console.log(...collapse(['*', '*', 2, '*', '*']));
    console.log(...collapse(["0", "1", "2", "3", "4", "5", "6", "*", "*", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "*", "34", "*", "36", "37", "*", "39", "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "50", "51", "52", "*", "54", "*", "56", "57", "58", "59", "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "70", "71", "72", "73", "*", "*", "*", "*", "78", "79", "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "*", "91", "92", "93", "94", "95", "*", "97", "*", "*"]));

    Basic online benchmark

    Test case name Result
    Simple loop (store prev) 1,228,712 ops/sec ±2.22% (64 runs sampled)
    Simple loop 351,955 ops/sec ±0.24% (67 runs sampled)
    State machine 31,174 ops/sec ±0.27% (64 runs sampled)
    Login or Signup to reply.
  6. This can be modeled as a simple state machine:

    State Diagram

    We start with an empty state value. When the state is empty and we encounter an asterisk, we move to the asterisk state. When we encounter a different value, we append it to our output and stay in the empty state. When we’re in the asterisk state, if we encounter an asterisk, we stay in the asterisk state and leave the output alone. When we encounter something else, we append *<value> to our output, and return to the empty state. There’s no cleanup needed at the end; we just return the output.

    We can code that state machine with a simple reduce call, which tracks a state variable and the current output array, like this:

    const trimAsterisks = (data) => data .reduce (
      ({state, output}, val) => val == '*' 
        ? {state: '*', output} 
        : {state: '', output: output .concat (state + val)}, 
      {state: '', output: []}
    ) .output
    
    const data = ["0", "1", "2", "3", "4", "5", "6", "*", "*", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "*", "34", "*", "36", "37", "*", "39", "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "50", "51", "52", "*", "54", "*", "56", "57", "58", "59", "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "70", "71", "72", "73", "*", "*", "*", "*", "78", "79", "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "*", "91", "92", "93", "94", "95", "*", "97", "*", "*"]
    
    console .log (trimAsterisks (data))
    .as-console-wrapper {max-height: 100% !important; top: 0}

    Because our state value is either an asterisk or an empty string, we can just prepend it to our value and append the result to the output array.

    Login or Signup to reply.
  7. Here is an optimized version of pilchard’s "Simple loop (store prev)" solution:

    function collapsePrev2(array) {
      const result = [];
    
      let prev = false;
      for (const n of array) {
        if (n !== '*') {
          if (prev) {
             result.push('*' + n);
             prev = false;
          }
          else
            result.push(n);
        }
        else
          prev = true;
      }
    
      return result;
    }
    

    The differences are:

    • Store a boolean in prev instead of a string
    • Concatenate strings only when necessary (not at each iteration)
    • Reset prev only when necessary

    The benchmark gives:

    Test case name Result
    collapsePrev 1,130,945 ops/sec ±0.59% (65 runs sampled)
    collapsePrev2 1,299,316 ops/sec ±0.73% (67 runs sampled)
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search