repeat.js (3745B)
1 /** 2 * @license Apache-2.0 3 * 4 * Copyright (c) 2018 The Stdlib Authors. 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 19 'use strict'; 20 21 // MODULES // 22 23 var isNonNegativeInteger = require( '@stdlib/assert/is-nonnegative-integer' ).isPrimitive; 24 var isString = require( '@stdlib/assert/is-string' ).isPrimitive; 25 var FLOAT64_MAX_SAFE_INTEGER = require( '@stdlib/constants/float64/max-safe-integer' ); 26 var format = require( './../../format' ); 27 28 29 // MAIN // 30 31 /** 32 * Repeats a string a specified number of times and returns the concatenated result. 33 * 34 * ## Method 35 * 36 * The algorithmic trick used in the implementation is to treat string concatenation the same as binary addition (i.e., any natural number (nonnegative integer) can be expressed as a sum of powers of two). 37 * 38 * For example, 39 * 40 * ```text 41 * n = 10 => 1010 => 2^3 + 2^0 + 2^1 + 2^0 42 * ``` 43 * 44 * We can produce a 10-repeat string by "adding" the results of a 8-repeat string and a 2-repeat string. 45 * 46 * The implementation is then as follows: 47 * 48 * 1. Let `s` be the string to be repeated and `o` be an output string. 49 * 50 * 2. Initialize an output string `o`. 51 * 52 * 3. Check the least significant bit to determine if the current `s` string should be "added" to the output "total". 53 * 54 * - if the bit is a one, add 55 * - otherwise, move on 56 * 57 * 4. Double the string `s` by adding `s` to `s`. 58 * 59 * 5. Right-shift the bits of `n`. 60 * 61 * 6. Check if we have shifted off all bits. 62 * 63 * - if yes, done. 64 * - otherwise, move on 65 * 66 * 7. Repeat 3-6. 67 * 68 * The result is that, as the string is repeated, we continually check to see if the doubled string is one which we want to add to our "total". 69 * 70 * The algorithm runs in `O(log_2(n))` compared to `O(n)`. 71 * 72 * 73 * @param {string} str - string to repeat 74 * @param {NonNegativeInteger} n - number of times to repeat the string 75 * @throws {TypeError} first argument must be a string 76 * @throws {TypeError} second argument must be a nonnegative integer 77 * @throws {RangeError} output string length must not exceed maximum allowed string length 78 * @returns {string} repeated string 79 * 80 * @example 81 * var str = repeat( 'a', 5 ); 82 * // returns 'aaaaa' 83 * 84 * @example 85 * var str = repeat( '', 100 ); 86 * // returns '' 87 * 88 * @example 89 * var str = repeat( 'beep', 0 ); 90 * // returns '' 91 */ 92 function repeat( str, n ) { 93 var rpt; 94 var cnt; 95 if ( !isString( str ) ) { 96 throw new TypeError( format( 'invalid argument. First argument must be a string. Value: `%s`.', str ) ); 97 } 98 if ( !isNonNegativeInteger( n ) ) { 99 throw new TypeError( format( 'invalid argument. Second argument must be a nonnegative integer. Value: `%s`.', n ) ); 100 } 101 if ( str.length === 0 || n === 0 ) { 102 return ''; 103 } 104 // Check that output string will not exceed the maximum string length: 105 if ( str.length * n > FLOAT64_MAX_SAFE_INTEGER ) { 106 throw new RangeError( format( 'invalid argument. Output string length exceeds maximum allowed string length. Value: `%u`.', str.length * n ) ); 107 } 108 rpt = ''; 109 cnt = n; 110 for ( ; ; ) { 111 // If the count is odd, append the current concatenated string: 112 if ( (cnt&1) === 1 ) { 113 rpt += str; 114 } 115 // Right-shift the bits: 116 cnt >>>= 1; 117 if ( cnt === 0 ) { 118 break; 119 } 120 // Double the string: 121 str += str; 122 } 123 return rpt; 124 } 125 126 127 // EXPORTS // 128 129 module.exports = repeat;