Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed last month.
Improve this question
lets say the input is
let word = 'I Lo1ve Co4ding'
the output would be I Loove Coooooding
so repeating x letters after putting a number after
Still don't understand how it works and how to replace it mid string.
You can use the callback function argument in String.prototype.replace, and call String.prototype.repeat on the first matching group (letter) and pass the second matching group (number) plus 1.
const expandStr = (str) =>
str.replace(/([a-z])(\d+)/gi, (g, g1, g2) => g1.repeat(+g2 + 1));
console.log(expandStr('I Lo1ve Co4ding'));
Caveat
As suggested in the comments, you may use the following:
/(\p{L})(\p{N}+)/gu
In place of:
/([a-z])(\d+)/gi
Explanation:
\p{L} β matches a single code point in the category "letter"
\p{N} β matches any kind of numeric character in any script
\p{S} β symbols e.g. emoji
const expandStr = (str) =>
str.replace(/(\p{L}|\p{S})(\p{N}+)/gu, (g, g1, g2) => g1.repeat(+g2 + 1));
console.log(expandStr('I Lo1ve Co4ding'));
console.log(expandStr('I give π½οΈ I saw π1!')); // Works with emoji
Please refer to "Unicode Categories" to learn more.
Alternative pattern: /([^(\p{N}|\s)])(\p{N}+)/gu
Tokenizing
Here is a more traditional example that incorporates loops. It does not use regular expressions, but follows a tokenizer (parser) approach.
Note: Keep in mind that this naΓ―ve example does not account for Unicode.
const
NUMBER_RANGE_START = '0' .charCodeAt(0), // 48
NUMBER_RANGE_END = '9' .charCodeAt(0), // 57
LETTER_UPPER_RANGE_START = 'A' .charCodeAt(0), // 65
LETTER_UPPER_RANGE_END = 'Z' .charCodeAt(0), // 90
LETTER_LOWER_RANGE_START = 'a' .charCodeAt(0), // 97
LETTER_LOWER_RANGE_END = 'z' .charCodeAt(0), // 122
WHITESPACE_CARRIAGE_RETURN = '\r'.charCodeAt(0), // 13
WHITESPACE_LINE_FEED = '\n'.charCodeAt(0), // 10
WHITESPACE_SPACE = ' ' .charCodeAt(0), // 32
WHITESPACE_TAB = '\t'.charCodeAt(0); // 9
const codeInRange = (code, start, end) => code >= start && code <= end;
const charInRange = (char, start, end) => codeInRange(char.charCodeAt(0), start, end);
const isNumber = (char) =>
charInRange(char, NUMBER_RANGE_START, NUMBER_RANGE_END);
const isUpperLetter = (char) =>
charInRange(char, LETTER_UPPER_RANGE_START, LETTER_UPPER_RANGE_END);
const isLowerLetter = (char) =>
charInRange(char, LETTER_LOWER_RANGE_START, LETTER_LOWER_RANGE_END);
const isLetter = (char) => isLowerLetter(char) || isUpperLetter(char);
const isWhiteSpace = (char) => {
switch (char.charCodeAt(0)) {
case WHITESPACE_CARRIAGE_RETURN:
case WHITESPACE_LINE_FEED:
case WHITESPACE_SPACE:
case WHITESPACE_TAB:
return true;
default:
return false;
}
};
const expandStr = (str) => {
const result = [];
let index, char, prevChar, count, step;
for (index = 0; index < str.length; index++) {
char = str[index];
if (
isNumber(char) &&
(
isLetter(prevChar) ||
(
!isWhiteSpace(prevChar) &&
!isNumber(prevChar)
)
)
) {
count = parseInt(char, 10);
for (step = 0; step < count; step++) {
result.push(prevChar);
}
} else {
result.push(char);
}
prevChar = char;
}
return result.join('');
};
console.log(expandStr('I Lo1ve Co4ding')); // I Loove Coooooding
A little bit longer way as from Mr.Polywhirl. But I think foreach loops make for good readability and you will see how it works.
const word = 'I Lo1ve Co4ding'
function rewrite(w) {
const arr = [...w];
let n = [];
arr.forEach((s,i) => {
if(! isNaN(s) && s != ' ') {
n.push(arr[i-1].repeat(s));
} else {
n.push(s);
}
});
return n.join('');
}
console.log( rewrite(word) );
Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 4 years ago.
Improve this question
I was working on this practice problem, and solved it, but I want a more elegant way of writing this code:
// Usually when you buy something, you're asked whether your credit card number, phone number or answer to your most secret question is still correct. However, since someone could look over your shoulder, you don't want that shown on your screen. Instead, we mask it.
// Your task is to write a function maskify, which changes all but the last four characters into '#'.
const maskify = (cc) => {
let ccArray = Array.from(cc);
let length = cc.length;
let lastFour = cc.slice(-4);
let newArray = [];
if (length <= 4) {
return cc;
} else if (length > 4) {
let index = length - 4;
ccArray.splice(index, 4);
ccArray.forEach(n => {
newArray.push('#');
return newArray;
});
return newArray.concat(lastFour).join('');
}
}
console.log(maskify('4556364607935616'));
// ############5616
console.log(maskify('1'));
// 1
console.log(maskify('11111'));
// #1111
There are many different approaches:
function maskify(cc) {
return "#".repeat(Math.max(0, cc.length-4)) + cc.slice(-4);
}
function maskify(cc) {
return Array.from(cc, (char, index) =>
index < cc.length - 4 ? "#" : char
).join('')
}
function maskify(cc) {
return cc.replace(/.(?=.{4})/g, "#");
}
This might be among the easiest ways to achieve that goal:
const maskify = (cc) => {
return ("################################"+cc.slice(-4)).slice(-cc.length);
}
console.log(maskify('4556364607935616'));
console.log(maskify('1'));
console.log(maskify('11111'));
Just make sure the "################################" is long enough to cover all your use-cases.
To make it dynamic and work for any length of a string, it gets only slightly more complicated:
const maskify = (cc) => {
return ([...cc].map(x=>'#').join('')+cc.slice(-4)).slice(-cc.length);
}
console.log(maskify('4556364607935616'));
console.log(maskify('1'));
console.log(maskify('11111'));
You can also use a regular expression:
const maskify = (cc) => {
return (cc.replace(/./g,'#')+cc.slice(-4)).slice(-cc.length);
}
console.log(maskify('4556364607935616'));
console.log(maskify('1'));
console.log(maskify('11111'));
Generally speaking, I think your code is fine. Since "more elegant" is a little nebulous, I'll concern myself with 1) time complexity and 2) memory complexity
1) Time Complexity
Generally, you're fine here. No extra loops, and no intensive operations. There is a minor improvement to remove the final .join since that will have to loop over the final array.
2) Memory complexity
Some room for improvement here. Currently, the function creates a bunch of extra arrays. This likely won't be an issue for something as small as a credit card number, but could bite you if you applied a similar logic in a different situation.
Annotated original:
const maskify = (cc) => {
let ccArray = Array.from(cc); // Array 1
let length = cc.length;
let lastFour = cc.slice(-4); // Array 2
let newArray = []; // Array 3
if (length <= 4) {
return cc;
} else if (length > 4) {
let index = length - 4;
ccArray.splice(index, 4);
// Loop 1
ccArray.forEach(n => {
newArray.push('#');
return newArray;
});
// Loop 2
return newArray.concat(lastFour).join(''); // Array 4
}
}
Improved version:
const maskify = (cc) => {
let ccArray = Array.from(cc); // Array 1
let length = cc.length;
// Loop 1
let resultString = ccArray.reduce((result, original, index) => {
result += (index >= length - 4 ? original : '#');
}, "");
return resultString;
}
Array Reduce Documentation
As others have noted, there are many other ways to write this method. Since the question was specifically about how the existing code could be better, I tried to focus this answer on that.
Here's a way you can do it using destructuring assignment and single ternary expression
const maskify = ([ a = '', b = '', c = '', d = '', ...rest ]) =>
rest.length === 0
? a + b + c + d
: '#' + maskify ([ b, c, d, ...rest ])
console.log(maskify('4556364607935616'));
// ############5616
console.log(maskify('1'));
// 1
console.log(maskify('11111'));
// #1111
Destructuring creates intermediate values which can be avoided by using a secondary parameter, i, which represents the index of the current computation
const maskify = (str, i = 0) =>
4 + i >= str.length
? str.substr (i)
: '#' + maskify (str, i + 1)
console.log(maskify('4556364607935616'));
// ############5616
console.log(maskify('1'));
// 1
console.log(maskify('11111'));
// #1111
Now it's easy to make 4 an argument to our function as well
const maskify = (str, n = 4, i = 0) =>
n + i >= str.length
? str.substr (i)
: '#' + maskify (str, n, i + 1)
console.log(maskify('4556364607935616'));
// ############5616
console.log(maskify('4556364607935616', 6));
// ##########935616
console.log(maskify('1'));
// 1
console.log(maskify('11111', 10));
// 11111
The typical way to loop x times in JavaScript is:
for (var i = 0; i < x; i++)
doStuff(i);
But I don't want to use the ++ operator or have any mutable variables at all. So is there a way, in ES6, to loop x times another way? I love Ruby's mechanism:
x.times do |i|
do_stuff(i)
end
Anything similar in JavaScript/ES6? I could kind of cheat and make my own generator:
function* times(x) {
for (var i = 0; i < x; i++)
yield i;
}
for (var i of times(5)) {
console.log(i);
}
Of course I'm still using i++. At least it's out of sight :), but I'm hoping there's a better mechanism in ES6.
Using the ES2015 Spread operator:
[...Array(n)].map()
const res = [...Array(10)].map((_, i) => {
return i * 10;
});
// as a one liner
const res = [...Array(10)].map((_, i) => i * 10);
Or if you don't need the result:
[...Array(10)].forEach((_, i) => {
console.log(i);
});
// as a one liner
[...Array(10)].forEach((_, i) => console.log(i));
Or using the ES2015 Array.from operator:
Array.from(...)
const res = Array.from(Array(10)).map((_, i) => {
return i * 10;
});
// as a one liner
const res = Array.from(Array(10)).map((_, i) => i * 10);
Note that if you just need a string repeated you can use String.prototype.repeat.
console.log("0".repeat(10))
// 0000000000
OK!
The code below is written using ES6 syntaxes but could just as easily be written in ES5 or even less. ES6 is not a requirement to create a "mechanism to loop x times"
If you don't need the iterator in the callback, this is the most simple implementation
const times = x => f => {
if (x > 0) {
f()
times (x - 1) (f)
}
}
// use it
times (3) (() => console.log('hi'))
// or define intermediate functions for reuse
let twice = times (2)
// twice the power !
twice (() => console.log('double vision'))
If you do need the iterator, you can use a named inner function with a counter parameter to iterate for you
const times = n => f => {
let iter = i => {
if (i === n) return
f (i)
iter (i + 1)
}
return iter (0)
}
times (3) (i => console.log(i, 'hi'))
Stop reading here if you don't like learning more things ...
But something should feel off about those...
single branch if statements are ugly β what happens on the other branch ?
multiple statements/expressions in the function bodies β are procedure concerns being mixed ?
implicitly returned undefined β indication of impure, side-effecting function
"Isn't there a better way ?"
There is. Let's first revisit our initial implementation
// times :: Int -> (void -> void) -> void
const times = x => f => {
if (x > 0) {
f() // has to be side-effecting function
times (x - 1) (f)
}
}
Sure, it's simple, but notice how we just call f() and don't do anything with it. This really limits the type of function we can repeat multiple times. Even if we have the iterator available, f(i) isn't much more versatile.
What if we start with a better kind of function repetition procedure ? Maybe something that makes better use of input and output.
Generic function repetition
// repeat :: forall a. Int -> (a -> a) -> a -> a
const repeat = n => f => x => {
if (n > 0)
return repeat (n - 1) (f) (f (x))
else
return x
}
// power :: Int -> Int -> Int
const power = base => exp => {
// repeat <exp> times, <base> * <x>, starting with 1
return repeat (exp) (x => base * x) (1)
}
console.log(power (2) (8))
// => 256
Above, we defined a generic repeat function which takes an additional input which is used to start the repeated application of a single function.
// repeat 3 times, the function f, starting with x ...
var result = repeat (3) (f) (x)
// is the same as ...
var result = f(f(f(x)))
Implementing times with repeat
Well this is easy now; almost all of the work is already done.
// repeat :: forall a. Int -> (a -> a) -> a -> a
const repeat = n => f => x => {
if (n > 0)
return repeat (n - 1) (f) (f (x))
else
return x
}
// times :: Int -> (Int -> Int) -> Int
const times = n=> f=>
repeat (n) (i => (f(i), i + 1)) (0)
// use it
times (3) (i => console.log(i, 'hi'))
Since our function takes i as an input and returns i + 1, this effectively works as our iterator which we pass to f each time.
We've fixed our bullet list of issues too
No more ugly single branch if statements
Single-expression bodies indicate nicely separated concerns
No more useless, implicitly returned undefined
JavaScript comma operator, the
In case you're having trouble seeing how the last example is working, it depends on your awareness of one of JavaScript's oldest battle axes; the comma operator β in short, it evaluates expressions from left to right and returns the value of the last evaluated expression
(expr1 :: a, expr2 :: b, expr3 :: c) :: c
In our above example, I'm using
(i => (f(i), i + 1))
which is just a succinct way of writing
(i => { f(i); return i + 1 })
Tail Call Optimisation
As sexy as the recursive implementations are, at this point it would be irresponsible for me to recommend them given that no JavaScript VM I can think of supports proper tail call elimination β babel used to transpile it, but it's been in "broken; will reimplement" status for well over a year.
repeat (1e6) (someFunc) (x)
// => RangeError: Maximum call stack size exceeded
As such, we should revisit our implementation of repeat to make it stack-safe.
The code below does use mutable variables n and x but note that all mutations are localized to the repeat function β no state changes (mutations) are visible from outside of the function
// repeat :: Int -> (a -> a) -> (a -> a)
const repeat = n => f => x =>
{
let m = 0, acc = x
while (m < n)
(m = m + 1, acc = f (acc))
return acc
}
// inc :: Int -> Int
const inc = x =>
x + 1
console.log (repeat (1e8) (inc) (0))
// 100000000
This is going to have a lot of you saying "but that's not functional !" β I know, just relax. We can implement a Clojure-style loop/recur interface for constant-space looping using pure expressions; none of that while stuff.
Here we abstract while away with our loop function β it looks for a special recur type to keep the loop running. When a non-recur type is encountered, the loop is finished and the result of the computation is returned
const recur = (...args) =>
({ type: recur, args })
const loop = f =>
{
let acc = f ()
while (acc.type === recur)
acc = f (...acc.args)
return acc
}
const repeat = $n => f => x =>
loop ((n = $n, acc = x) =>
n === 0
? acc
: recur (n - 1, f (acc)))
const inc = x =>
x + 1
const fibonacci = $n =>
loop ((n = $n, a = 0, b = 1) =>
n === 0
? a
: recur (n - 1, b, a + b))
console.log (repeat (1e7) (inc) (0)) // 10000000
console.log (fibonacci (100)) // 354224848179262000000
for (let i of Array(100).keys()) {
console.log(i)
}
Here is another good alternative:
Array.from({ length: 3}).map(...);
Preferably, as #Dave Morse pointed out in the comments, you can also get rid of the map call, by using the second parameter of the Array.from function like so:
Array.from({ length: 3 }, () => (...))
I think the best solution is to use let:
for (let i=0; i<100; i++) β¦
That will create a new (mutable) i variable for each body evaluation and assures that the i is only changed in the increment expression in that loop syntax, not from anywhere else.
I could kind of cheat and make my own generator. At least i++ is out of sight :)
That should be enough, imo. Even in pure languages, all operations (or at least, their interpreters) are built from primitives that use mutation. As long as it is properly scoped, I cannot see what is wrong with that.
You should be fine with
function* times(n) {
for (let i = 0; i < n; i++)
yield i;
}
for (const i of times(5)) {
console.log(i);
}
But I don't want to use the ++ operator or have any mutable variables at all.
Then your only choice is to use recursion. You can define that generator function without a mutable i as well:
function* range(i, n) {
if (i >= n) return;
yield i;
return yield* range(i+1, n);
}
times = (n) => range(0, n);
But that seems overkill to me and might have performance problems (as tail call elimination is not available for return yield*).
I think it is pretty simple:
[...Array(3).keys()]
or
Array(3).fill()
const times = 4;
new Array(times).fill().map(() => console.log('test'));
This snippet will console.log test 4 times.
Answer: 09 December 2015
Personally, I found the accepted answer both concise (good) and terse (bad). Appreciate this statement might be subjective, so please read this answer and see if you agree or disagree
The example given in the question was something like Ruby's:
x.times do |i|
do_stuff(i)
end
Expressing this in JS using below would permit:
times(x)(doStuff(i));
Here is the code:
let times = (n) => {
return (f) => {
Array(n).fill().map((_, i) => f(i));
};
};
That's it!
Simple example usage:
let cheer = () => console.log('Hip hip hooray!');
times(3)(cheer);
//Hip hip hooray!
//Hip hip hooray!
//Hip hip hooray!
Alternatively, following the examples of the accepted answer:
let doStuff = (i) => console.log(i, ' hi'),
once = times(1),
twice = times(2),
thrice = times(3);
once(doStuff);
//0 ' hi'
twice(doStuff);
//0 ' hi'
//1 ' hi'
thrice(doStuff);
//0 ' hi'
//1 ' hi'
//2 ' hi'
Side note - Defining a range function
A similar / related question, that uses fundamentally very similar code constructs, might be is there a convenient Range function in (core) JavaScript, something similar to underscore's range function.
Create an array with n numbers, starting from x
Underscore
_.range(x, x + n)
ES2015
Couple of alternatives:
Array(n).fill().map((_, i) => x + i)
Array.from(Array(n), (_, i) => x + i)
Demo using n = 10, x = 1:
> Array(10).fill().map((_, i) => i + 1)
// [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
> Array.from(Array(10), (_, i) => i + 1)
// [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
In a quick test I ran, with each of the above running a million times each using our solution and doStuff function, the former approach (Array(n).fill()) proved slightly faster.
I am late to the party, but since this question turns up often in search results, I would just like to add a solution that I consider to be the best in terms of readability while not being long (which is ideal for any codebase IMO). It mutates, but I'd make that tradeoff for KISS principles.
let times = 5
while( times-- )
console.log(times)
// logs 4, 3, 2, 1, 0
Array(100).fill().map((_,i)=> console.log(i) );
This version satisifies the OP's requirement for immutability. Also consider using reduce instead of map depending on your use case.
This is also an option if you don't mind a little mutation in your prototype.
Number.prototype.times = function(f) {
return Array(this.valueOf()).fill().map((_,i)=>f(i));
};
Now we can do this
((3).times(i=>console.log(i)));
+1 to arcseldon for the .fill suggestion.
Not something I would teach (or ever use in my code), but here's a codegolf-worthy solution without mutating a variable, no need for ES6:
Array.apply(null, {length: 10}).forEach(function(_, i){
doStuff(i);
})
More of an interesting proof-of-concept thing than a useful answer, really.
If you're willing to use a library, there's also lodash _.times or underscore _.times:
_.times(x, i => {
return doStuff(i)
})
Note this returns an array of the results, so it's really more like this ruby:
x.times.map { |i|
doStuff(i)
}
Afaik, there is no mechanism in ES6 similar to Ruby's times method. But you can avoid mutation by using recursion:
let times = (i, cb, l = i) => {
if (i === 0) return;
cb(l - i);
times(i - 1, cb, l);
}
times(5, i => doStuff(i));
Demo: http://jsbin.com/koyecovano/1/edit?js,console
In the functional paradigm repeat is usually an infinite recursive function. To use it we need either lazy evaluation or continuation passing style.
Lazy evaluated function repetition
const repeat = f => x => [x, () => repeat(f) (f(x))];
const take = n => ([x, f]) => n === 0 ? x : take(n - 1) (f());
console.log(
take(8) (repeat(x => x * 2) (1)) // 256
);
I use a thunk (a function without arguments) to achieve lazy evaluation in Javascript.
Function repetition with continuation passing style
const repeat = f => x => [x, k => k(repeat(f) (f(x)))];
const take = n => ([x, k]) => n === 0 ? x : k(take(n - 1));
console.log(
take(8) (repeat(x => x * 2) (1)) // 256
);
CPS is a little scary at first. However, it always follows the same pattern: The last argument is the continuation (a function), which invokes its own body: k => k(...). Please note that CPS turns the application inside out, i.e. take(8) (repeat...) becomes k(take(8)) (...) where k is the partially applied repeat.
Conclusion
By separating the repetition (repeat) from the termination condition (take) we gain flexibility - separation of concerns up to its bitter end :D
Advantages of this solution
Simplest to read / use (imo)
Return value can be used as a sum, or just ignored
Plain es6 version, also link to TypeScript version of the code
Disadvantages
- Mutation. Being internal only I don't care, maybe some others will not either.
Examples and Code
times(5, 3) // 15 (3+3+3+3+3)
times(5, (i) => Math.pow(2,i) ) // 31 (1+2+4+8+16)
times(5, '<br/>') // <br/><br/><br/><br/><br/>
times(3, (i, count) => { // name[0], name[1], name[2]
let n = 'name[' + i + ']'
if (i < count-1)
n += ', '
return n
})
function times(count, callbackOrScalar) {
let type = typeof callbackOrScalar
let sum
if (type === 'number') sum = 0
else if (type === 'string') sum = ''
for (let j = 0; j < count; j++) {
if (type === 'function') {
const callback = callbackOrScalar
const result = callback(j, count)
if (typeof result === 'number' || typeof result === 'string')
sum = sum === undefined ? result : sum + result
}
else if (type === 'number' || type === 'string') {
const scalar = callbackOrScalar
sum = sum === undefined ? scalar : sum + scalar
}
}
return sum
}
TypeScipt version
https://codepen.io/whitneyland/pen/aVjaaE?editors=0011
The simplest way I can think of for creating list/array within range
Array.from(Array(max-min+1), (_, index) => index+min)
I have another alternative
[...Array(30).keys()]
addressing the functional aspect:
function times(n, f) {
var _f = function (f) {
var i;
for (i = 0; i < n; i++) {
f(i);
}
};
return typeof f === 'function' && _f(f) || _f;
}
times(6)(function (v) {
console.log('in parts: ' + v);
});
times(6, function (v) {
console.log('complete: ' + v);
});
Generators? Recursion? Why so much hatin' on mutatin'? ;-)
If it is acceptable as long as we "hide" it, then just accept the use of a unary operator and we can keep things simple:
Number.prototype.times = function(f) { let n=0 ; while(this.valueOf() > n) f(n++) }
Just like in ruby:
> (3).times(console.log)
0
1
2
I wrapped #Tieme s answer with a helper function.
In TypeScript:
export const mapN = <T = any[]>(count: number, fn: (...args: any[]) => T): T[] => [...Array(count)].map((_, i) => fn())
Now you can run:
const arr: string[] = mapN(3, () => 'something')
// returns ['something', 'something', 'something']
I made this:
function repeat(func, times) {
for (var i=0; i<times; i++) {
func(i);
}
}
Usage:
repeat(function(i) {
console.log("Hello, World! - "+i);
}, 5)
/*
Returns:
Hello, World! - 0
Hello, World! - 1
Hello, World! - 2
Hello, World! - 3
Hello, World! - 4
*/
The i variable returns the amount of times it has looped - useful if you need to preload an x amount of images.
I am just going to put this here. If you are looking for a compact function without using Arrays and you have no issue with mutability/immutability :
var g =x=>{/*your code goes here*/x-1>0?g(x-1):null};
For me, this is the easiest answer to understand for many levels of developers
const times = (n, callback) => {
while (n) {
callback();
n--;
}
}
times(10, ()=> console.log('hello'))
It seems to me that the most correct answer (which is debatable) to this question is buried in a comment by Sasha Kondrashov and is also the most concise, using just two characters: "no". There is no functional alternative to a for-loop as nice as the syntax that Ruby has. We might want there to be one, but there just isn't.
It is not explicitly stated in the question, but I would argue any solution to the problem of 'looping N times' should not allocate memory, at least not proportional to N. That criterium would rule out most of the answers that are 'native to javascript'.
Other answers show implementations like the one in Ruby, which is fine, except that the question explicitly asks for a native javascript solution. And there is already a very decent hand-rolled solution in the question, arguably one of the most readable of all.