Convert Number To String (Charset Supplied) JavaScript - javascript

Update:
The following code works perfectly until $char.to_text encounters an integer greater than 55,834,574,847.
alpha="abcdefghijklmnopqrstuvwxyz";
$char={
to_num:function(s,c){
var l=c.length,o={};
c.split('').forEach(function(a,i){
o[a]=i
});
return s.split('').reduce(function(r,a){
return r*l+o[a]
},0)
},
to_text:function(i,c){
var l=c.length,s='';
do{
s=c[i%l]+s; // i%l
i/=l;
i|=0
}while(i!==0);
return s
}
};
Here is a quick snip:
$char.to_num("military",alpha) => 98987733674
$char.to_text(98987733674,alpha) => "undefinedundefinedundefinedundefinedundefinedundefinedundefinedy"
Manually iterating the above code should generate a normal response, why does it yield this "undefined..." string, is it simply because it's a large number operation for JS?

This is a proposal with a rewritten hash function, which uses an object o as simplified indexOf and a simple loop for the return value.
The wanted function ihash uses a single do ... until loop. It uses the remainder of the value and the length as index of the given charcter set. The value is then divided by the lenght of the caracter set and the integer part is taken for the next iteration, if not equal zero.
function hash(s) {
var c = '0abcdefghijklmnopqrstuvwxyz',
l = c.length,
o = {};
c.split('').forEach(function (a, i) {
o[a] = i;
});
return s.split('').reduce(function (r, a) {
return r * l + o[a];
}, 0);
}
function ihash(i) {
var c = '0abcdefghijklmnopqrstuvwxyz',
l = c.length,
s = '';
do {
s = c[i % l] + s;
i = Math.floor(i / l);
} while (i !== 0);
return s;
}
document.write(hash('0') + '<br>'); // => 0
document.write(hash('a') + '<br>'); // => 1
document.write(hash('hi') + '<br>'); // => 225
document.write(hash('world') + '<br>'); // => 12531838
document.write(hash('freecode') + '<br>'); // => 69810159857
document.write(ihash(0) + '<br>'); // => '0'
document.write(ihash(1) + '<br>'); // => 'a'
document.write(ihash(225) + '<br>'); // => 'hi'
document.write(ihash(12531838) + '<br>'); // => 'world'
document.write(ihash(69810159857) + '<br>'); // => 'freecode'

Here is a pseudo code for getting the string back. It's similar to convert numbers of different base.
var txt = function(n, charset) {
var s =""
while (n > 0) {
var r = n % charset.length;
n = n / charset.length;
s += charset[r];
}
return s;
}

Related

Math operations from string using Javascript

I am trying to find a simple way to perform a set of javascript math operations without using eval() function. Example: 1+2x3x400+32/2+3 and it must follow the PEMDAS math principle. This is what I have, but it doesn't work exactly it should.
function mdas(equation) {
let operations = ["*", "/", "+", "-"];
for (let outerCount = 0; outerCount < operations.length; outerCount++) {
for (let innerCount = 0; innerCount < equation.length; ) {
if (equation[innerCount] == operations[outerCount]) {
let operationResult = runOperation(equation[innerCount - 1], operations[outerCount], equation[innerCount + 1]);
var leftSideOfEquation = equation.substr(0, equation.indexOf(innerCount - 1));
var rightSideOfEquation = equation.substr(equation.indexOf(innerCount), equation.length);
var rightSideOfEquation = rightSideOfEquation.replace(rightSideOfEquation[0],String(operationResult));
equation = leftSideOfEquation + rightSideOfEquation;
innerCount = 0;
}
else {
innerCount++;
}
}
}
return "Here is it: " + equation; //result of the equation
}
If you don't want to use a complete library like mathjs - and you don't want to tackle creating your own script which would involve: lexical analysis, tokenization, syntax analysis, recursive tree parsing, compiling and output...
the simplest banal suggestion: Function
const calc = s => Function(`return(${s})`)();
console.log( calc("1+2*3*400+32/2+3") ); // 2420
console.log( calc("-3*-2") ); // 6
console.log( calc("-3 * + 1") ); // -3
console.log( calc("-3 + -1") ); // -4
console.log( calc("2 * (3 + 1)") ); // 8
My take at a custom MDAS
Here I created a Regex to retrieve operands and operators, accounting for negative values: /(-?[\d.]+)([*\/+-])?/g.
Firstly we need to remove any whitespace from our string using str.replace(/ /g , "")
Using JavaScript's String.prototype.matchAll() we can get a 2D array with all the matches as [[fullMatch, operand, operator], [.. ] we can than further flatten it using Array.prototype.flat()
Having that flattened array, we can now filter it using Array.prototype.filter() to remove the fullMatch -es returned by the regular expression and remove the last undefined value.
Define a calc Object with the needed operation functions
Iterate over the MDAS groups */ and than +- as regular expressions /\/*/ and /+-/
Consume finally the array of matches until only one array key is left
let str = "-1+2 * 3*+400+-32 /2+3.1"; // 2386.1
str = str.replace(/ +/g, ""); // Remove all spaces!
// Get operands and operators as array.
// Remove full matches and undefined values.
const m = [...str.matchAll(/(-?[\d.]+)([*\/+-])?/g)].flat().filter((x, i) => x && i % 3);
const calc = {
"*": (a, b) => a * b,
"/": (a, b) => a / b,
"+": (a, b) => a + b,
"-": (a, b) => a - b,
};
// Iterate by MDAS groups order (first */ and than +-)
[/[*\/]/, /[+-]/].forEach(expr => {
for (let i = 0; i < m.length; i += 2) {
let [a, x, b] = [m[i], m[i + 1], m[i + 2]];
x = expr.exec(x);
if (!x) continue;
m[i] = calc[x.input](parseFloat(a), parseFloat(b)); // calculate and insert
m.splice(i + 1, 2); // remove operator and operand
i -= 2; // rewind loop
}
});
// Get the last standing result
console.log(m[0]); // 2386.1
It's a little hacky, but you can try something like this:
var eqs = [
'1+2*3*4+1+1+3',
'1+2*3*400+32/2+3',
'-5+2',
'3*-2',
];
for(var eq in eqs) { console.log(mdas(eqs[eq])); }
function mdas(equation) {
console.log(equation);
var failsafe = 100;
var num = '(((?<=[*+-])-|^-)?[0-9.]+)';
var reg = new RegExp(num + '([*/])' + num);
while(m = reg.exec(equation)) {
var n = (m[3] == "*") ? m[1]*m[4] : m[1]/m[4];
equation = equation.replace(m[0], n);
if(failsafe--<0) { return 'failsafe'; }
}
var reg = new RegExp(num + '([+-])' + num);
while(m = reg.exec(equation)) {
var n = (m[3] == "+") ? 1*m[1] + 1*m[4] : m[1]-m[4];
equation = equation.replace(m[0], n);
if(failsafe--<0) { return 'failsafe'; }
}
return equation;
}

the multiplication of a code between two numbers incorrectly outputting number

function showMultiples(num, numMultiples){
for(i=1; i<=numMultiples; i++){
var multiple = num + " x " + i + " = " + num * i;
}
return multiple;
}
console.log('showMultiples(2,8) returns: ' + showMultiples(2,8));
For this code, what the function should do is, by looking at num and numMultiples variable, it should give you the list of multiplication that is possible with the two numbers. Therefore the console should print out
2x1=2 2x2=4 2x3=6 2x4=8 2x5=10 2x6=12 2x7=14 2x8=16
However, this code prints out 2x8 = 16 any guess to why?
You're assigning the value to multiple then returning it in the end, when your loop has finished, meaning multiple will be 2x8. If you do a console.log(multiple) right under var multiple = num + " x " + i + " = " + num * i; you will see it print out correctly.
EDIT:
function showMultiples(num, numMultiples){
var result = [];
for(i=1; i<=numMultiples; i++){
result.push(num + " x " + i + " = " + num * i);
}
return result.join(' ');
}
Add results to an array and when the function completes, join the values inside the array and return the results.
You only have one print statement, and that is outside of the loop. If you want to print multiple times, you need to put the print statement inside of the loop, something like this:
function showMultiples(num, numMultiples) {
console.log(`showMultiples(${num}, ${numMultiples}) returns:`);
Array.from({length: numMultiples}, (v, k) => k + 1).
forEach(i => console.log(`${num}×${i} = ${num * i}`));
}
showMultiples(2, 8)
// showMultiples(2, 8) returns:
// 2×1 = 2
// 2×2 = 4
// 2×3 = 6
// 2×4 = 8
// 2×5 = 10
// 2×6 = 12
// 2×7 = 14
// 2×8 = 16
However, that is bad design. You shouldn't mix data transformation and I/O. It is much better to separate the two and build the data up first completely, then print it:
function showMultiples(num, numMultiples) {
return Array.from({length: numMultiples}, (v, k) => k + 1).
map(i => `${num}×${i} = ${num * i}`).
join(", ");
}
console.log(`showMultiples(2, 8) returns: ${showMultiples(2, 8)}`);
// showMultiples(2, 8) returns: 2×1 = 2, 2×2 = 4, 2×3 = 6, 2×4 = 8, 2×5 = 10, 2×6 = 12, 2×7 = 14, 2×8 = 16
This is much more idiomatic ECMAScript.

High order function with javascript trouble

I want to be able to run the func n times with this createIterator function.
var createIterator = function (func, n) {
getDouble = function (x) {
return x + x;
};
return getDouble * n;
};
I want to be able to do this with the code:
var getQuad = createIterator(getDouble, 2);
getQuad(2) //8
Here are the tests is needs to pass:
Test.describe("Iterator for 'getDouble' function", function() {
var getDouble = function (n) {
return n + n;
};
Test.it("Running the iterator for once", function() {
var doubleIterator = createIterator(getDouble, 1);
Test.assertEquals(doubleIterator(3), 6, "Returns double of 3 as 6");
Test.assertEquals(doubleIterator(5), 10, "Returns double of 5 as 10");
});
Test.it("Running the iterator twice", function() {
var getQuadruple = createIterator(getDouble, 2);
Test.assertEquals(getQuadruple(2), 8, "Returns quadruple of 2 as 8");
Test.assertEquals(getQuadruple(5), 20, "Returns quadruple of 5 as 20");
});
});
I have been at this for awhile and have not been able to figure this out. Any help would be awesome. Thanks!
You can write a simple repeat procedure that when applied to n, f, and x, will repeat the application of function f to argument x, n times.
// ES6
const repeat = n => f => x =>
n === 1 ? f(x) : repeat(n-1)(f)(f(x));
const getDouble = x => x * 2;
const double = repeat(1)(getDouble);
const quad = repeat(2)(getDouble);
See it work
console.log(double(3)); // 6
console.log(double(5)); // 10
console.log(quad(2)); // 8
console.log(quad(5)); // 20
Let step through the evaluation of one of the examples:
const double = repeat(1)(getDouble);
Because repeat has been applied to n and f here, it returns
x => 1 === 1 ? getDouble(x) : repeat(0)(getDouble)(getDouble(x))
Now, when we call
double(3);
Substitute 3 for x
1 === 1 ? getDouble(3) : repeat(0)(getDouble)(getDouble(3));
Because 1 === 1, the first part of the ternary expression is returned
getDouble(3); // 6
Recap:
double(3) === getDouble(3) === 6
Now let's see the same process for quad
const quad = repeat(2)(getDouble);
Because repeat has been applied to n and f here, it returns
x => 2 === 1 ? getDouble(x) : repeat(1)(getDouble)(getDouble(x))
Now, when we call
quad(2);
Substitue 2 for x
2 === 1 ? getDouble(2) : repeat(1)(getDouble)(getDouble(2));
Because 2 === 1 is false, the second part of the ternary expression is returned
repeat(1)(getDouble)(getDouble(2))
repeat(1)(getDouble)(4)
So we have to call repeat again, with n=1, f=getDouble, x=4, so
1 === 1 ? getDouble(4) : repeat(0)(getDouble)(getDouble(4))
Because 1 === 1, the first part of the ternary expression is returned
getDouble(4); // 8
Recap:
quad(2) === getDouble(4) === 8
If you need the ES5, here you go
// ES5
var repeat = function repeat(n) {
return function (f) {
return function (x) {
return n === 1 ? f(x) : repeat(n - 1)(f)(f(x));
};
};
};
var getDouble = function getDouble(x) {
return x * 2;
};
var double = repeat(1)(getDouble);
var quad = repeat(2)(getDouble);
console.log(double(3)); // 6
console.log(double(5)); // 10
console.log(quad(2)); // 8
console.log(quad(5)); // 20
Lastly,
If you want your original API, which I believe to be inferior, we can still implement that
// ES6
const createIterator = (f, n) => x =>
n === 1 ? f(x) : createIterator(f, n-1)(f(x));
const getDouble = x => x * 2;
const double = createIterator(getDouble, 1);
const quad = createIterator(getDouble, 2);
And here's the ES5
// ES5
var createIterator = function createIterator(f, n) {
return function (x) {
return n === 1 ? f(x) : createIterator(f, n - 1)(f(x));
};
};
var getDouble = function getDouble(x) {
return x * 2;
};
var double = createIterator(getDouble, 1);
var quad = createIterator(getDouble, 2);
Both implementations work identically
So why is repeat(n)(f)(x) better ?
Well, because the function is fully curried, you can partially apply it in meaningful ways.
const getDouble = x => x * 2;
const once = repeat(1);
const twice = repeat(2);
const thrice = repeat(3);
const quad = twice(getDouble);
quad(5); // 20
const annoyingAlert = thrice(x => {alert(x); return x;});
annoyingAlert('wake up !'); // displays 'wake up !' three times
Your function isn't as flexible because it takes the function, f, and the number of times, n, as a tuple. Getting around this would require manually currying your function or using a Function.prototype.bind hack.
Try rearranging variables. Note, getDouble would be undefined at var getQuad = createIterator(getDouble, 2); as getDouble is defined within createIterator
var createIterator = function (func, n) {
getDouble = function (x) {
return (x + x) * n;
};
return func || getDouble;
};
var getQuad = createIterator(null, 2);
console.log(getQuad(2)) //8
alternatively
var createIterator = function(func, n) {
getDouble = function(x) {
return x + x;
};
return func.bind(null, n * n) || (getDouble(n)) * n;
};
var getQuad = createIterator(function(x) {
return x + x;
}, 2);
console.log(getQuad(2))
I just want to run getDouble n times.
You could use a loop inside of createIterator , return accumulated value as variable within function returned from createIterator that can be multiplied by parameter passed to getQuad
var createIterator = function(func, n) {
getDouble = function(x) {
return x + x;
};
var curr = n, res = 0;
while (--curr) {
res += func && func(n) || getDouble(n);
}
return function(y) {
return res * y
}
};
var getQuad = createIterator(null, 2);
console.log(getQuad(5)) // 20
You can do it like this,
Create an empty array of n
Fill the array by the function(arguments)
Join the array by operator
Create a function constructor using the joined array and arguments
Use the new function inside another function to pass the arguments from outside, and then return this new function.
Code snippet of what I am saying is here (if your browser supports Array.prototype.fill)
var createIterator = function (func, n, operator) {
operator = operator || "+";
var inner = Function(["f", "a"], "return " + Array(n ).fill("f(a)").join(operator) + ";");
return function (arg) {
return inner(func, arg);
};
};
var c = createIterator(function (n) {return n + n;}, 2);
document.write(c(3) + "<br/>");
document.write(c(6) + "<br/>");
var d = createIterator(function (n) {return n + n;}, 3, "+");
document.write(d(3) + "<br/>");
document.write(d(6) + "<br/>");
otherwise use this snippet
var createIterator = function (func, n, operator) {
operator = operator || "+";
var array = [];
for (var i = 0; i < n; i += 1) {array[i] = "f(a)";}
var inner = Function(["f", "a"], "return " + array.join(operator) + ";");
return function (arg) {
return inner(func, arg);
};
};
var c = createIterator(function (n) {return n + n;}, 2);
document.write(c(3) + "<br/>");
document.write(c(6) + "<br/>");
var d = createIterator(function (n) {return n + n;}, 3, "+");
document.write(d(3) + "<br/>");
document.write(d(6) + "<br/>");

Check if given three line segments represented by coordinates of 4 element array can form a triangle

I need to check if given three line segments form a triangle. A line segment can be expressed as an array of 4 integers giving the end-points coordinates in the form [ x1, y1, x2, y2 ].
So I need to write a function that is given as input three line segments K, L and M and will return 1 if they form a triangle, 0 otherwise.
If the input parameters are outside the range of the algorithm supports I need to return -1.
Examples :
function trigTest(K, L, M)
var K=[2,3,6,9], L=[8,1,6,9], M=[8,1,2,3], X=[1,7,6,9]
trigTest(K, L, M) // -> 1
trigTest(L, K, M) // -> 1
trigTest(M, K, L) // -> 1
trigTest(L, L, M) // -> 0
trigTest(X, L, M) // -> 0
I actually have a solution but it's pretty cumbersome and I don't think it is the right way. First I calculate the distance of every line segment and then I use triangle inequalities to check if they can actually form a triangles base on their lengths.
function distance(line){
var x1 = line[0],
y1 = line[1],
x2 = line[2],
y2 = line[3];
return Math.sqrt(Math.pow((x2-x1),2) + Math.pow(y2-y1),2)
}
function trigTest(K,L,M){
var distanceK = distance(K), distanceL = distance(L), distanceM = distance(M);
if((distanceK + distanceL) > distanceM && (distanceK + distanceM) > distanceL && distanceL + distanceM > distanceK){
// algorithm here
}else{
return 0;
}
}
Update
Thanks to #antoniskamamis and #trincot I have made a similar solution if someone wants to stick with arrays instead of working with strings. Big shout out to them.
function trigTest(K, L, M) {
var points = [];
var k = dots(K), l = dots(L), m = dots(M);
if(ifDotsOnSameLineAreEqual(k) || ifDotsOnSameLineAreEqual(l) || ifDotsOnSameLineAreEqual(m)){
return false;
}else{
return points.concat(k,l,m).every(function(point, index, array){
return array.filter(function(i){ return ifTwoDotsAreEqual(i,point)}).length == 2;
})
}
}
function dots(line) {
var x1 = line[0],
y1 = line[1],
x2 = line[2],
y2 = line[3];
return [[x1,y1],[x2, y2]];
}
function ifTwoDotsAreEqual(x,y){
return x[0] == y[0] && x[1] == y[1];
}
function ifDotsOnSameLineAreEqual(line){
return ifTwoDotsAreEqual(line[0],line[1]);
}
you could use this approach
function trigTest(a,b,c){
var parts = [];
Array.prototype.slice.call(arguments).forEach(function(item){
parts.push(item.slice(0,2).join("|"));
parts.push(item.slice(2).join("|"));
})
return parts.every(function(item, index, array){
return array.filter( function(x){ return x == item}).length == 2;
})
}
What it does is:
runs through the list of arguments
Array.prototype.slice.call(arguments).forEach
seperates the arrays into points first two, last two as strings parts.push(item.slice(0,2).join(""));parts.push(item.slice(2).join(""));
given the array of points it checks that each point is present two times parts.every(function(item, index, array){ return array.filter( function(x){ return x == item}).length == 2; })
Using a 'one liner'
function trigTest(a,b,c){
var slice = Array.prototype.slice;
return slice.call(arguments).reduce(function(previous, current){
previous.push(current.slice(0,2).join("|"));
previous.push(current.slice(2).join("|"));
return previous;
}, [])
.every(function(item, index, array){
return array.filter( function(x){ return x == item; }).length == 2;
})
}
Checking for zero length lines
if we know that the inputs are not validated to be lines before we have to add a check if any of the given lines has start and end points the same (is a 0 length line or a point)
in this case our code will have to be like this
function trigTest(a,b,c){
var slice = Array.prototype.slice;
if(slice.call(arguments).some(isPoint)){
return false;
};
return slice.call(arguments).reduce(function(previous, current){
previous.push(current.slice(0,2).join("|"));
previous.push(current.slice(2).join("|"));
return previous;
}, [])
.every(function(item, index, array){
return array.filter( function(x){ return x == item; }).length == 2;
})
}
function isPoint(value){
return value[0] == value[2] && value[1] == value[3];
}
Based on your examples, the key criteria is that you have exactly two copies of three x,y coordinates, so rather than deal with this from a geometric or trigonometric standpoint, you may have an easier time dealing with this based on basic set-theory: to have a triangle formed from three points A, B, C, your line segments must follow the pattern [Ax, Ay, Bx, By], [Bx, By, Cx, Cy], [Cx, Cy, Ax, Ay].
These segments are not required to be in that order, such as [Bx, By, Ax, Ay] is also valid for the first term.
To check for a valid triangle, count repeated coordinates first to verify two repeats of three unique coordinates (this will also eliminate repeated line segments), then verify that each line segment is non-zero in length (not [Ax, Ay, Ax, Ay]). Those two checks will handle the first two requirements.
I don't know the boundary limits, so I cannot advise on how to test whether it is outside the bounds of the algorithm, but I suspect that will require checking the actual coordinate range, which is integer arithmetic.
This approach should be usable in any javascript engine, although your specific choice of javascript engine will determine the best way to implement it.
var getRandom = () => 1+ Math.floor( Math.random() * 3 ) ;
// get random line
var getLine = () =>
{
do
var l = {
'a' : {
'x' : getRandom(),
'y' : getRandom()
},
'b' : {
'x' : getRandom(),
'y' : getRandom()
}
};
// repeat until startPoint differ from endPoint
while ( l.a.x == l.b.x & l.a.y == l.b.y )
return l;
};
var match = (K, L, M) => {
// Tirangle consist of three points
// three lines -> six points
var p1 = K.a.x + "," + K.a.y,
p2 = K.b.x + "," + K.b.y,
p3 = L.a.x + "," + L.a.y,
p4 = L.b.x + "," + L.b.y,
p5 = M.a.x + "," + M.a.y,
p6 = M.b.x + "," + M.b.y;
// count frequency
var freq = {};
freq[p1] = freq[p1] + 1 || 1;
freq[p2] = freq[p2] + 1 || 1;
freq[p3] = freq[p3] + 1 || 1;
freq[p4] = freq[p4] + 1 || 1;
freq[p5] = freq[p5] + 1 || 1;
freq[p6] = freq[p6] + 1 || 1;
// result Array
var result = Array();
for ( point in freq ){
// if the point is common for two lines add to result array
freq[point] == 2 ? result.push( point ) : false;
}
return result;
}
var test = () => {
// Three random lines
var K = getLine(), L = getLine(), M = getLine();
// Test if three lines has three common points
if ( match(K, L, M).length == 3 ) {
printSvg(K,L,M);
return 1
} else {
return 0
}
}
// run when document ready
var app = () => {
// div#box needed to print svg with triangles
const box = document.getElementById('box');
// test random lines, repeat
for (x =0; x <= 1000; x++) {
t = test ();
}
}
// fire app() when document ready
document.onreadystatechange = ()=> document.readyState == "complete" ? app() : false;
// format legend html
var printWsp = (L) => "("+ L.a.x + ","+ L.a.y+") ("+L.b.x+","+L.b.y+")";
// append svg to div#box
var printSvg = (K, L, M) => {
var legend = '<div class="legend">K ' + printWsp(K) +"<br>L " + printWsp(L) +"<br>M "+ printWsp(M) + "</div>";
var svgStr = "<svg height='250' width='250'>";
svgStr += "<line x1="+K.a.x*60 +" y1="+K.a.y*60 +" x2="+K.b.x*60 +" y2="+K.b.y*60 +" style='stroke:rgb(255,0,0);stroke-width:2' />";
svgStr += "<line x1="+L.a.x*60 +" y1="+L.a.y*60 +" x2="+L.b.x*60 +" y2="+L.b.y*60 +" style='stroke:rgb(0,255,0);stroke-width:2' />";
svgStr += "<line x1="+M.a.x*60 +" y1="+M.a.y*60 +" x2="+M.b.x*60 +" y2="+M.b.y*60 +" style='stroke:rgb(255,0,255);stroke-width:2' />";
svgStr += "</svg> ";
box.insertAdjacentHTML('beforeend', legend);
box.insertAdjacentHTML('beforeend', svgStr);
}

How to reduce consecutive integers in an array to hyphenated range expressions?

In JavaScript, how can I convert a sequence of numbers in an array to a range of numbers? In other words, I want to express consecutive occurring integers (no gaps) as hyphenated ranges.
[2,3,4,5,10,18,19,20] would become [2-5,10,18-20]
[1,6,7,9,10,12] would become [1,6-7,9-10,12]
[3,5,99] would remain [3,5,99]
[5,6,7,8,9,10,11] would become [5-11]
Here is an algorithm that I made some time ago, originally written for C#, now I ported it to JavaScript:
function getRanges(array) {
var ranges = [], rstart, rend;
for (var i = 0; i < array.length; i++) {
rstart = array[i];
rend = rstart;
while (array[i + 1] - array[i] == 1) {
rend = array[i + 1]; // increment the index if the numbers sequential
i++;
}
ranges.push(rstart == rend ? rstart+'' : rstart + '-' + rend);
}
return ranges;
}
getRanges([2,3,4,5,10,18,19,20]);
// returns ["2-5", "10", "18-20"]
getRanges([1,2,3,5,7,9,10,11,12,14 ]);
// returns ["1-3", "5", "7", "9-12", "14"]
getRanges([1,2,3,4,5,6,7,8,9,10])
// returns ["1-10"]
Just having fun with solution from CMS :
function getRanges (array) {
for (var ranges = [], rend, i = 0; i < array.length;) {
ranges.push ((rend = array[i]) + ((function (rstart) {
while (++rend === array[++i]);
return --rend === rstart;
})(rend) ? '' : '-' + rend));
}
return ranges;
}
Very nice question: here's my attempt:
function ranges(numbers){
var sorted = numbers.sort(function(a,b){return a-b;});
var first = sorted.shift();
return sorted.reduce(function(ranges, num){
if(num - ranges[0][1] <= 1){
ranges[0][1] = num;
} else {
ranges.unshift([num,num]);
}
return ranges;
},[[first,first]]).map(function(ranges){
return ranges[0] === ranges[1] ?
ranges[0].toString() : ranges.join('-');
}).reverse();
}
Demo on JSFiddler
I needed TypeScript code today to solve this very problem -- many years after the OP -- and decided to try a version written in a style more functional than the other answers here. Of course, only the parameter and return type annotations distinguish this code from standard ES6 JavaScript.
function toRanges(values: number[],
separator = '\u2013'): string[] {
return values
.slice()
.sort((p, q) => p - q)
.reduce((acc, cur, idx, src) => {
if ((idx > 0) && ((cur - src[idx - 1]) === 1))
acc[acc.length - 1][1] = cur;
else acc.push([cur]);
return acc;
}, [])
.map(range => range.join(separator));
}
Note that slice is necessary because sort sorts in place and we can't change the original array.
Here's my take on this...
function getRanges(input) {
//setup the return value
var ret = [], ary, first, last;
//copy and sort
var ary = input.concat([]);
ary.sort(function(a,b){
return Number(a) - Number(b);
});
//iterate through the array
for (var i=0; i<ary.length; i++) {
//set the first and last value, to the current iteration
first = last = ary[i];
//while within the range, increment
while (ary[i+1] == last+1) {
last++;
i++;
}
//push the current set into the return value
ret.push(first == last ? first : first + "-" + last);
}
//return the response array.
return ret;
}
Using ES6, a solution is:
function display ( vector ) { // assume vector sorted in increasing order
// display e.g.vector [ 2,4,5,6,9,11,12,13,15 ] as "2;4-6;9;11-13;15"
const l = vector.length - 1; // last valid index of vector array
// map [ 2,4,5,6,9,11,12,13,15 ] into array of strings (quote ommitted)
// --> [ "2;", "4-", "-", "6;", "9;", "11-", "-", "13;", "15;" ]
vector = vector.map ( ( n, i, v ) => // n is current number at index i of vector v
i < l && v [ i + 1 ] - n === 1 ? // next number is adjacent ?
`${ i > 0 && n - v [ i - 1 ] === 1 ? "" : n }-` :
`${ n };`
);
return vector.join ( "" ). // concatenate all strings in vector array
replace ( /-+/g, "-" ). // replace multiple dashes by single dash
slice ( 0, -1 ); // remove trailing ;
}
If you want to add extra spaces for readability, just add extra calls to string.prototype.replace().
If the input vector is not sorted, you can add the following line right after the opening brace of the display() function:
vector.sort ( ( a, b ) => a - b ); // sort vector in place, in increasing order.
Note that this could be improved to avoid testing twice for integer adjacentness (adjacenthood? I'm not a native English speaker;-).
And of course, if you don't want a single string as output, split it with ";".
Rough outline of the process is as follows:
Create an empty array called ranges
For each value in sorted input array
If ranges is empty then insert the item {min: value, max: value}
Else if max of last item in ranges and the current value are consecutive then set max of last item in ranges = value
Else insert the item {min: value, max: value}
Format the ranges array as desired e.g. by combining min and max if same
The following code uses Array.reduce and simplifies the logic by combining step 2.1 and 2.3.
function arrayToRange(array) {
return array
.slice()
.sort(function(a, b) {
return a - b;
})
.reduce(function(ranges, value) {
var lastIndex = ranges.length - 1;
if (lastIndex === -1 || ranges[lastIndex].max !== value - 1) {
ranges.push({ min: value, max: value });
} else {
ranges[lastIndex].max = value;
}
return ranges;
}, [])
.map(function(range) {
return range.min !== range.max ? range.min + "-" + range.max : range.min.toString();
});
}
console.log(arrayToRange([2, 3, 4, 5, 10, 18, 19, 20]));
If you simply want a string that represents a range, then you'd find the mid-point of your sequence, and that becomes your middle value (10 in your example). You'd then grab the first item in the sequence, and the item that immediately preceded your mid-point, and build your first-sequence representation. You'd follow the same procedure to get your last item, and the item that immediately follows your mid-point, and build your last-sequence representation.
// Provide initial sequence
var sequence = [1,2,3,4,5,6,7,8,9,10];
// Find midpoint
var midpoint = Math.ceil(sequence.length/2);
// Build first sequence from midpoint
var firstSequence = sequence[0] + "-" + sequence[midpoint-2];
// Build second sequence from midpoint
var lastSequence = sequence[midpoint] + "-" + sequence[sequence.length-1];
// Place all new in array
var newArray = [firstSequence,midpoint,lastSequence];
alert(newArray.join(",")); // 1-4,5,6-10
Demo Online: http://jsbin.com/uvahi/edit
; For all cells of the array
;if current cell = prev cell + 1 -> range continues
;if current cell != prev cell + 1 -> range ended
int[] x = [2,3,4,5,10,18,19,20]
string output = '['+x[0]
bool range = false; --current range
for (int i = 1; i > x[].length; i++) {
if (x[i+1] = [x]+1) {
range = true;
} else { //not sequential
if range = true
output = output || '-'
else
output = output || ','
output.append(x[i]','||x[i+1])
range = false;
}
}
Something like that.
An adaptation of CMS's javascript solution for Cold Fusion
It does sort the list first so that 1,3,2,4,5,8,9,10 (or similar) properly converts to 1-5,8-10.
<cfscript>
function getRanges(nArr) {
arguments.nArr = listToArray(listSort(arguments.nArr,"numeric"));
var ranges = [];
var rstart = "";
var rend = "";
for (local.i = 1; i <= ArrayLen(arguments.nArr); i++) {
rstart = arguments.nArr[i];
rend = rstart;
while (i < ArrayLen(arguments.nArr) and (val(arguments.nArr[i + 1]) - val(arguments.nArr[i])) == 1) {
rend = val(arguments.nArr[i + 1]); // increment the index if the numbers sequential
i++;
}
ArrayAppend(ranges,rstart == rend ? rstart : rstart & '-' & rend);
}
return arraytolist(ranges);
}
</cfscript>
Tiny ES6 module for you guys. It accepts a function to determine when we must break the sequence (breakDetectorFunc param - default is the simple thing for integer sequence input).
NOTICE: since input is abstract - there's no auto-sorting before processing, so if your sequence isn't sorted - do it prior to calling this module
function defaultIntDetector(a, b){
return Math.abs(b - a) > 1;
}
/**
* #param {Array} valuesArray
* #param {Boolean} [allArraysResult=false] if true - [1,2,3,7] will return [[1,3], [7,7]]. Otherwise [[1.3], 7]
* #param {SequenceToIntervalsBreakDetector} [breakDetectorFunc] must return true if value1 and value2 can't be in one sequence (if we need a gap here)
* #return {Array}
*/
const sequenceToIntervals = function (valuesArray, allArraysResult, breakDetectorFunc) {
if (!breakDetectorFunc){
breakDetectorFunc = defaultIntDetector;
}
if (typeof(allArraysResult) === 'undefined'){
allArraysResult = false;
}
const intervals = [];
let from = 0, to;
if (valuesArray instanceof Array) {
const cnt = valuesArray.length;
for (let i = 0; i < cnt; i++) {
to = i;
if (i < cnt - 1) { // i is not last (to compare to next)
if (breakDetectorFunc(valuesArray[i], valuesArray[i + 1])) {
// break
appendLastResult();
}
}
}
appendLastResult();
} else {
throw new Error("input is not an Array");
}
function appendLastResult(){
if (isFinite(from) && isFinite(to)) {
const vFrom = valuesArray[from];
const vTo = valuesArray[to];
if (from === to) {
intervals.push(
allArraysResult
? [vFrom, vTo] // same values array item
: vFrom // just a value, no array
);
} else if (Math.abs(from - to) === 1) { // sibling items
if (allArraysResult) {
intervals.push([vFrom, vFrom]);
intervals.push([vTo, vTo]);
} else {
intervals.push(vFrom, vTo);
}
} else {
intervals.push([vFrom, vTo]); // true interval
}
from = to + 1;
}
}
return intervals;
};
module.exports = sequenceToIntervals;
/** #callback SequenceToIntervalsBreakDetector
#param value1
#param value2
#return bool
*/
first argument is the input sequence sorted array, second is a boolean flag controlling the output mode: if true - single item (outside the intervals) will be returned as arrays anyway: [1,7],[9,9],[10,10],[12,20], otherwise single items returned as they appear in the input array
for your sample input
[2,3,4,5,10,18,19,20]
it will return:
sequenceToIntervals([2,3,4,5,10,18,19,20], true) // [[2,5], [10,10], [18,20]]
sequenceToIntervals([2,3,4,5,10,18,19,20], false) // [[2,5], 10, [18,20]]
sequenceToIntervals([2,3,4,5,10,18,19,20]) // [[2,5], 10, [18,20]]
Here's a version in Coffeescript
getRanges = (array) ->
ranges = []
rstart
rend
i = 0
while i < array.length
rstart = array[i]
rend = rstart
while array[i + 1] - array[i] is 1
rend = array[i + 1] # increment the index if the numbers sequential
i = i + 1
if rstart == rend
ranges.push rstart + ''
else
ranges.push rstart + '-' + rend
i = i + 1
return ranges
I've written my own method that's dependent on Lo-Dash, but doesn't just give you back an array of ranges, rather, it just returns an array of range groups.
[1,2,3,4,6,8,10] becomes:
[[1,2,3,4],[6,8,10]]
http://jsfiddle.net/mberkom/ufVey/

Categories