Grouping continuous number as "from~to" strings in JavaScript array - javascript

I have an array with numbers.
var myArray = [1, 2, 3, 5, 8, 9, 10];
and I'd like to group them into a string with a from~to format until the next value is not continuous with the previous value, like the following example.
var outputArray = ["1~3", "5", "8~10"];
How to do that?

You could take a slightly shorter approach by using the index to check if the first element needs an new array or if the value is not in order.
For the second mapping, you could just join all elements.
function group(numbers) {
return numbers
.reduce((result, value, index, array) => {
if (!index || array[index - 1] + 1 !== value) {
result.push([value]);
} else {
result[result.length - 1][1] = value;
}
return result;
}, [])
.map(array => array.join('~'));
}
console.log(group([1, 2, 3, 5, 8, 9, 10]));

You can do this with Array.reduce() followed by Array.map():
const nums = [1, 2, 3, 5, 8, 9, 10]
const res = nums.reduce((acc, cur, idx, arr) => {
if (idx === 0 || cur - 1 > arr[idx - 1]) {
acc.push([cur])
} else {
acc[acc.length - 1].push(cur)
}
return acc
}, []).map(cur => cur.length > 1 ? cur.shift() + "~" + cur.pop() : cur[0])
console.log(res)

Go thru only one time all elements. Whenever there is pattern of numbers not in sequence, just put them in results array.
var myArray = [1, 2, 3, 5, 8, 9, 10];
let start = myArray[0];
let end = myArray[0];
const results = [];
for (let i = 1; i < myArray.length; i++) {
if (myArray[i] === (end + 1)) {
end = myArray[i];
} else {
results.push((start === end) ? `${start}` : `${start}~${end}`);
start = myArray[i];
end = myArray[i];
}
}
results.push((start === end) ? `${start}` : `${start}~${end}`);
console.log(results);

Click here for CodePen (or a Chinese(中文)version).
Basically just create a lastValue variable, and iterating the array. If the currentValue - 1 equals the lastValue then it's a continuous number. Create a new group once the condition failed.
In the final you just merge the groups with a string.
var newArray = [];
var tempArray = [];
var oldArray = [1, 2, 3, 5, 8, 9, 10];
var lastValue = null;
oldArray.forEach(value => {
if (lastValue != value - 1 || lastValue === null) {
tempArray.push([value, value]);
} else {
tempArray[tempArray.length - 1][1] = value;
}
lastValue = value;
});
tempArray.forEach(value => {
if (value[0] === value[1]) {
newArray.push(value[0].toString());
} else {
newArray.push(value.join("~"));
}
});
// OUTPUS: (3) ["1~3", "5", "8~10"]
document.write(JSON.stringify(newArray));

For typescript, this function takes a sorted array of numbers and groups them into a single string.
function convertNumberArrayToRangeString(numbers: number[]): string {
const delimiter = '~';
return numbers
.reduce((accumulator: string, currentValue: number, index: number, array: number[]) => {
if (index > 0 && array[index - 1] + 1 !== currentValue) {
if (index > 2 && array[index - 1] - array[index - 2] === 1) {
accumulator += delimiter + array[index - 1].toString();
}
accumulator += ',' + currentValue.toString();
} else if (index === 0 || index === array.length - 1) { // first or last
if (array[index - 1] === currentValue - 1) {
accumulator += delimiter;
} else if (index > 0) {
accumulator += ',';
}
accumulator += currentValue.toString();
}
return accumulator;
}, '');
}
const list = [1, 2, 3, 6, 7, 8]; // sorted unique list
const groupedList = convertNumberArrayToRangeString(list);
console.log(groupedList);
Output> "1~3,6~8"

Related

How can I return "1-5" if the array is [1,2,3,4,5] in JavaScript?

I am trying to make a program that returns ["1-5"] if I give [1,2,3,4,5].
I have made it but I can't filter it. So I want a code that will filter my output code. Or any code that is better than mine.
let array = [1,2,3,5,6,7,8,10,11, 34, 56,57,];
let x = [];
for(let i = 0; i < array.length; i++){
for(let j = 0; j < array.length; j++){
if(array[i] + j == array[j]){
x.push(array[i] + "-" + array[j]);
}
if(array[j] > array[i] + j && array[j + 1]){
let y = array.slice(j, array.length)
array = y;
i, j = 0;
}
if(array[i] - array[i + 1] != -1 && array[i + 1] - array[i] != 1 && array[i + 1] != undefined){
x.push(array[i]);
}
}
}
console.log(x);
The phrasing of the question makes this somewhat difficult to answer, but based on your code snippet I can gather that you are either:
Attempting to find the range of the entire array OR
Attempting to find contiguous ranges within the array
Based on these interpretations, you could answer this question as follows:
function detectRange(a) {
// clone a
const b = [...a]
// remove first value
const min = max = b.splice(0, 1)[0]
// compute range
const range = b.reduce(({min, max}, i) => {
if(i < min) min = i
if(i > max) max = i
return { min, max }
}, {min, max})
return range
}
function detectRanges(a) {
// clone a
const b = [...a]
// remove first value
const min = max = b.splice(0, 1)[0]
// init ranges array
const ranges = [ ]
// compute ranges
const range = b.reduce(({min, max}, i) => {
if(i === max + 1) {
return {min , max: i}
} else {
ranges.push({min, max})
return {min: i, max: i}
}
}, {min, max})
// push the remaining range onto the array
ranges.push(range)
return ranges
}
function printRange(r) {
console.log(`["${r.min}-${r.max}"]`)
}
function printRanges(r) {
r.forEach(i => {
printRange(i)
})
}
// detect and print range of whole array
printRange(detectRange([1, 2, 3, 5, 6, 7, 8, 10, 11, 34, 56, 57]))
// detect and print only contiguous ranges within array
printRanges(detectRanges([1, 2, 3, 5, 6, 7, 8, 10, 11, 34, 56, 57]))
If you assume that the list is sorted, we only need to traverse the list sequentially. There's no need to have double-nested loops. If you maintain sufficient states, you can determine whether you are in a group and you merely manage the start versus the last element in the group.
To simplify things I made use of ES6 string interpolation ${start}-${last}.
let array = [1,2,3,5,6,7,8,10,11, 34, 56,57];
let result = [ ];
let hasStart = false;
let start = 0;
let last = 0;
for (let num of array) {
if (!hasStart) {
hasStart = true;
last = start = num;
continue;
}
if (num === last + 1) {
last = num;
continue;
}
result.push( start === last ? start : `${start}-${last}` );
last = start = num;
}
if (hasStart) {
result.push( start === last ? start : `${start}-${last}` );
}
console.log(result);
Input: [1,2,3,4,5]
Output: ["1-5"]
So I assume you want to get string in format:
["smallestelement-largestelement"]
var input1 = [1,2,3,4,5]
console.log( "["+'"'+Math.min(...input1)+"-"+Math.max(...input1)+'"'+"]")
If what you want is string in format:
["firstelement-lastelement"]
var input1 = [1,2,3,4,5]
console.log( "["+'"'+input1[0]+"-"+input1.pop()+'"'+"]")
If you have an integer array, and if you want to output the range, you could natively sort() it (you can also provide rules for sorting) and use shift() for the first element and slice(-1) for the last:
let arr = [4,1,5,3].sort();
console.log(arr.shift()+'-'+arr.slice(-1));
As said in the comments, you should clarify if you wish "1-57" for the snippet array, or describe your use case more broadly.
const array = [1, 2, 3, 5, 6, 7, 8, 10, 11, 34, 56, 57];
let s = null;
const result = array.sort((a, b) => a - b).reduce((p, c, i, arr) => {
if (!s) s = c;
if (c + 1 !== arr[i + 1]) {
p.push(s === c ? s : `${s}-${c}`);
s = null;
}
return p
}, [])
console.log(result);

Find unique number in unsorted array JavaScript

I have to find a unique number in unsorted array, but my function returns wrong number, I can't understand why.
Here is my code:
function findUniq(arr) {
let sorted = [...arr].sort();
if (sorted.length === 0) return 0;
// do magic
let num = 0;
for (let i = 1; i < sorted.length; i++) {
if (sorted[num] !== sorted[i]) {
num++;
sorted[num] = sorted[i];
}
}
return num + 1;
}
const testArray = [9, 7, 7, 9, 6, 6, 5, 5, 5];
console.log(findUniq(testArray));
if I invoke findUniq([9,7,7,6,6,5,5,5]) it gives 4. What do I do wrong? Thanks in advance. I forgot to mention I have to have just one for loop to implement O(n) time complexity
I don't know why your solution doesn't work, but here is working code:
const arr = [9,9,7,7,8,6,1,6,5,5,5]
function findUnique(arr){
const sorted = [...arr].sort()
if(sorted[0] !== sorted[1]) return sorted[0]
if(sorted[sorted.length - 1] !== sorted[sorted.length - 2]) return sorted[sorted.length - 1]
for(let i = 0; i < sorted.length; i++){
if(sorted[i] !== sorted[i - 1] && sorted[i] !== sorted[i + 1]) return sorted[i]
}
return "no unique"
}
console.log(findUnique(arr))
This should work:
It returns only one unique number in array or undefined
If you want all unique numbers replace return arr[0] with return arr. It will then return array of all unique numbers (or empty array if there are not any)
function findUniq(arr) {
arr.filter((item, index) => {
arr.splice(index, 1)
const unique = !arr.includes(item)
arr.splice(index, 0, item)
return unique
})
return arr[0]
}
ES6 aproach:
function findUniq(arr) {
return arr
.map((c) => arr.filter((b) => c == b))
.filter((e) => e.length < 2)
.reduce((total, cur) => total.concat(cur), [])
}
You can use reduce and can find the item that repeats once in the last index.
var arr = [9,7,7,6,6,5,5,5]
var uniqueNumber;
arr.reduce((obj,val,index) =>
{
obj[val] ? ++obj[val] : obj[val] = 1;
if(index == (arr.length - 1))
{
uniqueNumber = Object.keys(obj).find(key => obj[key] === 1)
}
return obj
},{})
console.log(uniqueNumber)
To do it in a single O(n) loop, reduce, keeping track of counts as well as a set of singular items.
function findUniq(arr) {
let [single] = arr.reduce((acc, el) => {
if (!acc[el]) acc[el] = 0;
if (++acc[el] === 1) acc.singular.add(el);
else acc.singular.delete(el);
return acc;
}, { singular: new Set() }).singular;
return single;
}
const input = [2, 2, 9, 7, 7, 6, 6, 5, 5, 5];
const result = findUniq(input);
console.log(result);

Is there any other simple way to change my vanilla JavaScript code?

Is there a way I can make my code even more simple?
FYI, I'm a beginner and I've been learning JavaScript only for a week.
Thank you!
let array = [1,2,4,591,392,391,2,5,10,2,1,1,1,20,20];
// from above to [[1, 1, 1, 1], [2, 2, 2], 4, 5, 10, [20, 20], 391, 392, 591]
const answer = (arr) => {
arr.sort((a, b) => {
return a - b;
});
let counter = 0;
arr.forEach((num, i) => {
if (arr[i] === arr[i+1]) {
counter++;
} else if (arr[i] !== arr[i + 1] && arr[i] === arr[i-1]) {
arr[i-counter] = arr.slice(i-counter, i + 1);
counter = 0;
}
});
arr.forEach((num, i) => {
while (arr[i][0] && arr[i][0] === arr[i + 1]) {
arr.splice(i + 1, 1);
}
});
return arr;
}
const newArray = answer(array);
console.log(newArray);
Another option. First push each number in object's appropriate array, then convert object to resulting array as needed.
let arr = [1,2,4,591,392,391,2,5,10,2,1,1,1,20,20];
const obj = {}, res = [];
for(e of arr) !obj[e] ? obj[e] = [e] : obj[e].push(e);
for(a in obj) obj[a].length === 1 ? res.push(obj[a][0]) : res.push(obj[a]);
console.log(res);
You can make it simpler just first sort and then group them.
let array = [1, 2, 4, 591, 392, 391, 2, 5, 10, 2, 1, 1, 1, 20, 20],
last = null;
const result = array
.sort((a, b) => a - b)
.reduce((acc, curr, index, arr) => {
// Getting the next element in array
const next = arr[index + 1];
if (last && last === curr) acc[acc.length - 1].push(curr);
else if (next && next === curr) acc.push([curr]);
else acc.push(curr);
// Changing the last to the curr
last = curr;
return acc;
}, []);
console.log(result);
Approach that uses a Map to group the elements then sorts the Map entries by key so as not to mutate original array at all
let array = [1,2,4,591,392,391,2,5,10,2,1,1,1,20,20];
const answer = (arr) =>{
const m = new Map();
arr.forEach(n => m.set(n, (m.get(n) || []).concat(n)));
return [...m.entries()].sort(([a],[b])=> a-b)
.map(([k,v]) => v.length > 1 ? v : v[0])
}
console.log(answer(array))

trying to find element of array from which sum of left side of array is equal to sum of right side of array in JavaScript

I am trying to find element of array from which sum of left side of array is equal to sum of right side of array in JavaScript
I am using:
function findEvenIndex(arr)
{
//Code goes here!
let num = 0;
function check(i){
console.log(arr);
console.log(num)
let arrPart1 = arr.slice(0,i).reduce(((a,b)=>a+b),0);
console.log(arrPart1)
let arrPart2 = arr.slice(i+1,arr.length).reduce(((c,d)=>c+d),0);
console.log(arrPart2)
if(arrPart2 === 0){
return -1
}
else if(arrPart1 !== arrPart2){
num++;
check(num);
}
}
return check(num);
}
For array:
[1,100,50,-51,1,1]
Getting:
[ 1, 100, 50, -51, 1, 1 ]
0
0
101
[ 1, 100, 50, -51, 1, 1 ]
1
1
1
Error:
The array was: [1,100,50,-51,1,1]
: expected undefined to equal 1
I'd recommend abandoning the recursive solution and opting for an iterative one - in this situation, it's easier to debug. I've written a function that is easy to understand and can solve your problem here:
function findEvenIndex(arr)
{
for(let i = 0; i < arr.length; i++) {
let leftTotal = arr.slice(0, i).reduce((t, v) => t + v, 0);
let rightTotal = arr.slice(i + 1).reduce((t, v) => t + v, 0);
if (leftTotal === rightTotal) {
return i;
}
}
return -1;
}
First check if the array length is an even number
if (array.length % 2 === 1) {
return false;
}
Slice the array in half and reverse the 2nd half
let half = (array.length / 2);
let sub1 = array.slice(0, half);
let sub2 = array.slice(-half).reverse();
Then filter both and return matches
return sub1.filter((num, idx) => num === sub2[idx]);
const array1 = [1, 100, 50, -51, 1, 1];
const array2 = [5, 62, 8, 0, 0, 15, 62, -5];
const array3 = [0, 1, 0];
const mirroredValues = array => {
let half;
if (array.length % 2 === 1) {
return false;
} else {
half = (array.length / 2);
}
let sub1 = array.slice(0, half);
let sub2 = array.slice(-half).reverse();
return sub1.filter((num, idx) => num === sub2[idx]);
};
console.log(mirroredValues(array1));
console.log(mirroredValues(array2));
console.log(mirroredValues(array3));
I would do it like this:
const findElementIndex = (array, index = 0) => {
let arr_part_1 = array.slice(0, index).reduce(((a, b) => a + b), 0);
let arr_part_2 = array.slice(index + 1, array.length).reduce(((a, b) => a + b), 0);
return arr_part_1 === arr_part_2 ? index : index === array.length ? -1 : findElementIndex(array, index + 1);
};
console.log('Example 1: ', findElementIndex([1, 1, 10, 2]));
console.log('Example 2: ', findElementIndex([1, 1, 1, 10, 2]));
console.log('Example 3: ', findElementIndex([100, 1, 100]));
That one could be better from performance point of view (reduce only once)
function inTheMiddle(arr) {
let rightSum = arr.slice(2).reduce((total, item) => total + item, 0);
let leftSum = arr[0];
console.log(leftSum, rightSum);
for (let i = 2; i < arr.length; i++) {
if (leftSum === rightSum) {
return i-1;
}
leftSum += arr[i-1];
rightSum -= arr[i];
console.log(leftSum, rightSum);
}
return -1;
}
console.log("In the middle:", inTheMiddle([1,100,50,-51,1,1]))

Javascript: Keep getting one off when looking for consecutive numbers in array

I am doing some coding practice and found some questions online.
I keep getting 1 integer lower than expected when looking to return the number of consecutive numbers inside an array.
function LongestConsecutive(arr) {
arr.sort((a,b) => {return a-b});
let highest = 0;
let counter = 0;
let prevNum;
arr.forEach((num,index,arr) => {
if (prevNum === undefined) {
prevNum = num
} else {
if (num + 1 == arr[index + 1]) {
counter += 1;
highest = Math.max(highest,counter)
} else {
counter = 0;
}
}
})
return highest;
}
for example, the input [5, 6, 1, 2, 8, 9, 7], should return 5 -- because when sorted, there are 5 consecutive numbers. I keep getting one lower than I should so for this example, I get 4. The only way to get the correct answer is when I return 'highest + 1', which obviously is avoiding the problem.
The first iteration will hit
if (prevNum === undefined) {
prevNum = num;
}
But isn’t that already the first consecutive number? So counter = 1; and highest = 1; should be here.
Next, you reset counter = 0; in an else case. Why? There’s at least one number that is consecutive, so reset it to 1 instead.
Then, you’re not really using prevNum for anything. if (prevNum === undefined) can be replaced by if (index === 1).
You then check if the current number (num) precedes the next number (arr[index + 1]), but you skip this check for the first index. How about checking if the current number succeeds the previous?
This code uses the above changes plus some code quality changes:
function longestConsecutive(arr) { // Non-constructor functions start with a lower-case letter
arr.sort((a, b) => a - b); // Use expression form
let highest = 0;
let counter = 0;
arr.forEach((num, index, arr) => {
if (index === 0) {
highest = 1;
counter = 1;
} else if (num - 1 === arr[index - 1]) { // Merge `else if`, use strict equal
counter += 1;
highest = Math.max(highest, counter);
} else {
counter = 1;
}
});
return highest;
}
Well, by the definition of consecutive, you'll always have 1 consecutive number. So you need to start the counter from 1.
I tried this code (its different than the one posted in the question) which gives the expected result. In addition, if there are two sets of consecutive numbers of same (and largest) length, both are printed,
var arr = [5, 6, 1, 2, 8, 9, 7, 99, 98];
arr.sort((a, b) => a - b);
var prevNum = arr[0];
var consecutiveNumbersArr = [prevNum];
// Map of consecutiveNumbersArr array as key and
// the array length as values
var arrMap = new Map();
for (let i = 1; i < arr.length; i++) {
let num = arr[i];
if (num === prevNum+1) {
prevNum = num;
consecutiveNumbersArr.push(num);
continue;
}
arrMap.set(consecutiveNumbersArr, consecutiveNumbersArr.length);
consecutiveNumbersArr = [];
consecutiveNumbersArr.push(num);
prevNum = num;
}
arrMap.set(consecutiveNumbersArr, consecutiveNumbersArr.length);
// the largest length of all the consecutive numbers array
var largest = 0;
for (let value of arrMap.values()) {
if (value > largest) {
largest = value;
}
}
// print the result - the largest consecutive array
for (let [key, value] of arrMap) {
if (value === largest) {
console.log("RESULT: " + key);
}
}
Can also be achieved with array:reduce
function longestRun(array) {
const { streak } = array
.sort((a, b) => a - b) // sort into ascending order
.reduce(({ count, streak }, current, index, arr) => {
if (current === arr[index - 1] + 1) {
count++; // increment if 1 more than previous
} else {
count = 1; // else reset to 1
}
return {count, streak: Math.max(streak, count)};
}, { count: 0, streak: 0 }); // initial value is 0,0 in case of empty array
return streak;
}
console.log(longestRun([])); // 0
console.log(longestRun([0])); // 1
console.log(longestRun([0, 1])); // 2
console.log(longestRun([0, 1, 0])); // 2
console.log(longestRun([0, 0, 0])); // 1
console.log(longestRun([2, 0, 1, 0, 3, 0])); // 4
If you are able to split arrays into subarrays via a condition, you can do it by splitting the array at non consecutive points.
const arr = [5, 6, 1, 2, 8, 9, 7, 11, 12, 13, 14]
// utility for splitting array at condition points
const splitBy = (arr, cond) => arr.reduce((a, cur, i, src) => {
if(cond(cur, i, src)){
a.push([])
}
a[a.length - 1].push(cur)
return a
}, [])
const consecutives = splitBy(
arr.sort((a, b) => a - b),
(cur, i, src) => cur !== src[i-1] + 1
).sort((a, b) => b.length - a.length)
// largest consecutive list will be the first array
console.log(consecutives[0].length)

Categories

Resources