I have an odd problem where I am receiving computer generated equations (as a string) where multiplications/divisions with zero or one and lone zeros are occasionally present. These equations are to be presented to a user in string form.
I know that I can remove these redundant parts of the equation by implementing a kind of parser, but I was curious as to whether a regular expression could be used to remove them.
I came up with the following before I finally gave up on my (quite limited) regex skills:
/([^\+\-]*(?:0|0\*|\*0){1,}[^\+\-]*)|(?:1\*|\*1)/g
It seems to work only if:
there are no non-zero numbers with a zero in them (ie. no 10's,20's,etc.)
there are no negations.
It also doesn't work well with parentheses. Unfortunately parentheses are quite common.
Note that removing the redundant portions stated above may result in redundant parentheses or "zero" parentheses (ie it could turn out like ()*x, which is equivalent to 0*x). The redundant parentheses are not as much of an issue, but I assume the "zero" parentheses could be removed by a second pass similar to the first looking for (). If either of these could be done in the same regex as the one that solves the problem I would be extremely impressed.
So I turn to you regex gurus of Stack Overflow. Can it be done?
Assumptions
The following can be assumed true about the stringified equations:
There are no divisions by zero, equations will not have any occurrence of [expr]/0or even expressions that evaluate to [expr]/0 such as [expr]/sin(0).
The only operators within the equations themselves are + - * and /.
Minus operator (-) includes both subtraction and negation, although negation is always surrounded by parentheses.
Any operation other than the above (sin,cos, pow, etc.) will appear as a function call. (no ^ %, etc.)
Sample Equation
"(0+(0/0.5+(0+1*cos(p)+0*0+0*sin(p))*cos(k)+(0+1*0+0*1+0*0)*(-sin(k))+(0+1*(-sin(p))/0.5+0*0+0*cos(p))*0)*x+(0+(0+1*cos(p)+0*0+0*sin(p))*sin(k)+(0+1*0+0*1+0*0)*cos(k)+(0+1*(-sin(p))/0.5+0*0+0*cos(p))*0)*y+(0+(0+1*cos(p)+0*0+0*sin(p))*0+(0+1*0+0*1+0*0)*0+(0+1*(-sin(p))/0.5+0*0+0*cos(p))*1)*z)"
Quite cluttered isn't it?
After leaving the comment I couldn't resist having a crack at it :)
You're biggest problem is nested parentheses. Regexes themselves are really bad at handling nested structures. This is a prime example of my mantra "Regular expressions are a tool, not a solution".
With regexes as your tool, you can apply kind of a "leaf-first" (or bottom-up) approach for this tree-structure, that's what I do in the first part while (sEq.match(...)) {...}. After that I can walk through the created array and do some simple text edits.
I've also added that 1*, *1 and /1 are deleted as they similarly don't affect the equation. You could probably expand this to make it smart enough to replace sin(0)/cos(0) with 0 and 1 respectively, then the solution would be even smaller in some cases.
(As mentioned in the comments of the code, this breaks if the equation contains stuff like 5.0*4 because JavaScript regexes don't have lookbehind so I'm trusting the \b word boundary to do that work for me. Simply adding logic that deletes unnecessary decimals would solve this though. Something like sEq = sEq.replace(/\.0+\b/g, ''); but I don't know if that's necessary for your use-case.) Edit: now fixed, 5.0*4 should remain in tact
This is not thoroughly tested though, feedback welcome.
var sEq = "(0+(0/0.5+(0+1*cos(p)+0*0+0*sin(p))*cos(k)+(0+1*0+0*1+0*0)*(-sin(k))+(0+1*(-sin(p))/0.5+0*0+0*cos(p))*0)*x+(0+(0+1*cos(p)+0*0+0*sin(p))*sin(k)+(0+1*0+0*1+0*0)*cos(k)+(0+1*(-sin(p))/0.5+0*0+0*cos(p))*0)*y+(0+(0+1*cos(p)+0*0+0*sin(p))*0+(0+1*0+0*1+0*0)*0+(0+1*(-sin(p))/0.5+0*0+0*cos(p))*1)*z)";
var aParts = [];
document.getElementById('output').value = sEq + '\n';
while (sEq.match(/\([^()]*\)/)) {
// while there are still "leafs", save them to aParts and replace with
// a reference to their index in aParts, making their parent a new
// "leaf" because it now doesn't contain the round brackets anymore
sEq = sEq.replace(/([a-z]*)\(([^()]*)\)/gi, function(m, f, a) {
var n = aParts.length;
aParts[n] = {
'found':m,
'funct':f,
'arith':a
};
return '[' + n + ']';
});
}
for (var i = 0; i < aParts.length; i++) {
// isolate divisions/multiplications
var dms = aParts[i]['arith'].split(/(?=[+-])/);
for (var j = 0; j < dms.length; j++) {
// if the isolated part is multiplied by or divided into 0, replace with 0
if (dms[j].match(/([^.]|^)\b0[*\/]|\*0(?!\.?0*[1-9])/)) {
dms[j] = dms[j].replace(/([+-]?).*/, '$1'+'0');
}
// remove /1, *1 and 1*
dms[j] = dms[j].replace(/[\/*]1\b(?!\.0*[1-9])(?:\.0*)?/g, '').replace(/([^.]|^)\b1\*/g, '$1');
}
// join back together, remove 0+, +0 and -0; 0- results in negation
aParts[i]['arith'] = dms.join('').replace(/[+-]0(?!\.?0*[1-9])(?:\.?0*)?/g, '').replace(/([^.]|^)\b0(?:\+|(-))/g, '$1$2');
// if after this the part contains just 0, perpetuate down to further eliminate
if (aParts[i]['funct']=='' && aParts[i]['arith']=='0') {
for (var j = i + 1; j < aParts.length; j++) {
if (aParts[j]['arith'].indexOf('[' + i + ']') != -1) {
aParts[j]['arith'] = aParts[j]['arith'].replace('[' + i + ']', '0');
break;
}
}
}
// add back parts previously simplified by replacing [n] with the content of aParts[n]
aParts[i]['arith'] = aParts[i]['arith'].replace(/\[(\d+)\]/g, function (m, n) {
return aParts[parseInt(n)]['funct'] + '(' + aParts[parseInt(n)]['arith'] + ')';
});
// This is just to show the progress of the algorithm
document.getElementById('parts').value += i + '\t' + aParts[i]['found'] + '\n';
var tmp = [];
for (var a = 0; a < aParts.length; a++) {
tmp[a] = {
'funct':aParts[a]['funct'],
'arith':aParts[a]['arith'].replace(/\[(\d+)\]/g, function (m, n) {
return tmp[parseInt(n)]['funct'] + '(' + tmp[parseInt(n)]['arith'] + ')';
})
};
}
// some steps didn't change after analysing, only append when significant
if (document.getElementById('output').value.indexOf('\n' + tmp[tmp.length-1]['arith'] + '\n') ==-1)
document.getElementById('output').value += tmp[tmp.length-1]['arith'] + '\n';
}
document.getElementById('solution').innerHTML =
aParts[aParts.length-1]['funct'] +
'(' + aParts[aParts.length-1]['arith'] + ')';
<h3>Parts isolated:</h3>
<textarea id="parts" rows="10" style="width:100%" wrap="off"></textarea>
<h3>Steps that simplified the equation:</h3>
<textarea id="output" rows="10" style="width:100%" wrap="off"></textarea>
<h3>Solution:</h3>
<pre id="solution"></pre>
I ended up implementing a completely non-regex, recursive approach to the problem. The cleanupEqn() function essentially splits each string by operators (top level parentheses are grouped as a single operand), recursively operates on each sub part, then does another run through on the way back up the function call chain.
Comparing this with funkwurm's regex solution in jsperf shows it is significantly faster (at least in my personal chrome and firefox browsers).
It hasn't been thoroughly tested yet, and I'm sure there could be improvements made so I welcome any feedback.
Stealing funkwurm's snippet display to show my solution:
var sEq = "(0+(0/0.5+(0+1*cos(p)+0*0+0*sin(p))*cos(k)+(0+1*0+0*1+0*0)*(-sin(k))+(0+1*(-sin(p))/0.5+0*0+0*cos(p))*0)*x+(0+(0+1*cos(p)+0*0+0*sin(p))*sin(k)+(0+1*0+0*1+0*0)*cos(k)+(0+1*(-sin(p))/0.5+0*0+0*cos(p))*0)*y+(0+(0+1*cos(p)+0*0+0*sin(p))*0+(0+1*0+0*1+0*0)*0+(0+1*(-sin(p))/0.5+0*0+0*cos(p))*1)*z)";
var operators = ['+','-','*','/'];
var level = 0;
var result = cleanupEqn(sEq);
document.getElementById('solution').innerHTML = result;
function cleanupEqn(eqn){
var parts = removeRedundant(splitByParen(eqn));
level++;
document.getElementById('output').value += 'Level ' + level + ': Processing ' + eqn + '\n';
for(var i=0; i < parts.length; i++){
document.getElementById('parts').value += parts[i] + '\n';
if(parts[i].charAt(0) === '('){
// Clean up the expression inside the parentheses
var tmp = cleanupEqn(parts[i].substring(1,parts[i].length-1));
// If it was reduced to a zero, don't add the parentheses back
if(tmp === '0'){
parts[i] = '0';
}
else {
parts[i] = '(' + tmp + ')';
}
}
}
// Finally, remove redundancies again, since some might have bubbled up.
removeRedundant(parts);
document.getElementById('output').value += 'Level ' + level + ': Completed ' + eqn + '\n' + JSON.stringify(parts, null, '\t') + '\n';
level--;
// Join it all into a string and return
return parts.join('');
}
function splitByParen(str){
var out = [];
var exprStart = 0;
var count = 0;
var i;
for (i = 0; i < str.length; i++) {
var t = str.charAt(i);
if(str.charAt(i) === '('){
if(i > exprStart && count === 0){
out.push(str.substring(exprStart, i));
exprStart = i;
}
count++;
}
else if(str.charAt(i) === ')'){
count--;
if(count === 0){
out.push(str.substring(exprStart, i+1));
exprStart = i+1;
}
}
else if(count === 0 && operators.indexOf(str.charAt(i)) > -1){
if(i > exprStart){
out.push(str.substring(exprStart, i));
}
out.push(str.charAt(i));
exprStart = i+1;
}
}
// Add the last part
if(i > exprStart){
out.push(str.substring(exprStart, i));
}
return out;
}
function removeRedundant(parts) {
for(var i=0; i < parts.length; i++){
if(parts[i] === '0'){
if(i === 0){
switch(parts[i+1]){
case '*':
case '/':
if(parts[i+1] === '*' || parts[i+1] === '/'){
parts.splice(i, 3, '0');
}
else {
parts.splice(i, 2);
}
i--;
break;
case '+':
parts.splice(i, 2);
i--;
break;
case '-':
parts.splice(i, 1);
i--;
}
}
else {
switch(parts[i-1]){
case '*':
if(parts[i+1] === '*' || parts[i+1] === '/'){
// Check if the prior portion is part of a function call
if(i > 2 && operators.indexOf(parts[i-3]) < 0){
// Check if the next portion is part of a function call (or undefined)
if(i+3 < parts.length && operators.indexOf(parts[i+3]) < 0){
parts.splice(i-3, 6, '0');
i -= 4;
}
else {
parts.splice(i-3, 5, '0');
i -= 4;
}
}
else {
parts.splice(i-2, 4, '0');
i -= 3;
}
}
else {
parts.splice(i-2, 3, '0');
i -= 3;
}
break;
case '+':
case '-':
if(parts[i+1] === '*' || parts[i+1] === '/'){
// Check if the next portion is part of a function call (or undefined)
if(i+3 < parts.length && operators.indexOf(parts[i+3]) < 0){
parts.splice(i, 4, '0');
}
else {
parts.splice(i, 3, '0');
}
i--;
}
else if(parts[i+1] === '+'){
parts.splice(i-1, 2);
i -= 2;
}
else {
parts.splice(i-1, 2);
i -= 2;
}
}
}
}
else if(parts[i] === '1'){
if(i === 0){
switch(parts[i+1]){
case '*':
parts.splice(i, 2);
i--;
break;
case '+':
case '-':
if(parts[i+1] === '*'){
parts.splice(i, 2);
i--;
}
}
}
switch(parts[i-1]){
case '*':
case '/':
if(parts[i+1] !== '/'){
parts.splice(i-1, 2);
i -= 2;
}
break;
case '+':
case '-':
if(parts[i+1] === '*'){
parts.splice(i, 2);
i--;
}
}
}
}
return parts;
}
<h3>Parts isolated:</h3>
<textarea id="parts" rows="10" style="width:100%" wrap="off"></textarea>
<h3>Steps that simplified the equation:</h3>
<textarea id="output" rows="10" style="width:100%" wrap="off"></textarea>
<h3>Solution:</h3>
<pre id="solution"></pre>
<script src="new.js"></script>
Related
Is there a way to summarize a replace function and make the code a bit cleaner? I haven't found a way to do this yet and cannot find an answer on here.
replaceFunction(string) {
this.string = (encodeURIComponent(string.toLowerCase()
.replace('/%[a-fA-F0-9]{2}/','-')
.replace('/-+/','-')
.replace('/-$/','')
.replace('/^-/','')
.replace('ä','ae')
.replace('ö','oe')
.replace('ü','ue')
.replace('Ä','ae')
.replace('Ö','oe')
.replace('Ü','ue')
.replace('_','-')
.replace('.','-')
.replace(/\s/g, '-')
.replace(/["']/g, '')
));
return string;
}
You could try something similar to this (which is just a start, not the full implementation). This approach would also make it unnecessary to iterate over the string several times.
// First insert the polyfill posted here: https://stackoverflow.com/a/4314050/10406502
let a = 'Häberle-+Hänsle';
for(let i = 0; i < a.length; i++) {
switch (a[i]) {
case '-':
if (i + 1 < a.length && a[i + 1] !== '+')
break;
a = a.splice(i + 1, 1, '');
i--;
break;
case 'ä':
a = a.splice(i, 1, 'ae');
i++;
break;
}
}
input
books.copies.[read_by.[p_id="65784"].page=5468].text.[paragraph="20"].letters
the idea is to split the string by dots but ignore those inside square brackets
so after splitting there should be an array
[
'books',
'copies',
'[read_by.[p_id="65784"].page=5468]',
'text',
'[paragraph="20"]',
'letters'
]
I already looked at this answer but it doesn't work with nested square brackets, which is what i need. Also I'm using javascript, so negative lookbehinds are not supported.
Help is much appreciated.
Edit 1: expand example
It isn't possible to do it with a regex in Javascript that isn't able to match nested structures. You need to use the good old method: a stack.
var text = 'books.copies.[read_by.[p_id="65784"].page=5468].text.[paragraph="20"].letters';
var item = '', result = [], stack = 0;
for (var i=0; i < text.length; i++) {
if ( text[i] == '.' && stack == 0 ) {
result.push(item);
item = '';
continue;
} else if ( text[i] == '[' ) {
stack++;
} else if ( text[i] == ']' ) {
stack--;
}
item += text[i];
}
result.push(item);
console.log(result);
You need to write a parser for this since a JavaScript regex does not support regex recursion, nor balanced constructs.
The point in these functions is that they keep a stack (level, openBrackets) of opening delimiters (in your case, it is [) and then check the stack state: if the stack is not emppty, the found . is considered inside the brackets, and is thus just appended to the current match. Else, when the stack is empty, the . found is considered outside of brackets, and is thus used to split on (the current value is appended to the output array (result, ret)).
function splitByDotsOutsideBrackets(string){
var openBrackets = 0, ret = [], i = 0;
while (i < string.length){
if (string.charAt(i) == '[')
openBrackets++;
else if (string.charAt(i) == ']')
openBrackets--;
else if (string.charAt(i) == "." && openBrackets == 0){
ret.push(string.substr(0, i));
string = string.substr(i + 1);
i = -1;
}
i++;
}
if (string != "") ret.push(string);
return ret;
}
var res = splitByDotsOutsideBrackets('books.copies.[read_by.[p_id="65784"].page=5468].text.[paragraph="20"].letters');
console.log(res);
Or another variation:
function splitOnDotsOutsideNestedBrackets(str) {
var result = [], start = 0, level = 0;
for (var i = 0; i < str.length; ++i) {
switch (str[i]) {
case '[':
++level;
break;
case ']':
if (level > 0)
--level;
break;
case '.':
if (level)
break;
if (start < i)
result.push(str.substr(start, i - start));
start = i + 1;
break;
}
}
if (start < i)
result.push(str.substr(start, i - start));
return result;
}
var s = 'books.copies.[read_by.[p_id="65784"].page=5468].text.[paragraph="20"].letters';
console.log(splitOnDotsOutsideNestedBrackets(s))
Adapted from one of my previous answers.
I'm trying to write a function to abbreviate words in a sentence, where there are 4 or more characters in a word. So "elephant-rides are really fun!" becomes "E6t-r3s are r4y fun!".
I managed to get up to where I abbreviated all the words, but I can't figure out three things.
My code requires that the string ends with a space. How to re-organise it so it doesn't have to end with a space for it to work?
My code doesn't work properly for words that are 3 or less characters - these are not to be abbreviated (I tried an else if statement after the if statement and it confused me)
It doesn't account for commas, semi-colons or other punctuation. Is there a way to do this without stuffing the if condition with a bunch of === statements?
Edit: I would also be interested in a non RegEx answer (although the ones that have been posted have been helpful) as I'm new to programming and am still trying to figure loops out.
function abbrv(str) {
var word=""
var newStr=""
var counter= 0
var oldCounter= 0
for (var i=0; i<str.length; i+=1){
counter+= 1
word+= str[i]
if(str[i]===" "||str[i]==="-"){
newStr += word[oldCounter]+(counter-(oldCounter+3)).toString()+word[counter-2]+str[i]
oldCounter= counter
}
}
console.log(newStr)
}
abbrv("Elephant-rides are really fun ");
You could use the \b regex to match words:
function abbrWord(word) {
if (word.length <= 3) return word; // This also filters out ", " or "-"
return word[0] +
(word.length - 2) +
word[word.length - 1];
}
function abbrv(str) {
return str.split(/\b/) // Create an array of words and word boundaries
.map(abbrWord) // for each item in the array, replace with abbr.
.join(""); // join items together to form a string
}
console.log(abbrv("Elephant-rides are really fun"))
Notes:
It's nice to have your word-abbreviation and sentence-splitting logic separated. This makes it easy to change one of the two without influencing the other
With regex, there are many ways to quickly search for certain patterns of characters. Look in to match and test as well.
You could look at each character and check for a non letter and reset the counter. If a letter is found, check the count and append if the count is zero.
function abbrv(str) {
var newStr = "",
count = 0,
i;
for (i = 0; i < str.length; i++) {
if (str[i] === " " || str[i] === "-") {
if (count > 0) {
newStr += count > 3 ? count - 2 : str[i - 2];
newStr += str[i - 1];
}
newStr += str[i];
count = 0;
continue;
}
if (count === 0) {
newStr += str[i];
}
count++;
}
if (count > 0) {
newStr += count > 3 ? count - 2 : str[i - 2];
newStr += str[i - 1];
}
return newStr;
}
console.log(abbrv("Elephant-rides are really funy"));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Or you could use a regular expression for replacing the words with the abbreviation.
function abbrv(str) {
return str.replace(/\w{4,}/g, function (s) {
var l = s.length;
return s[0] + (l - 2) + s[l - 1];
});
}
console.log(abbrv("Elephant-rides are really fun"));
.as-console-wrapper { max-height: 100% !important; top: 0; }
const input = 'Elephant-rides are really fun ';
const result = input.split(/\W+/).filter(x => x).map(x => x.length < 4 ? x : x[0] + (x.length - 2) + x[x.length-1]);
console.log(result);
Suppose I want to know whether a string contains 5 or more continuous consecutive numbers.
var a = "ac39270982"; // False
var a = "000223344998"; // False
var a = "512345jj7"; // True - it contains 12345
var a = "aa456780"; // True - it contains 45678
Is there a RegEx available to accomplish this? Would it also be able to work in the following situation?
var a = "5111213141587"; // True
This should be true because it contains 11,12,13,14,15.
I'm not sure if it is possible to check the provided examples (single-digit, double-digit numbers) as well as larger numbers (triple-digit, etc.).
I took the time to make a 100% Javascript approach to your question. I made it to simply parse each character in the string and do integer only comparison. This works not only for five consecutive integers, but it works for checking for tenths as well (10's, 20's, etc). You can also increase/decrease the number of comparisons if you wish.
A fair warning: despite this method being potentially scalable if coded to look for all kinds of numeric sizes, you'd still be bound by computing power and number of comparisons. That is why I only provided the code for single digits and tenths, I leave it open to you/the community to decide how to expand from here.
jsFiddle
If you happen to need more details about how it works then let me know, I can further clarify its inner workings.
var str = "1111122asdgas222*&^%121314151617bdjfjahdi234bdce56789";
var consecutive = 5; // Number of comparisons
// Single digits
alert("It is " + consecutiveDigits(str, consecutive) + " that " + str + " contains " + consecutive + " consecutive digits.");
// Tenths digits
alert("It is " + consecutiveDigits(str, consecutive) + " that " + str + " contains " + consecutive + " consecutive tenths.");
function consecutiveDigits(str, consecutive){
var curr,
prev,
count = 0;
for(var i = 0; i < str.length; ++i) {
curr = parseInt(str.split('')[i]);
if(isNumeric(curr)) {
if(count === 0){
++count;
}
else if(prev + 1 === curr){
++count;
if(count === consecutive){
return true;
}
}
prev = curr;
}
}
return false;
}
function consecutiveTenths(str, consecutive, iterations){
var curr,
prev,
curr_tenth = 0,
prev_tenth = 0,
count = 0,
count_tenth = 0;
for(var i = 0; i < str.length; ++i) {
curr = parseInt(str.split('')[i]);
if(isNumeric(curr)) {
++count;
if(count === iterations){
curr_digit = (prev * 10) + curr;
alert(count_digit + " " + curr_digit + " " + prev_tenth);
if(count_digit === 0){
++count_digit;
}
else if(curr_tenth === (prev_tenth + 1)){
++count_digit;
if(count_digit === consecutive){
return true;
}
}
prev_digit = curr_digit;
count = 0;
}
else {
prev = curr;
}
}
else {
count = 0;
}
}
}
function isNumeric(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
}
You can build regexp that will validate if it's true or not, but you might have a hard time retrieving the whole consecutive string. That said the RegExp will be a bit cumbersome, but you can create a function to create the regexp needed, depending on parameters wanted. See snippet:
function build_regexp(n) {
var string = "";
for (var i = 0; i <= 14 - n; i++) {
var start_num = i
for (var j = 0; j < n; j++) {
string += (start_num++).toString()
}
string += "|";
}
string = string.replace(/\|$/, '');
return string
}
document.getElementById('check').onclick = function() {
var regex = new RegExp(build_regexp(document.getElementById('cons').value), "g");
document.getElementById('regex').textContent = regex;
document.getElementById('result').innerHTML = (regex.exec(document.getElementById('to_check').value) || "false")
}
<div id="regex"></div>
<div>Enter wanted consecutive numbers: <input id="cons"></input></div>
<div>Enter string to check: <input id="to_check"></input></div>
<button id="check">check</button>
<div id="result"></div>
EDIT: Added a code snippet & fixed bug in numRegex
To answer the general case (i.e. contiguous sequence of arbitrary-length digits), you can do something like this:
http://jsfiddle.net/ksgLzL9u/8/
/* Find a sequence of n > 1 contiguously increasing integers in input
*
* If sequence is found, return an object:
* {
* start: <starting index of the sequence in input>,
* length: <length of the found sequence string>,
* first: <first number in the sequence>
* }
*
* Otherwise, return null
*/
function findSequence(input, n) {
var numRegex = /^(?:0|[1-9][0-9]*)$/;
// Try every starting position
for (var i = 0; i < input.length; ++i) {
// At the current starting position, try every length for the 1st number
for (var firstLen = 1; i + firstLen < input.length - 1; ++firstLen) {
var afterFirst = i + firstLen;
var first = input.slice(i, afterFirst);
// If the first string isn't an integer, move on
if (!numRegex.test(first)) {
continue;
}
// Convert the first string to an integer
var firstInt = parseInt(first, 10);
// Build what the rest of the string should look like following the
// first, in order to get a valid sequence
var rest = "";
for (var j = 1; j < n; ++j) {
rest = rest.concat(firstInt + j);
}
// Compare to what actually follows the starting string; if it
// matches, then we have our sequence; otherwise, continue on
if (input.slice(afterFirst, afterFirst + rest.length) === rest) {
return {
start: i,
length: first.length + rest.length,
first: first
};
}
}
}
return null;
}
$(function() {
function processChange() {
var input = $('#input').val();
var n = parseInt($('#n').val());
if (n > 1 && input.length) {
var result = findSequence(input, n);
if (result) {
$('#result').text(JSON.stringify(result, null, 2));
var afterFirst = result.start + result.first.length;
var afterSeq = result.start + result.length;
$('#highlighted').empty()
.append($('<span/>')
.text(input.slice(0, result.start)))
.append($('<span/>')
.addClass('sequence')
.append($('<span/>')
.addClass('first')
.text(result.first))
.append($('<span/>')
.text(input.slice(afterFirst, afterSeq))))
.append($('<span/>')
.text(input.slice(afterSeq)));
} else {
$('#result').text("No sequence found");
$('#highlighted').empty();
}
} else {
$('#result').text("");
$('#highlighted').empty();
}
}
$('input,n').on("keyup mouseup", processChange);
processChange();
});
#input {
width: 50%;
min-width: 200px;
}
#n {
width: 50px;
}
.highlighted-result {
font-family: monospace;
}
.highlighted-result .sequence {
background-color: yellow;
}
.highlighted-result .first {
border: solid black 1px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<h1>Input</h1>
<div>
<input id="input" type="text" value="111121314155" placeholder="input">
<input id="n" type="number" value="5" placeholder="n">
</div>
<h1>Results</h1>
<div id="highlighted" class="highlighted-result"></div>
<pre id="result"></pre>
I haven't attempted to optimize the solution (e.g. the firstLen iteration can be short-circuited, and the entire rest string doesn't need to be built up), but I left as-is to make the algorithm clearer.
function NstreamsOfNumberN (str) {
for (let i = 0; i < str.length; i++) {
let numBeingConsidered = Number(str[i]);
let numOfComparisonsToBeDone = numBeingConsidered - 1;
for (let j = i; j < numOfComparisonsToBeDone + i; j++) {
if (str[j] != str[j+1]) {break}//compare neigbourin nums
else if ((j - i + 1) === numOfComparisonsToBeDone)
{ let theNwithNstreams = numBeingConsidered
return [str, (theNwithNstreams), true]}
//(j - i + 1) equals num of comparisons that has been done.
}
}
return [str,null,false]
}
NstreamsOfNumberN('334775555583444582')
9 streams of the number 9
8 streams of the number 8
7 streams of the number 7 ...
3 streams of the number 3
2 streams of the number 2.
It is very difficult to do this with regex, but here is a tentative:
One digit
(?:0(?=1)|1(?=2)|2(?=3)|3(?=4)|4(?=5)|5(?=6)|6(?=7)|7(?=8)|8(?=9)){4,}\d
https://regex101.com/r/mw4bvG/1
Two digits
(?:(\d)(?:0(?=(?:\1)1)|1(?=(?:\1)2)|2(?=(?:\1)3)|3(?=(?:\1)4)|4(?=(?:\1)5)|5(?=(?:\1)6)|6(?=(?:\1)7)|7(?=(?:\1)8)|8(?=(?:\1)9))|09(?=10)|19(?=20)|29(?=30)|39(?=40)|49(?=50)|59(?=60)|69(?=70)|79(?=80)|89(?=90)){4,}\d{2}
https://regex101.com/r/Kcl9FC/1
Three digits
(?:(\d{2})(?:0(?=(?:\1)1)|1(?=(?:\1)2)|2(?=(?:\1)3)|3(?=(?:\1)4)|4(?=(?:\1)5)|5(?=(?:\1)6)|6(?=(?:\1)7)|7(?=(?:\1)8)|8(?=(?:\1)9))|(\d)(?:09(?=(?:\2)10)|19(?=(?:\2)20)|29(?=(?:\2)30)|39(?=(?:\2)40)|49(?=(?:\2)50)|59(?=(?:\2)60)|69(?=(?:\2)70)|79(?=(?:\2)80)|89(?=(?:\2)90))|099(?=100)|199(?=200)|299(?=300)|399(?=400)|499(?=500)|599(?=600)|699(?=700)|799(?=800)|899(?=900)){4,}\d{3}
https://regex101.com/r/joeWdR/1
All together
(?:0(?=1)|1(?=2)|2(?=3)|3(?=4)|4(?=5)|5(?=6)|6(?=7)|7(?=8)|8(?=9)){4,}\d|(?:(\d)(?:0(?=(?:\1)1)|1(?=(?:\1)2)|2(?=(?:\1)3)|3(?=(?:\1)4)|4(?=(?:\1)5)|5(?=(?:\1)6)|6(?=(?:\1)7)|7(?=(?:\1)8)|8(?=(?:\1)9))|09(?=10)|19(?=20)|29(?=30)|39(?=40)|49(?=50)|59(?=60)|69(?=70)|79(?=80)|89(?=90)){4,}\d{2}|(?:(\d{2})(?:0(?=(?:\2)1)|1(?=(?:\2)2)|2(?=(?:\2)3)|3(?=(?:\2)4)|4(?=(?:\2)5)|5(?=(?:\2)6)|6(?=(?:\2)7)|7(?=(?:\2)8)|8(?=(?:\2)9))|(\d)(?:09(?=(?:\3)10)|19(?=(?:\3)20)|29(?=(?:\3)30)|39(?=(?:\3)40)|49(?=(?:\3)50)|59(?=(?:\3)60)|69(?=(?:\3)70)|79(?=(?:\3)80)|89(?=(?:\3)90))|099(?=100)|199(?=200)|299(?=300)|399(?=400)|499(?=500)|599(?=600)|699(?=700)|799(?=800)|899(?=900)){4,}\d{3}
https://regex101.com/r/NyCLh6/1
I would like to have a function which takes 3 arguments:
sentence (string),
maxCharLen=20 (number),
separator (string)
and transform the sentence based on the parameters.
Example
var sentence = "JavaScript is a prototype-based scripting language that is dynamic, weakly typed and has first-class functions."
var newSentence = breakSentence(sentence, maxCharLen=20, separator="<br>");
newSentence // JavaScript is a prototype-based <br> scripting language that is dynamic, <br> weakly typed and has first-class functions.
P.S:
This is what I have tried:
var breakSentence = function (sentence, maxCharLen, separator)
{
sentence = sentence || "javascript is a language" ;
maxCharLen = 10 || maxCharLen; // max numb of chars for line
separator = "<br>" || separator;
var offset;
var nbBreak = sentence.length // maxCharLen;
var newSentence = "";
for (var c = 0; c < nbBreak; c += 1)
{
offset = c * maxCharLen;
newSentence += sentence.substring(offset, offset + maxCharLen) + separator;
}
return newSentence;
}
It works in this way:
breakSentence() // "javascript<br> is a lang<br>uage<br>"
it should be:
breakSentence() // "javascript<br>is a <br>language"
Here's a solution: http://snippets.dzone.com/posts/show/869
//+ Jonas Raoni Soares Silva
//# http://jsfromhell.com/string/wordwrap [v1.0]
String.prototype.wordWrap = function(m, b, c){
var i, j, l, s, r;
if(m < 1)
return this;
for(i = -1, l = (r = this.split("\n")).length; ++i < l; r[i] += s)
for(s = r[i], r[i] = ""; s.length > m; r[i] += s.slice(0, j) + ((s = s.slice(j)).length ? b : ""))
j = c == 2 || (j = s.slice(0, m + 1).match(/\S*(\s)?$/))[1] ? m : j.input.length - j[0].length
|| c == 1 && m || j.input.length + (j = s.slice(m).match(/^\S*/)).input.length;
return r.join("\n");
};
usage:
var sentence = "JavaScript is a prototype-based scripting language that is dynamic, weakly typed and has first-class functions."
sentence.wordWrap(20, "<br>",true)
// Output "JavaScript is a <br>prototype-based <br>scripting language <br>that is dynamic, <br>weakly typed and has<br> first-class <br>functions."
I would try it like that (not tested):
var breakSentence = function (sentence, maxCharLen, separator)
{
var result = "";
var index = 0;
while (sentence.length - index > maxCharLen)
{
var spaceIndex = sentence.substring(index, index + maxCharLen).lastIndexOf(' ');
if (spaceIndex < 0) //no spaces
{
alert('Don\'t know what do do with substring with one long word');
spaceIndex = maxCharLen; //assume you want to break anyway to avoid infinite loop
}
result += sentence.substring(index, index + spaceIndex + 1) + separator;
index += spaceIndex + 1;
}
return result;
}
Should break after spaces only now...
Here is my attempt to get it. It has two things you should notice:
it first removes all the separator instances (so the reordering is completely new)
it doesn't break words longer then maxCharLen characters.
It worked in node 0.6.10
var breakSentence = function (sentence, maxCharLen, separator) {
var words = [] // array of words
, i // iterator
, len // loop
, current = '' // current line
, lines = [] // lines split
;
sentence = sentence || "javascript is a language";
maxCharLen = 10 || maxCharLen;
separator = separator || "<br>";
sentence = sentence.replace(separator, '');
if (sentence.length < maxCharLen) return [sentence]; // no need to work if we're already within limits
words = sentence.split(' ');
for (i = 0, len = words.length; i < len; i += 1) {
// lets see how we add the next word. if the current line is blank, just add it and move on.
if (current == '') {
current += words[i];
// if it's not like that, we need to see if the next word fits the current line
} else if (current.length + words[i].length <= maxCharLen) { // if the equal part takes the space into consideration
current += ' ' + words[i];
// if the next word doesn't fit, start on the next line.
} else {
lines.push(current);
current = words[i];
// have to check if this is the last word
if (i === len -1) {
lines.push(current);
}
}
}
// now assemble the result and return it.
sentence = '';
for (i = 0, len = lines.length; i < len; i += 1) {
sentence += lines[i];
// add separator if not the last line
if (i < len -1) {
sentence += separator;
}
}
return sentence;
}