How can I extract numbers sequence from array?

Viewed 237

I have the following array :

var selectedRange = [
  null,
  null,
  null,
  null,
  null,
  null,
  null,
  null,
  9, //sequences
  10,//sequences
  null,
  12,
  13,
  14,
  null,
  null,
  null,
  null,
  null,
  20,
  null,
  null,
  null,
  null,
  25,
  null,
  null,
  null,
  29,
  30,
  31,
  32,
  null,
  null,
  null,
  null,
  37,
  38,
  null,
  40,
  41,
  42,
  null,
  44,
  null,
  null,
  47,
  null,
  49,
  50,
  null,
  null,
  null,
  null,
  null,
  null
]

I need to extract sequences of number and only numbers - so the result would be like:

["9,10", "12,13,14", "20", "25", "29,30,31,32", "44", "47", "49,50"]

The problem is that I can't come up with a solution to extract a sequence as it is, I guess I need a recursive function that repeats the part of checking if the last number and the current are following - like here ...(!!prm && (prm - (index-1) ==1 )

let tempArr = [];
$.each(selectedRange, function (index, prm) {
    if (!!prm && (prm - (index - 1) == 1) {
        tempArr.push(prm); // need to loop this section with recursion 
    }
});
8 Answers

One option is to join into a comma delimited string and then split apart the empty elements. Add a final filter to remove leading or trailing empty elements. This might not be the most efficient.

var selectedRange = [null, null, null, null, null, null, null, null, 9, 10, null, 12, 13, 14, null, null, null, null, null, 20, null, null, null, null, 25, null, null, null, 29, 30, 31, 32, null, null, null, null, 37, 38, null, 40, 41, 42, null, 44, null, null, 47, null, 49, 50, null, null, null, null, null, null ]

console.log(
  selectedRange.join(',').split(/,,+|^,|,$/).filter(Boolean)
)  

(Edited to incorporate comment-suggestions from @KooiInc and @Ifaruki.)

You could iterate the array and have a look to the predecessor.

let selectedRange = [null, 8, null, null, null, 9, 10, null],
    result = [],
    last;

for (const value of selectedRange) {
    if (value === null) continue;
    if (last + 1 === value) result[result.length - 1] += `,${value}`;
    else result.push(value.toString());
    last = value;
}

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

You can use Array.prototype.reduce to do this operation as follows.

var selectedRange = [ null, null, null, null, null, null, null, null, 9, 10, null, 12, 13, 14, null, null, null, null, null, 20, null, null, null, null, 25, null, null, null, 29, 30, 31, 32, null, null, null, null, 37, 38, null, 40, 41, 42, null, 44, null, null, 47, null, 49, 50, null, null, null, null, null, null ];

let split = null;
const output = selectedRange.reduce((acc, cur) => {
  if (cur) {
    split ? split += "," + cur : split = `${cur}`;
  } else {
    if (split) {
      acc.push(split);
      split = null;
    }
  }
  return acc;
}, []);
console.log(output);

strong text

You can do the following,

var selectedRange = [ null, null, null, null, null, null, null, null, 9, 10, null, 12, 13, 14, null, null, null, null, null, 20, null, null, null, null, 25, null, null, null, 29, 30, 31, 32, null, null, null, null, 37, 38, null, 40, 41, 42, null, 44, null, null, 47, null, 49, 50, null, null, null, null, null, null ];

let isNullSeq = true;
const arr = [];
let temp = '';
selectedRange.forEach((item) => {
  if (item) {
    if (isNullSeq) {
      temp = temp + item;
      isNullSeq = false;
    } else {
      temp += ',';
      temp += item;
    }
  } else {
    if (!isNullSeq) {
      isNullSeq = true;
      arr.push(temp);
      temp = '';
    }
  }
});
console.log(arr);

if you only have nulls in between - how about something simpler?

var selectedRange = [ null, null, null, null, null, null, null, null, 9, 10, null, 12, 13, 14, null, null, null, null, null, 20, null, null, null, null, 25, null, null, null, 29, 30, 31, 32, null, null, null, null, 37, 38, null, 40, 41, 42, null, 44, null, null, 47, null, 49, 50, null, null, null, null, null, null]
console.log(selectedRange.toString().replace(/,{2,}/g, ',,').split(",,").filter(function(el) { return el != "";}))

I would probably use takeWhile and dropWhile, you can get them from an utility library like Ramda or implement them by yourself.

const inputs = [
  [1,2,null,null,5,6,7,null,null,10,11],
  [null,null,3,4,null],
  [1,2,null,null,5,null,null],
  [null],
  [1],
  [null, 1],
  [],
];

const takeWhile = (predicate) => ([head, ...tail]) => {
  if (head === undefined || !predicate (head)) return [];
  return [head].concat (takeWhile (predicate) (tail));
}

const dropWhile = (predicate) => ([head, ...tail]) => {
  if (head === undefined) return [];
  if (!predicate (head)) return [head, ...tail];
  return dropWhile (predicate) (tail);
};

const isNull = x => x === null;
const isNotNull = x =>  x !== null;

const sequence = (list) => {
  if (list.length === 0) return [];
  if (list[0] === null) return sequence (dropWhile (isNull) (list));
  return [takeWhile (isNotNull) (list).join (',')].concat (
    sequence (dropWhile (isNotNull) (list))
  );
}

console.log (inputs.map (sequence));
.as-console-wrapper { max-height: 100% !important; top: 0; }

Create an intermediate array of sequence arrays using Array.reduceand map the resulting sequences to strings

const selectedRange = [null,null,null,null,null,null,null,null,9, 10,null,12,13,14,null,null,null,null,null,20,null,null,null,null,25,null,null,null,29,30,31,32,null,null,null,null,37,38,null,40,41,42,null,44,null,null,47,null,49,50,null,null,null,null,null,null];

// the reducer lambda
const sequenceReducer = (acc, val) =>
  !val ? acc : val - 1 === acc.flat().pop() ?
    acc[acc.length-1].push(val) && acc : [...acc, [].concat(val)];

console.log( JSON.stringify( selectedRange.reduce(sequenceReducer, []).map( v => v.join() ) ) );

// let's try discontinuous sequences (numbers are not consecutive)
const x = [null, 8, null, null, null, 9, 10, null, 11, null, null, 12, null, 14, null, 15, null, null, 16];
console.log( JSON.stringify( x.reduce(sequenceReducer, []).map( v => v.join() ) ) );

// sequences are discontinuous AND not ordered. Sort first
// (and by the way, note the negative values)
const y = [26, 13, null, 8, null, -3, null, 9, 25, 10, null, 11, null, 7, 12, null, 14, -2, 15, null, 17, 16, -1];
const sortSequenceArray = (a, b) => +a - +b;
console.log( JSON.stringify( y.sort(sortSequenceArray).reduce(sequenceReducer, []).map( v => v.join() ) ) );

By taking into consideration sequence like given in question (9, 10,..., - seq = diff of 1). Following code will produce the the required result -

var selectedRange = [
  null,
  null,
  null,
  null,
  null,
  null,
  null,
  null,
  9, //sequences
  10,//sequences
  null,
  12,
  13,
  14,
  null,
  null,
  null,
  null,
  null,
  20,
  null,
  null,
  null,
  null,
  25,
  null,
  null,
  null,
  29,
  30,
  31,
  32,
  null,
  null,
  null,
  null,
  37,
  38,
  null,
  40,
  41,
  42,
  null,
  44,
  null,
  null,
  47,
  null,
  49,
  50,
  null,
  null,
  null,
  null,
  null,
  null
]
 

selectedRange = selectedRange.filter(v => v)
var finalArray = [], temp = [];
for(var i=0; i<selectedRange.length; i++)
{
    if(selectedRange[i+1]-selectedRange[i] > 1 )
    {
        temp.push(selectedRange[i]);
        
        finalArray.push(temp.join(","));
        temp.length = 0;

    }    
    else temp.push(selectedRange[i])

}
finalArray.push(temp.join(","));

console.log(finalArray);

Related