How can I detect the CSV separator from a string in Javascript/NodeJS?
Which is the standard algorithm?
Note that the separator is not a comma always. The most common separators being ;, , and \t (tab).
A possible algorithm for getting the likely separator(s) is pretty simple, and assumes the data is well-formed:
For every delimiter,
For every line,
Split the line by the delimiter, check the length.
If its length is not equal to the last line's length, this is not a valid delimiter.
Proof of concept (doesn't handle quoted fields):
function guessDelimiters (text, possibleDelimiters) {
return possibleDelimiters.filter(weedOut);
function weedOut (delimiter) {
var cache = -1;
return text.split('\n').every(checkLength);
function checkLength (line) {
if (!line) {
return true;
}
var length = line.split(delimiter).length;
if (cache < 0) {
cache = length;
}
return cache === length && length > 1;
}
}
}
The length > 1 check is to make sure the split didn't just return the whole line. Note that this returns an array of possible delimiters - if there's more than one item, you have an ambiguity problem.
Another solution is using the detect method from the csv-string package:
detect(input : String) : String Detects the best separator.
var CSV = require('csv-string');
console.log(CSV.detect('a,b,c')); // OUTPUT : ","
console.log(CSV.detect('a;b;c')); // OUTPUT : ";"
console.log(CSV.detect('a|b|c')); // OUTPUT : "|"
console.log(CSV.detect('a\tb\tc'));// OUTPUT : "\t"
function delimiter(csvText) {
t = t.split("\n")[0];
let delArray = [',', ';', '|', ' '];
let comma = samiclon = pipe = tab = 0;
delArray.forEach((e, i) => {
if (i === 0) {
comma = t.split(e).length;
} else if (i === 1) {
samiclon = t.split(e).length;
} else if (i === 2) {
pipe = t.split(e).length;
} else if (i === 3) {
tab = t.split(e).length;
}
});
let tmpArray1 = [comma, samiclon, pipe, tab]
let tmpArray = [comma, samiclon, pipe, tab];
let maxLen = tmpArray.sort((a, b) => b - a)[0];
let delimiter_i = 0;
tmpArray1.forEach((e, i) => {
if (maxLen === e) {
delimiter_i = i;
}
});
if (delimiter_i === 0) {
return ',';
} else if (delimiter_i === 1) {
return ';'
} else if (delimiter_i === 2) {
return '|'
} else if (delimiter_i === 3) {
return ' '
}
}
This solution allows to detect the csv separator only for the top lines and handles quoted fields by using csv-parse.
It can be useful for large csv file to avoid reading the whole file several times.
const parse = require('csv-parse/lib/sync');
const fs = require('fs')
function detectCsvDelimiter(file, maxLineCount, delimiters = [',', ';', '\t']) {
return new Promise((resolve, reject) => {
// Read only maxLineCount lines
let stream = fs.createReadStream(file, {
flags: 'r', encoding: 'utf-8', bufferSize: 64 * 1024 });
let lineCounter = 0;
let data = '';
stream.on("data", (moreData) => {
data += moreData;
lineCounter += data.split("\n").length - 1;
if (lineCounter > maxLineCount + 1) {
stream.destroy();
// Remove invalid last line
resolve(data.split('\n').slice(0, maxLineCount));
}
});
stream.on("error", (err) => reject(err));
stream.on("end", () => resolve(data.split("\n")));
}).then(lines => {
return new Promise(resolve => {
const csvData = lines.join("\n");
const validDelimiters = delimiters.filter(delimiter => {
let isValid = true;
// csv-parse throw error by default
// if the number of columns is inconsistent between lines
try {
const rows = parse(csvData, {delimiter});
isValid = rows.some(row => row.length > 1);
} catch (e) {
isValid = false;
}
return isValid;
});
resolve(validDelimiters);
});
});
}
I found Zirak's answer good in theory, but in practice it failed in many particular places.
Here are some examples that would fail and thus the method would return no delimiter :
a trailing delimiter (by mistake) : test,test1,test2,
missing cells without delimiters: test,test1
This is the most convenient and elegant solution :
let columnData = row.split(/,|;|\|| /);
What sucks here is if there is two delimiters that appear in a row, then this fails.
Say you want to seperate this data: "test,tes|t1,test2".
The above code produces an array that looks like this :
[test, tes, t1, test2]... No good.
Here is a more robust solution I wrote that was inspired by "the programmers" answer:
const rowSplitChars = this.determineCSVLineBreak(text);
const columnSplitChars = this.determineCSVDelimiter(text, rowSplitChars);
private determineCSVLineBreak(text: any): string {
try {
const delArray = ["\r\n", "\n", "\r"];
const rnrnArray =
[text.match(/\r/g).length
,text.match(/\n/g).length
,text.match(/\r\n/g).length];
return delArray[this.getMaxIndexInArray(rnrnArray)];
} catch {
this.handleError('Error determining CSV file line break character.');
return '';
}
}
private determineCSVDelimiter(text: any, rowSplitChars: string): string {
const t = text.split(rowSplitChars)[0];
const delArray = [',', ';', '|', ' '];
const countArray =
[t.split(delArray[0]).length
,t.split(delArray[1]).length
,t.split(delArray[2]).length
,t.split(delArray[3]).length];
return delArray[this.getMaxIndexInArray(countArray)];
}
private getMaxIndexInArray(countArray: any[]) {
let max = countArray[0];
let maxIndex = 0;
for (let i = 1; i < countArray.length; i++) {
if (countArray[i] > max) {
maxIndex = i;
max = countArray[i];
}
}
return maxIndex;
}
Related
Let's say we have this string:
BBBBAAAABBAAAAAACCCCCBDDDDEEEEEEE,FFF
As you can see, here B is occurring 4 times at first but B is also present before DDDD.
Similarly, A is occurring 4 times at the beginning and later 6 times.
I want the expected output if I am searching B it should 4 times as the max occurrence B is 4. However if I am searching A then it should return 6 because the most occurrence for A is 6.
Here is my code I tried:
function checkRepeatativeString(str) {
let hashMap = {};
let seen = new Set();
let counter = 1;
let maxValue = 1;
let isPreviousValueSame = false;
let isNextValueSame = true;
for (let i = 0; i < str.length; i++) {
/**
* is previous value same
*/
if (str[i] == str[i-1]) {
isPreviousValueSame = true;
}
/**
* is next value same
*/
if (str[i] == str[i+1]) {
isNextValueSame = true;
}
if (seen.has(str[i]) && isPreviousValueSame) {
hashMap[str[i]][0]++;
hashMap[str[i]][1]++;
isPreviousValueSame = false;
} else if(seen.has(str[i]) && !isNextValueSame) {
maxValue = Math.max(hashMap[str[i]][1], maxValue);
counter = 0;
hashMap[str[i]] = [counter, maxValue];
} else {
maxValue = Math.max(maxValue, counter);
seen.add(str[i]);
hashMap[str[i]] = [counter, maxValue];
isPreviousValueSame = false;
}
}
return hashMap;
}
let str = "BBBBAAAABBAAAAAACCCCCBDDDDEEEEEEE,FFF";
console.log(checkRepeatativeString(str));
This code is working but if you look for B, I am getting stuck at the beginning of value.
My program returns out for B:
B: [ 1, 1 ]
^ ^
Inside array, 1 is a counter which scans the string and second 1 in array is a max value which should return the output. However my program is returning 1 for B. I am expecting 4 as max value.
Help would be appreciated~
Quick and dirty.
function maxConsecutiveCharacters(check, haystack) {
if(check.length !== 1) return false;
let result = 0;
let buffer = 0;
for(let i = 0; i < haystack.length; i++) {
if(haystack[i] === check) {
buffer++;
}
else {
if(buffer > result) {
result = buffer;
}
buffer = 0;
}
if(buffer > result) {
result = buffer;
}
}
return result;
}
That looks overly complicated. Consider approaching the problem from a different angle - first split up the string into segments of repeating characters, and group them into an object based on the length of the longest substring for a given character.
const checkRepeatativeString = (str) => {
const longestCounts = {};
for (const consecutive of (str.match(/(.)\1*/g) || [])) {
const char = consecutive[0];
longestCounts[char] = Math.max(
longestCounts[char] || 0, // Use the existing value in the object if it exists and is higher
consecutive.length // Otherwise, use the length of the string iterated over
);
}
return longestCounts;
};
let str = "BBBBAAAABBAAAAAACCCCCBDDDDEEEEEEE,FFF";
console.log(checkRepeatativeString(str));
Simpler code often means less surface area for bugs.
I have ‘array magazine’ and ‘string ransomNote’. And want to access an element of the array based on an element from the string.
This is what I am trying: magazine.findIndex(ransomNote[i])
var canConstruct = function(ransomNote, magazine){
magazine = magazine.split('');
//console.log(magazine);
for(let i = 0; i < ransomNote.length; i++){
if (magazine.includes(ransomNote[i])){
element_to_erase = magazine.findIndex(ransomNote[i]);
magazine = magazine.splice(element_to_erase , 1);
//console.log(magazine);
continue;
} else {
return false;
}
}
return true;
};
console.log(canConstruct('aa', 'aab'));
findIndex takes a function as an argument, and you are passing it a string
you need to do
magazine.findIndex((magazineString) => magazineString === ransomNote[i])
Or just use indexOf as its pointed in the comments, and probably validate if that returns something other than -1 (indexOf) or undefined (findIndex) in either case.
Instead of converting the input string magazine to an array, you can use magazine.search directly -
const removeCharAt = (str = 0, pos = 0) =>
str.substr(0, pos) + str.substr(pos + 1)
const canConstruct = (ransomNote = "", magazine = "") => {
for (const char of ransomNote) {
const pos = magazine.search(char)
if (pos >= 0)
magazine = removeCharAt(magazine)
else
return false
}
return true
}
console.log(canConstruct('aa', 'aab'))
// true
console.log(canConstruct('az', 'aab'))
// false
console.log(canConstruct('stay inside', 'diet coke on sale this sunday'))
// true
There is another issue. While splice, u don't need to reassign. Splice modify the same array.
You can simplify using array.every.
var canConstruct = function (ransomNote, magazine) {
magazine = magazine.split("");
return ransomNote.split("").every((rChar) => {
const rIndex = magazine.indexOf(rChar);
if (rIndex !== -1) {
magazine.splice(rIndex, 1);
return true;
}
});
};
console.log(canConstruct("aa", "aab"));
console.log(canConstruct("az", "aab"));
I've been trying to solve this codewars challenge. The idea is to return the string, rearranged according to its hierarchy, or separated into chunks according to the repeating character.
You will receive a string consisting of lowercase letters, uppercase letters and digits as input. Your task is to return this string as blocks separated by dashes ("-"). The elements of a block should be sorted with respect to the hierarchy listed below, and each block cannot contain multiple instances of the same character.
The hierarchy is:
lowercase letters (a - z), in alphabetic order
uppercase letters (A - Z), in alphabetic order
digits (0 - 9), in ascending order
Examples
"21AxBz" -> "xzAB12"
since input does not contain repeating characters, you only need 1 block
"abacad" -> "abcd-a-a"
character "a" repeats 3 times, thus 3 blocks are needed
"" -> ""
an empty input should result in an empty output
What I've tried actually works for the given test cases:
describe("Sample tests", () => {
it("Tests", () => {
assert.equal(blocks("21AxBz"), "xzAB12");
assert.equal(blocks("abacad"), "abcd-a-a");
assert.equal(blocks(""), "");
});
});
But fails when there are any repeating characters, besides in the test cases:
function repeatingChar(str){
const result = [];
const strArr = str.toLowerCase().split("").sort().join("").match(/(.)\1+/g);
if (strArr != null) {
strArr.forEach((elem) => {
result.push(elem[0]);
});
}
return result;
}
function blocks(s) {
if (s.length === 0) {
return '';
}
//if string does not contain repeating characters
if (!/(.).*\1/.test(s) === true) {
let lower = s.match(/[a-z]/g).join('');
let upper = s.match(/[A-Z]/g).join('');
let numbers = s.match(/[\d]/g).sort().join('');
return lower + upper + numbers;
}
//if string contains repeating characters
if (!/(.).*\1/.test(s) === false) {
let repeatChar = (repeatingChar(s)[0]);
let repeatRegex = new RegExp(repeatingChar(s)[0]);
let repeatCount = s.match(/[repeatRegex]/gi).length;
let nonAChars = s.match(/[^a]/gi).join('');
function getPosition(string, subString, index) {
return s.split(repeatChar, index).join(repeatChar).length;
}
let index = getPosition(s, repeatChar, 2);
// console.log('indexxxxx', index);
return s.slice(0, index) + nonAChars.slice(1) + ('-' + repeatChar).repeat(repeatCount - 1);
}
}
console.log(blocks("abacad"));
And actually, I'm not sure what's wrong with it, because I don't know how to unlock any other tests on Codewars.
You can see that what I'm trying to do, is find the repeating character, get all characters that are not the repeating character, and slice the string from starting point up until the 2 instance of the repeating character, and then add on the remaining repeating characters at the end, separated by dashes.
Any other suggestions for how to do this?
For funzies, here's how I would have approached the problem:
const isLower = new RegExp('[a-z]');
const isUpper = new RegExp('[A-Z]');
const isDigit = new RegExp('[0-9]');
const isDigitOrUpper = new RegExp('[0-9A-Z]');
const isDigitOrLower = new RegExp('[0-9a-z]');
const isLowerOrUpper = new RegExp('[a-zA-Z]');
function lowerUpperNumber(a, b)
{
if(isLower.test(a) && isDigitOrUpper.test(b))
{
return -1;
}
else if(isUpper.test(a) && isDigitOrLower.test(b))
{
if(isDigit.test(b))
{
return -1;
}
else if(isLower.test(b))
{
return 1;
}
}
else if(isDigit.test(a) && isLowerOrUpper.test(b))
{
return 1;
}
else if(a > b)
{
return 1;
}
else if(a < b)
{
return -1;
}
return 0;
}
function makeBlocks(input)
{
let sortedInput = input.split('');
sortedInput.sort(lowerUpperNumber);
let output = '';
let blocks = [];
for(let c of sortedInput)
{
let inserted = false;
for(let block of blocks)
{
if(block.indexOf(c) === -1)
{
inserted = true;
block.push(c);
break;
}
}
if(!inserted)
{
blocks.push([c]);
}
}
output = blocks.map(block => block.join('')).join('-');
return output;
}
console.log(makeBlocks('21AxBz'));
console.log(makeBlocks('abacad'));
console.log(makeBlocks('Any9Old4String22With7Numbers'));
console.log(makeBlocks(''));
The first obvious error I can see is let repeatCount = s.match(/[repeatRegex]/gi).length;. What you really want to do is:
let repeatRegex = new RegExp(repeatingChar(s)[0], 'g');
let repeatCount = s.match(repeatRegex).length;
The next is that you only look at one of the repeating characters, and not all of them, so you won't get blocks of the correct form, so you'll need to loop over them.
let repeatedChars = repeatingChar(s);
for(let c of repeatedChars)
{
//figure out blocks
}
When you're building the block, you've decided to focus on everything that's not "a". I'm guessing that's not what you originally wrote, but some debugging code, to work on that one sample input.
If I understand your desire correctly, you want to take all the non-repeating characters and smoosh them together, then take the first instance of the first repeating character and stuff that on the front and then cram the remaining instances of the repeating character on the back, separated by -.
The problem here is that the first repeating character might not be the one that should be first in the result. Essentially you got lucky with the repeating character being a.
Fixing up your code, I would create an array and build the blocks up individually, then join them all together at the end.
let repeatedChars = repeatingChar(s);
let blocks = []
for(let c of repeatedChars)
{
let repeatRegex = new RegExp(c, 'g');
let repeatCount = s.match(repeatRegex).length;
for(let i = 1; i <= repeatCount; i++)
{
if(blocks.length < i)
{
let newBlock = [c];
blocks.push(newBlock);
}
else
{
block[i - 1].push(c);
}
}
}
let tailBlocks = blocks.map(block => block.join('')).join('-');
However, this leaves me with a problem of how to build the final string with the non-repeating characters included, all in the right order.
So, to start with, let's make the initial string. To do so we'll need a custom sort function (sorry, it's pretty verbose. If only we could use regular ASCII ordering):
function lowerUpperNumber(a, b)
{
if(a.match(/[a-z]/) && b.match(/[A-Z0-9]/))
{
return -1;
}
else if(a.match(/[A-Z]/) && (b.match(/[0-9]/) || b.match(/[a-z]/)))
{
if(b.match(/[0-9]/))
{
return -1;
}
else if(b.match(/[a-z]/))
{
return 1;
}
}
else if(a.match(/[0-9]/) && b.match(/[a-zA-Z]/))
{
return 1;
}
else if(a > b)
{
return 1;
}
else if(a < b)
{
return -1;
}
return 0;
}
Then create the head of the final output:
let firstBlock = [...(new Set(s))].sort(lowerUpperNumber);
The Set creates a set of unique elements, i.e. no repeats.
Because we've created the head string, when creating the blocks of repeated characters, we'll need one fewer than the above loop gives us, so we'll be using s.match(repeatRegex).length-1.
I get the desire to short circuit the complicated bit and return quickly when there are no repeated characters, but I'm going to remove that bit for brevity, and also I don't want to deal with undefined values (for example try '123' as your input).
Let's put it all together:
function lowerUpperNumber(a, b)
{
if(a.match(/[a-z]/) && b.match(/[A-Z0-9]/))
{
return -1;
}
else if(a.match(/[A-Z]/) && (b.match(/[0-9]/) || b.match(/[a-z]/)))
{
if(b.match(/[0-9]/))
{
return -1;
}
else if(b.match(/[a-z]/))
{
return 1;
}
}
else if(a.match(/[0-9]/) && b.match(/[a-zA-Z]/))
{
return 1;
}
else if(a > b)
{
return 1;
}
else if(a < b)
{
return -1;
}
return 0;
}
function makeBlocks(s)
{
if (s.length === 0)
{
return '';
}
let firstBlock = [...(new Set(s))].sort(lowerUpperNumber);
let firstString = firstBlock.join('');
let blocks = [];
for(let c of firstString)
{
let repeatRegex = new RegExp(c, 'g');
let repeatCount = s.match(repeatRegex).length - 1;
for(let i = 1; i <= repeatCount; i++)
{
if(blocks.length < i)
{
let newBlock = [c];
blocks.push(newBlock);
}
else
{
blocks[i - 1].push(c);
}
}
}
blocks.unshift(firstBlock);
return blocks.map(block => block.join('')).join('-');
}
console.log(makeBlocks('21AxBz'));
console.log(makeBlocks('abacad'));
console.log(makeBlocks('Any9Old4String22With7Numbers'));
console.log(makeBlocks(''));
You'll see I've not bothered generating the characters that repeat, because I can just skip the ones that don't.
How do i check that a given word is an isogram with pure javascript, using a function. the function must return true or false.
An isogram is a word with a repeated character.
I know this code works, but i need a better solution.
function isIsogram(word){
x = false; y = false;
for(i = 0; i < word.length; i++){
wordl = word.substring(0,i)
wordr = word.substring(i)
x = wordl.includes(word.charAt(i))
y = wordr.includes(word.charAt(i))
//console.log(x,wordl,wordr)
}
return x&&y
}
isIsogram("thomas");//False
isIsogram("moses"); //True
Remove the duplicate letter from string then check both length. if same its an isogram.
function isIsogram(str){
return str.split('').filter((item, pos, arr)=> arr.indexOf(item) == pos).length == str.length;
}
console.log(isIsogram('thomas'));
console.log(isIsogram('moses'));
One way of doing this!
function isIsogram(str){
return !str.match(/([a-z]).*\1/i);
}
Here is a simple approach using .split() and .every():
let isIsogram = (str) => str.split("").every((c, i) => str.indexOf(c) == i);
console.log(isIsogram("thomas")); /* no repeating letter */
console.log(isIsogram("moses")); /* s repeat 2 times */
console.log(isIsogram("hello")); /* l repeat 2 times */
console.log(isIsogram("world")); /* no repeating letter */
console.log(isIsogram("a b c")); /* space character repeat 2 times */
Docs:
String.prototype.split()
String.prototype.indexOf()
Array.prototype.every()
Building on kishea's answer:
function isIsogram(sWord)
{
for (iCharIndex = 0; iCharIndex < sWord.length; iCharIndex++)
if (sWord.substring(iCharIndex + 1).includes(sWord.charAt(iCharIndex)))
return false;
return true;
}
If the character at the current position (charAt) is found (includes) to the right of the current position (substring), false is returned. Otherwise the loop runs to the end and true is returned.
const isIsogram = (word) => {
return new Set(word.toLowerCase()).size === word.length
}
console.log(isIsogram('Thor'));//true
console.log(isIsogram('Loki'));//true
console.log(isIsogram('America'));//false
function isIsogram(str) {
return new Set(str.toUpperCase()).size == str.length
}
What about :
> function isIsogram(word) {
... var a = word.split('')
... var b = Array.from(new Set(a))
... return a.length === b.length;
... }
undefined
> isIsogram("mesos")
false
> isIsogram("thomas")
true
Or better (checking each char only once) :
> function isIsogram2(word) {
... for(var i=0,len=word.length-1;i<len;++i) {
..... var c = word[i]
..... if(word.indexOf(c,i+1) !== -1) return false;
..... }
... return true;
... }
undefined
> isIsogram2("mesos")
false
> isIsogram2("thomas")
true
function isIsogram(word){
return !/(.).*\1|\d/i.test(word)
}
var str=prompt('Enter a string');
var strlen=str.length;
for(i=0;i<strlen;i++){
var stc=str.charAt(i);
var flag=0;
for(j=0;j<strlen;j++){
var stk=str.charAt(j);
if(stc==stk){
flag=flag+1;
}
}
if(flag!=1){
break;
}
}
if(flag!=1){
alert('It is not an isogram');
}
else{
alert('It is an isogram');
}
While given a word, this function if splits the word into two,
That is wordl and wordr respectively.
Both splittings are checked to include a character in the original word. If wordl and wordr both contain any character in the original word. Then surely this is an isogram
function isIsogram(word){
x = false; y = false;
for(i = 0; i < word.length; i++){
wordl = word.substring(0,i)
wordr = word.substring(i)
x = wordl.includes(word.charAt(i))
y = wordr.includes(word.charAt(i))
//console.log(x,wordl,wordr)
}
return !x&&y
}
isIsogram("thomas");//True
isIsogram("moses"); //False
const isIsogram = (string) => {
const lowerCased = string.toLowerCase()
const result = lowerCased.split('').every((v,i)=>lowerCased.indexOf(v)===i)
return result
}
console.log(isIsogram('ambidExtRously')) // true
console.log(isIsogram('patteRN')) // false
function isIsogram(word) {
let uniqueCharacters = new Set(word.split(''));
uniqueCharacters = Array.from(uniqueCharacters); //get all the unique char
let charFreq = {}; //e.g {a:2, b:3}
for (element of uniqueCharacters) {
charFreq[element] = 0;
} //set the frequency of each char to zero
function updateFrequency(element) {
charFreq[element] += 1;
}//callback used directly below
word.split('').forEach(updateFrequency); //for each char encountered, update frequency
let frequencyOfCharacter = [];
for (keys in charFreq) {
frequencyOfCharacter.push(charFreq[keys]);
}
function equal(item) {
return item === frequencyOfCharacter[0];
}
//check if all the frequencies are the same, an isogram means all characters occur at the same frequency
return frequencyOfCharacter.every(equal);
}
console.log(isIsogram('try'), isIsogram('baba'), isIsogram('tests'));
I have this simple function which will prepend a desired string to the beginning of each line. I have this working with streams, but in some cases it's also convenient using a console.log() type of construct.
Here is the function:
// takes a string to prepend, and a stream to prepend to:
exports.lp = function (str, strm) {
return function prependLog() {
var args = Array.from(arguments);
var hasNonWhitespace = args.some(function (a) {
var str = String(a);
return str.length > 0 && /\S/g.test(str);
});
if (hasNonWhitespace) {
strm.write(str);
}
args.forEach(function (s, i) {
String(s).split('\n').forEach(function (s, i) {
if (i < 1) {
strm.write(s + ' ');
}
else {
strm.write('\n' + str + s);
}
});
});
strm.write('\n');
};
};
Here is the use:
const {lp} = require('log-prepend');
const fn = lp(' [foobar] ', process.stdout);
fn('\n');
fn();
fn();
fn('','','');
fn('log1', 'log2\n3',4,5 + '\n55');
fn('a','b','c');
and here is the output from the above:
[foobar]
[foobar] log1 log2
[foobar] 34 5
[foobar] 55
[foobar] a b c
the problem is that for empty lines with no non-whitespace character, it usually works, but when I include a newline character, it outputs [foobar] even though there is nothing on that line.
I can't figure out why my function doesn't omit [foobar] for lines with no non-whitespace. So to be exact, it is the first instance of [foobar] above that is mystifying me.
Having a little trouble following the logic here but is it because you are expecting to use the index variable from the first forEach function, when it's actually using it from the second? Renaming the initial variables might help.
I think I made a good fix like so:
exports.lp = function (str, strm) {
return function prependLog() {
var args = Array.from(arguments);
var hasNonWhitespace = args.some(function (a) {
var str = String(a);
return str.length > 0 && /\S/g.test(str);
});
if (hasNonWhitespace) {
strm.write(str);
}
args.forEach(function (s, i) {
String(s).split('\n').forEach(function (s, i) {
if (i < 1) {
strm.write(s + ' ');
}
else {
// => here is the fix, add the following if/else
if (/\S/g.test(s)) {
strm.write('\n' + str + s);
}
else {
strm.write('\n' + s);
}
}
});
});
strm.write('\n');
};
};
but if anyone has a better way to do this, please let me know.