Splitting string into array based on first and last - javascript

I have this array :-
var a = [' DL1,C1,C5,C6','M4,DL3-7,B1-5']
And I want to split them like
[DL1,C1,C5,C6,M4,DL3,DL4,DL5,DL6,DL7,B1,B2,B3,B4,B5]
So that DL3-7 or DL3-DL7 this Split like this DL3,DL4,DL5,DL6,DL7
Reason why I am doing this, is because I want to block duplicate entry like DL3 should not come anywhere else, I am trying for loops to do this, just want to know if there is any simpler way to do it, and check for duplicacy afterwards.
Thanks

You have to break down your problems into three parts:
getting comma delimited values into different array items
resolving "DL3-7" to "DL3", "DL4"...
removing duplicates
Once you break down the problem, it is much easier to handle them one by one. The code is pretty readable, let me know if there is anything difficult to understand what's going on.
const a = ['DL1,C1,C5,C6', 'M4,DL3-7,B1-5']
//this will split all comma delimited values
const commaDelimit = a.map(item => item.split(',')).flat();
console.log("Separate values by comma: ")
console.log(commaDelimit);
//this will turn the ranges into individual items
//this does not account for if the number is bigger than 9.
//you can try doing this part yourself if you need to, should be a good learning exercise.
const resolveRange = commaDelimit.map(item => {
if (item.includes('-')) {
const pos = item.indexOf('-');
const beginning = Number(item.charAt(pos - 1));
const end = Number(item.charAt(pos + 1)) + 1;
const toReturn = [];
const prependString = item.substring(0, pos - 1);
for (let i = beginning; i < end; i++) {
toReturn.push(`${prependString}${i}`)
}
return toReturn;
}
return item;
}).flat();
console.log("Change 'DL3-7' to DL3, DL4 and so on: ")
console.log(resolveRange);
//this will get rid of duplicates
const uniques = [...new Set(resolveRange)];
console.log("Remove duplicates: ")
console.log(uniques);

Create an Array with that length, iterate and transform,
I've just wrote the most challenged part:
function splitRange(range) {
let a = range.split('-');
if (a.length < 2) return [range];
const baseString = (a[0].match(/[a-z A-Z]/g))?.join('');
const baseNumber = +((a[0].match(/\d+/))?.shift());
return Array.from({length: +a.pop().match(/\d+/) - baseNumber + 1}).map((_,i)=>`${baseString}${i+baseNumber}`);
}
const s='DL1,C1,C5,C6,M4,DL3-7,B1-5';
console.log(
s.split(',').map(item=>splitRange(item)).flat()
);

Basically, #cSharp has explained the concept of data transformation to the desired output.
Split by comma.
Work with regex to transform the range value and append it to the array. Regex pattern & test data
Distinct the array value.
var a = [' DL1,C1,C5,C6','M4,DL3-7,B1-5'];
var formatteds = a.reduce((previous, current) => {
var splits = current.trim().split(',');
var rangedSplits = splits.reduce((prev, cur) => {
var pattern = new RegExp(/([A-Z]*)(\d)-[A-Z]*(\d)/);
var match = pattern.exec(cur);
if (match) {
// Pattern 1: ['DL3-7', 'DL', '3', '7']
// Pattern 2: ['DL3-DL7', 'DL', '3', '7']
var startIndex = parseInt(match[2].toString());
var endIndex = parseInt(match[3].toString());
var arr = [];
for (let i = startIndex; i <= endIndex; i++) {
arr.push(match[1].toString() + i);
}
prev = prev.concat(arr);
} else {
prev = prev.concat([cur]);
}
return prev;
}, []);
previous = previous.concat(rangedSplits);
return previous;
}, []);
var result = formatteds.filter((x, i, array) => array.indexOf(x) === i);
console.log(result);

Related

what's the most efficient way to split an array of millions of data based on condition?

It goes something like this where I have a london array containing more than 10 million data
london = ['dwig7xmW','gIzbnHNI' ...]
And now I have a userTraveled which also contains millions of data
userTraveled = ['ntuJV09a' ...]
Now what's the most efficient way to split userTraveled into inLondon and notInLondon.
My attempt.
inLondon = []
notInLondon = []
userTraveled.forEach((p) => london.includes(p) ? inLondon.push(p) : notInLondon.push(p))
london.includes(p) will do a linear search over the array. Doing that for every userTraveled is horribly inefficient. Use a Set instead:
const usersInLondon = [], usersNotInLondon = [];
const lookup = new Set(london);
for (const p of usersTraveled) {
(lookup.has(p) ? usersInLondon : usersNotInLondon).push(p);
}
I can offer a O(n*log(n)) solution instead of your O(n^2), first order the passwords and later use the binary search on it instead of the include to search for an item
Hope it helps =)
const london = ['dwig7xmW','gIzbnHNI']
const userTraveled = ['ntuJV09a', 'dwig7xmW']
let inLondon = []
let notInLondon = []
const sortedlondon=london.sort();
userTraveled.forEach((p) => (binarySearch(sortedlondon,p)!=-1 ? inLondon.push(p) : notInLondon.push(p)))
//https://www.htmlgoodies.com/javascript/how-to-search-a-javascript-string-array-using-a-binary-search/
function binarySearch(items, value){
var startIndex = 0,
stopIndex = items.length - 1,
middle = Math.floor((stopIndex + startIndex)/2);
while(items[middle] != value && startIndex < stopIndex){
//adjust search area
if (value < items[middle]){
stopIndex = middle - 1;
} else if (value > items[middle]){
startIndex = middle + 1;
}
//recalculate middle
middle = Math.floor((stopIndex + startIndex)/2);
}
//make sure it's the right value
return (items[middle] != value) ? -1 : middle;
}
I hope you are not using these data in a wrong way.
const passwords = ['a', 'b']
const rawPasswords = ['c', 'b'];
const setPasswords = new Set(passwords)
const uniquePassword = [];
const usedPassword = [];
rawPasswords.forEach(rp => {
if (setPasswords.has(rp)) {
usedPassword.push(rp)
} else {
uniquePassword.push(rp)
}
})
console.log(uniquePassword, usedPassword)
Referring to this answer for performance tests: Get all unique values in a JavaScript array (remove duplicates) the best solution in your case would be to use an Object. Since you require to know about the duplicates and not just remove them.
function uniqueArray( ar ) {
var j = {};
var k = [];
var unique;
ar.forEach( function(v) {
if(j.hasOwnProperty(v)){
k.push(v);
} else {
j[v] = v;
}
});
unique = Object.keys(j).map(function(v){
return j[v];
});
return [unique, k];
}
var arr = [1, 1, 2, 3, 4, 5, 4, 3];
console.log(uniqueArray(arr));
First it loops through the input array and checks if the value is already existing as a key on the object. If that's not the case, it adds it. If it is, it pushes the value to another array. Since objects use a hash, the Javascript engine can work faster with it.
Secondly it goes through the object's keys to turn it back into an array and finally returns both. I didn't add this explanation because the provided reference already explained it.
The result will be an array containing 2 arrays. First the array with unique values, second the array with duplicates.

How to remove only one of repeated chars in string using JavaScript

I have a string with repeated chars like : 'CANADA'.
And I am trying to get the string which removed only one of repeated chars :
'CNADA', 'CANDA', 'CANAD'.
I've tried it with subString, but it returned the part of string removed.
Also I've tried it with reduce, but it ended up removing all the repeated chars ('CND').
What is the way of removing only one char at time?
The results can be stored in array. (results = ['CNADA', 'CANDA', 'CANAD'])
Thank you.
You can achieve this by utilizing the second parameter of String#indexOf() which specifies the position from which to start the search. Here in a while loop, and using a Set to remove dulplicates before returning.
function getReplaceOptions(str, char) {
let res = [], i = str.indexOf(char, 0);
while (i !== -1) {
res.push(str.substring(0, i) + str.substring(++i));
i = str.indexOf(char, i)
}
return Array.from(new Set(res))
}
console.log(getReplaceOptions('CANADA', 'A'));
console.log(getReplaceOptions('Mississippi', 's'));
You can first count all the occurrences in the string. Later you can iterate over the script and if the count is greater than 1 you can remove that character.
const theString = 'CANADA'
const letterCount = {}
const resultArr = []
for (var i = 0; i < theString.length; i++) {
const theLetter = theString.charAt(i)
if(letterCount[theLetter]){
letterCount[theLetter] = letterCount[theLetter] + 1
}
else{
letterCount[theLetter] = 1
}
}
console.log(letterCount)
for (var i = 0; i < theString.length; i++) {
const theLetter = theString.charAt(i)
if(letterCount[theLetter] && letterCount[theLetter] > 1){
resultArr.push(theString.substr(0, i) + theString.substr(i + 1))
}
}
console.log(resultArr)
If you want to remove only the first repeating character then you can use matchAll here as:
Just see the browser compatibility before using this
const str = 'CANADA';
const firstRepeatedChar = 'A';
const result = [];
for (let { index } of str.matchAll(firstRepeatedChar)) {
result.push(str.slice(0, index) + str.slice(index + 1));
}
console.log(result);
NOTE: If you want to search for the first repeating character then remove it, then you can do as:
const str = 'CANADA';
let firstRepeatedChar = '';
const set = new Set();
for (let i = 0; i < str.length; ++i) {
if (!set.has(str[i])) {
set.add(str[i]);
} else {
firstRepeatedChar = str[i];
break;
}
}
const result = [];
for (let { index } of str.matchAll(firstRepeatedChar)) {
result.push(str.slice(0, index) + str.slice(index + 1));
}
console.log(result);
You could use some Array magic to remove duplicate characters:
function removeDuplicateCharacters(value) {
// convert string to array and loop through each character
return String(value).split('').filter(function(char, index, all) {
// return false if char found at a different index
return (index === all.indexOf(char));
})
.join(''); // convert back to a string
}
// returns `CAND`
removeDuplicateCharacters('CANADA');
// returns `helo wrd`
removeDuplicateCharacters('hello world');

Find indices within a string where any combination of an array of words is found

Sample data:
String: "barfoofoobarthefoobarman"
Array of words: ["bar", "foo", "the"]
Output:
[6, 9, 12]
I was asked this question during an interview. Due to time constraint, I tried to find all the possible words that could be made out of the array of words (i. e. "barfoothe"), but was told that would not scale for large arrays. Was suggested to use a map data structure, but I think my solution doesn't scale either, and it's brute forced.
Here's the solution.
var solution = function(string, words) {
let output = [];
let wordsMap = new Map();
let wordsNumber = words.length;
let wordLength = words[0].length;
words.forEach((word) => {
if (!wordsMap.has(word))
wordsMap.set(word, 1);
else
wordsMap.set(word, wordsMap.get(word) + 1);
});
for (let i = 0; i <= string.length-(wordsNumber*wordLength); i+=wordLength) {
let tempMap = new Map(wordsMap);
let check = true;
let tempString = string.substring(i, i + wordsNumber*wordLength);
for (let j = 0; j <= tempString.length - wordLength; j += wordLength) {
let tempString2 = tempString.substring(j, j + wordLength);
if (tempMap.has(tempString2))
tempMap.set(tempString2, tempMap.get(tempString2) - 1);
}
for (let val of tempMap.values()){
if (val !== 0){
check = false
break;
}
}
if (check)
output.push(i)
}
console.log(output);
}
solution("barfoothefoobarman", ["foo", "bar"]);
Any suggestion for a smarter solution?
You could create a dynamic regular expression.
const words = ['foo', 'bar']
const rx = new RegExp(words.join('|'), 'g')
// todo escape special characters
Then search away.
const counts = words.map(it=>0) // [0,0]
// todo use map or object to track counts instead of array
while (m = rx.exec(inputString)) {
const index = words.indexOf(m[0])
counts[index]++
}
Thank you for your question. I think the question in the interview was less about the right solution and more about the right approach.
The trickiest part is actually just finding the word combinations. There are several approaches here. For me it's a clear case for recursion.
So my approach would be:
find all word combinations, except combinations with itself (for example: foofoo or barbar).
iterate through the word combinations and ask whether they are contained in the string.
extra: Sort SolutionArray
Done!
Note: I use indexOf() for point 2 but I think a regex match would make it even better because you find all possibilities of a word in a string and not just the first one like with indexOf. Would make sense for longer strings.
const arr = ["foo", "bar"];
const str = "barfoothefoobarman"
let res = [];
const combinations = (len, val, existing) => {
if (len == 0) {
res.push(val);
return;
}
for(let i=0; i<arr.length; i++) {
if(! existing[i]) {
existing[i] = true;
combinations(len-1, val + arr[i], existing);
existing[i] = false;
}
}
}
const buildCombinations = (arr = []) => {
for(let i = 0; i < arr.length; i++) {
combinations(arr.length - i, "", []);
}
};
buildCombinations(arr);
// exclude the base wordes from result array
newRes = res.filter((e) => {
if (! arr.includes(e)) {
return e;
}
})
console.log('all word combinations:', newRes);
// get the string position
const _positions = [];
newRes.forEach((w) => {
let res = str.indexOf(w);
if (res != -1 && ! _positions.includes(res)) {
_positions.push(res);
}
})
// sort array and use Float64Array to speed up
const positions = new Float64Array(_positions)
console.log('positions', positions.sort())

Javascript split strings in array on specific index

I have this array of strings.
const numbersArray = ['1000','10000','100000']
My goal is to split each one of them on specific index for example: output of 1000 should be 1,000 and etc...
Here is what i have right now:
const splitArrayHandler = (arr) =>{
for (let i = 0; i < arr.length; i++) {
let indexOfSymbol = Math.round(arr[i].length / 3)
return splitAtIndex(arr[i],indexOfSymbol)
}
}
const splitAtIndex = (value,index) => {
return value.substring(0,index) + ',' + value.substring(index)
}
splitArrayHandler(numbersArray)
The first function splitArrayHandler loops through my array,finds specific index of the symbol in the string and then function splitAtIndex does the rest of the hard work.
The problem is only first element of the string is passing to the splitAtIndexfunction and I dont understand why. any suggestions please?
const numbersArray = ['1000','10000','100000']
const splitArrayHandler = (arr) =>{
for (let i = 0; i < arr.length; i++) {
let indexOfSymbol = Math.round(arr[i].length / 3)
return splitAtIndex(arr[i],indexOfSymbol)
}
}
const splitAtIndex = (value,index) => {
return value.substring(0,index) + ',' + value.substring(index)
}
splitArrayHandler(numbersArray)
Use Intl.NumberFormat for the job. No need for string parsing / manipulating:
const numbersArray = ['1000', '10000', '100000', '654654686156', '1000.66', '10e14', '0xFFFF'];
const format = new Intl.NumberFormat('en-US').format;
const formattedNumbers = numbersArray.map(Number).map(format);
console.log(formattedNumbers);
You are breaking the loop by returning the splitAtIndex function. Create another array and push the results to it.
const splitArrayHandler = (arr) =>{
let arr2 = []
for (let i = 0; i < arr.length; i++) {
let indexOfSymbol = Math.round(arr[i].length / 3)
arr2.push(splitAtIndex(arr[i],indexOfSymbol))
}
return arr2
}
You might use regular expression and map function (though there is no real difference between map and hard coded loop)
const numbersArray = ['1000','10000','100000']
function addComa(x) {
return x.replace(/\B(?=(\d{3})+(?!\d))/g, ',')
}
const resolved = numbersArray.map(addComma)
console.log(resolved) // ['1,000','10,000','100,000']

Cut JS string present in an array at every occurrance of a particular substring and append to same array

Note:
At this point in time, I'm unable to word the question title better. If someone is able to put it accross better, please go right ahead!
What I have:
var array = ["authentication.$.order", "difference.$.user.$.otherinformation", ... , ...]
What I need:
["authentication", "authentication.$", "authentication.$.order",
"difference", "difference.$", "difference.$.user", "difference.$.user.$",
"difference.$.user.$.otherinformation"]
Basically, wherevever I see .$., I need to preserve it, then append everything before the occourrence of .$. along with everything before the occourrence of .$
Example:
difference.$.user.$.otherinformation should be parsed to contain:
difference
difference.$
difference.$.user
difference.$.user.$
difference.$.user.$.otherinformation
I'm strongly feeling that some sort of recursion is to be involved here, but have not progressed in that direction yet.
Below is my implementation for the same, but unfortunately, my when my substring matches the first occourrence of .$., it stops and does not proceed to further check for other occurrences of .$. in the same string.
How best can I take this to closure?
Current flawed implementation:
for(var i=0; i<array.length; i++){
// next, replace all array field references with $ as that is what autoform's pick() requires
// /\.\d+\./g,".$." ==> replace globally .[number]. with .$.
array[i] = array[i].replace(/\.\d+\./g,".$.");
if(array[i].substring(0, array[i].lastIndexOf('.$.'))){
console.log("Substring without .$. " + array[i].substring(0, array[i].indexOf('.$.')));
console.log("Substring with .$ " + array[i].substring(0, array[i].indexOf('.$.')).concat(".$"));
array.push(array[i].substring(0, array[i].indexOf('.$.')).concat(".$"));
array.push(array[i].substring(0, array[i].indexOf('.$.')));
}
}
// finally remove any duplicates if any
array = _.uniq(array);
A functional single liner could be;
var array = ["authentication.$.order", "difference.$.user.$.otherinformation"],
result = array.reduce((r,s) => r.concat(s.split(".").reduce((p,c,i) => p.concat(i ? p[p.length-1] + "." + c : c), [])), []);
console.log(result);
You can use this function inside your array loop.
var test = "difference.$.user.$.otherinformation";
function toArray(testString) {
var testArr = testString.split(".")
var tempString = "";
var finalArray = [];
for (var i = 0; i < testArr.length; i++) {
var toTest = testArr[i];
if (toTest == "$") {
tempString += ".$"
} else {
if (i != 0) {
tempString += ".";
}
tempString += toTest;
}
finalArray.push(tempString)
}
return finalArray;
}
console.log(toArray(test))
I used a Regex expression to grab everything until the last occurrence of .$ and the chopped it, until there was nothing left. Reverse at the end.
let results = [];
let found = true;
const regex = /^(.*)\.\$/g;
let str = `difference.\$.user.\$.otherinformation`;
let m;
results.push(str);
while(found) {
found = false;
while ((m = regex.exec(str)) !== null) {
// This is necessary to avoid infinite loops with zero-width matches
if (m.index === regex.lastIndex) {
regex.lastIndex++;
}
if(m.length > 0) {
found = true;
results.push(m[0]);
str = m[1];
}
}
}
results.push(str);
results = results.reverse();
// Concat this onto another array and keep concatenating for the other strings
console.log(results);
You will just need to loop this over your array, store the results in a temp array and keep concatenating them onto a final array.
https://jsfiddle.net/9pa3hr46/
You can use reduce as follows:
const dat = ["authentication.$.order", "difference.$.user.$.otherinformation"];
const ret = dat.reduce((acc, val) => {
const props = val.split('.');
let concat = '';
return acc.concat(props.reduce((acc1, prop) => {
concat+= (concat ? '.'+ prop : prop);
acc1.push(concat);
return acc1;
}, []));
}, [])
console.log(ret);
Actually recursion is unnecessary for this problem. You can use regular loop with subloop instead.
All you need is:
split each occurence in the array into substrings;
build a series of accumulated values from these substrings;
replace the current element of the array with this series.
Moreover, in order to make replacement to work properly you have to iterate the array in reverse order. BTW in this case you don't need to remove duplicates in the array.
So the code should look like this:
var array = ["authentication.$.order", "difference.$.user.$.otherinformation"];
var SEP = '.$.';
for (var i = array.length-1; i >= 0; i--){
var v = array[i];
var subs = v.replace(/\.\d+\./g, SEP).split(SEP)
if (subs.length <= 1) continue;
var acc = subs[0], elems = [acc];
for (var n = subs.length-1, j = 0; j < n; j++) {
elems[j * 2 + 1] = (acc += SEP);
elems[j * 2 + 2] = (acc += subs[j]);
}
array.splice.apply(array, [i, 1].concat(elems));
}
console.log(array);
Use a simple for loop like below:
var str = "difference.$.user.$.otherinformation";
var sub, initial = "";
var start = 0;
var pos = str.indexOf('.');
for (; pos != -1; pos = str.indexOf('.', pos + 1)) {
sub = str.substring(start, pos);
console.log(initial + sub);
initial += sub;
start = pos;
}
console.log(str);

Categories

Resources