Script to group elements where every character is same to each other - javascript

For input:
["abc","def","okg","fed","bca"]
expected output should be:
["abc","bca"],["def","fed"],["okg"]
here "abc", "bca" and "def", "fed" contains same character and "okg" there is no element which contains these character
const arr = ["abc", "def", "okg", "fed", "bca"];
let find = (arr) => {
let res = [];
for (let i = 0; i < arr.length; i++) {
for (let j = 1; j < arr.length; j++) {
if (arr[i].search(arr[j])) {
res.push(arr[j]);
}
}
}
return res;
}
console.log(find(arr))

A reduce will do the trick - it seems the shortest code here (apart from the one using lodash)
const arr = ["abc", "def", "okg", "fed", "bca"],
res = Object.values(arr.reduce((acc, ele) => {
const key = ele.split("").sort();
(acc[key] = acc[key] || []).push(ele)
return acc
}, {}))
console.log(res)

.search returns a number indicating the index of where the match was found. Check that the result isn't -1 instead of checking that the result is truthy. But...
.search isn't the right tool here anyway, because it won't find different combinations of the same character. You need a different approach. One way would be to create an object whose keys are the characters found, and the values are the number of occurrences, then use a sorted representation of that object as a key. For example, have both abc and bca turn into something like:
a,1-b,1-c,1
Iterate through the input array, generating a key for each string, and putting the string on an object with that key. At the end, take the object's values.
const strToKey = (str) => {
const grouped = {};
for (const char of str) {
grouped[char] = (grouped[char] || 0) + 1;
}
return Object.entries(grouped)
.sort((a, b) => a[0].localeCompare(b[0]))
.join('-');
};
let find = (arr) => {
const grouped = {};
for (const str of arr) {
const key = strToKey(str);
grouped[key] ??= [];
grouped[key].push(str);
}
return Object.values(grouped);
}
console.log(find(["abc", "def", "okg", "fed", "bca"]));
Another option, when creating the keys, instead of sorting the object afterwards, you could sort the string first:
const strToKey = (str) => {
const grouped = {};
for (const char of [...str].sort()) {
grouped[char] = (grouped[char] || 0) + 1;
}
return Object.entries(grouped).join('-');
};
let find = (arr) => {
const grouped = {};
for (const str of arr) {
const key = strToKey(str);
grouped[key] ??= [];
grouped[key].push(str);
}
return Object.values(grouped);
}
console.log(find(["abc", "def", "okg", "fed", "bca"]));

const input = ["abc","def","okg","fed","bca"]
function getSortedString (str) {
return [...str].sort().join('');
};
function groupBy(input) {
const grouped = [];
while(input.length) {
const nextInput = [];
const first = input[0];
const matched = [first];
for (let i = 1; i < input.length; i++) {
if(getSortedString(first) === getSortedString(input[i])) {
matched.push(input[i])
} else {
nextInput.push(input[i])
}
}
input = nextInput;
grouped.push(matched);
}
console.log(grouped);
}
groupBy(input);

Using Object.values and groupBy (from lodash), you can get a straightforward solution:
You group your array elements by their "sorted" form and then use Object.values to get the output array.
const arr = ["abc", "def", "okg", "fed", "bca"];
const sortString = (str) => str.split("").sort().join("")
const result = Object.values(_.groupBy(arr, sortString));
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>

Related

Remove all anagrams from array

I need find and delete all anagrams from an array. All my attempts give ["bac","art"], but I need ["art"]
const deleteAnagrams = (arr) => {
let obj = {};
for (let i = 0; i < arr.length; i++) {
let sorted = arr[i].toLowerCase().split("").sort().join("");
obj[sorted] = arr[i];
}
return Object.values(obj);
};
console.log(deleteAnagrams(['cab', 'bac', 'art']))
You are actually storing unique strings with the last occurence to the object. But you need to remove duplicates and keep only singel occurences of same sorted strings.
To get the result, you need to filter the values.
const deleteAnagrams = array => {
const object = {};
for (const word of array) {
let sorted = word.toLowerCase().split("").sort().join("");
object[sorted] = sorted in object ? undefined : word;
}
return Object.values(object).filter(Boolean);
};
console.log(deleteAnagrams(['cab', 'bac', 'art']));

Reduce array in javascript with patern matching

How would you reduce items from array arr into reducedArray by removing items that first two characters match with strings from array generalCase and if they encountered, then an asterisk is added to indicated that this string was encountered on array arr.
let arr =["ADB", "AB1", "BD1", "BD4", "AB3", "BP9", "BPX","STR", "ABS"]
let generalCase = ["AB", "BD", "BP"]
let reducedArray = []
arr.forEach( item => {
if (item.startsWith("AB") && !reducedArray.includes("AB*")) {
reducedArray.push("AB*");
} else if (item.startsWith("BD") && !reducedArray.includes("BD*")) {
reducedArray.push("BD*")
} else if (item.startsWith("BP") && !reducedArray.includes("BP*")) {
reducedArray.push("BP*")
} else if (item === "STR") {
reducedArray.push("STR")
} else if (!reducedArray.includes(item)) {
reducedArray.push(item)
}
})
// Desired result: ["ADB", "AB*", "BD*", "BP*", "STR"]
console.log(reducedArray) // ["ADB", "AB*", "BD*", "BD4", "AB3", "BP*", "BPX", "STR", "ABS"]
You could create a regex from the generalCase array to test if any of the strings in arr start with any of them.
In this case, it creates a regex like this:
/^(?:AB|BD|BP)/
Here's a snippet:
const arr = ["ADB", "AB1", "BD1", "BD4", "AB3", "BP9", "BPX", "STR", "ABS"],
generalCase = ["AB", "BD", "BP"],
regex = new RegExp(`^(?:${generalCase.join("|")})`),
set = new Set;
for (const str of arr) {
if (regex.test(str))
set.add(str.slice(0, 2) + "*")
else
set.add(str)
}
console.log([...set])
Use reduce():
const arr = ["ADB", "AB1", "BD1", "BD4", "AB3", "BP9", "BPX","STR", "ABS"];
const generalCase = ["AB", "BD", "BP"];
const reducedArray = arr.reduce((p, c) => {
const firsttwo = c.substring(0,2);
const toPush = (generalCase.includes(firsttwo)) ? (firsttwo + '*') : c;
if (!p.includes(toPush)) {
p.push(toPush);
}
return p;
}, []);
console.log(reducedArray);
Result:
[
"ADB",
"AB*",
"BD*",
"BP*",
"STR"
]
Iterate normally, and deduplicate the result in the end:
let arr =["ADB", "AB1", "BD1", "BD4", "AB3", "BP9", "BPX","STR", "ABS"]
let generalCase = ["AB", "BD", "BP"]
let reducedArray = arr.map(item => {
for (let x of generalCase)
if (item.startsWith(x))
return x + '*'
return item
})
reducedArray = [...new Set(reducedArray)]
console.log(reducedArray)

Longest prefix that match more than 50% of items in an array

Suppose I have array of strings: ["apple", "ape", "application", "item"].
I need to find the longest common prefix that matches more than 50% of the strings in that array.
For example, we got "ap" being the prefix of 3 strings in a 4 elements array -> so more than 50% of the array -> returns the prefix.
This is my attempt:
const longestPrefix = (arrStr) => {
if (arrStr.length === 0) return "";
let prefix = "";
let noPrefixMatched = {};
// loop the characters of first word
for (let i = 0; i < arrStr[0].length; i++) {
let char = arrStr[0][i];
let j = 0;
// loop all the words of the array except first one
for (j = 1; j < arrStr.length; j++) {
if (arrStr[j][i] !== char) {
// if first char not matched then set that word as notMatched
if (i === 0) {
noPrefixMatched[arrStr[j]] = true;
}
break;
}
// if the first characters are equal in all words and the loop reach the final word
if (arrStr[j][i] === char && j === arrStr.length - 1) {
prefix += char;
}
}
}
return prefix;
};
I try to get the most common prefix by vertical comparison, but it's not working with a word without any prefix in the array (like "item" in the above array). How should I do this?
One way to do this is to iterate all the words, constructing prefixes one letter at a time and counting the occurrence of each prefix as you see it. You can then filter that result based on the count being greater than 1/2 the length of the input array, and finally sort it by the prefix length descending, returning the first entry in the result:
const words = ["apple", "ape", "application", "item"]
const counts = words.reduce((acc, w) => {
prefix = ''
for (i = 0; i < w.length; i++) {
prefix += w[i]
acc[prefix] = (acc[prefix] || 0) + 1
}
return acc
}, {})
const bestPrefix = Object.entries(counts)
.filter(([_, v]) => v > words.length / 2.0)
.sort((a, b) => b[0].length - a[0].length)
[0][0]
console.log(bestPrefix)
The first word should not be considered special or be hardcoded in any way - it may not even be a match for the substring selected, after all.
A simple way to code this would be to iterate over all words and their possible prefixes and see which prefixes pass the test, while keeping the best one in an outer variable - then return it at the end.
const getPrefixes = str => Array.from(str, (_, i) => str.slice(0, i + 1));
const matches = (arr, prefix) => {
const matchCount = arr.reduce((a, str) => a + str.startsWith(prefix), 0);
return matchCount / arr.length > 0.5;
};
const longestPrefix = (arrStr) => {
let bestPrefix = '';
for (const str of arrStr) {
for (const prefix of getPrefixes(str)) {
if (prefix.length > bestPrefix.length && matches(arrStr, prefix)) {
bestPrefix = prefix;
}
}
}
return bestPrefix;
};
console.log(longestPrefix(["apple", "ape", "application", "item"]));
A less computationally complex but more complicated method would be to construct a tree structure of characters from each string in the input, and then iterate through the tree to identify which nodes have enough nested children, and then pick the longest such node. This has the advantage of only requiring iterating over each character of each string in the input once.
const getBestChild = (obj, totalRequired, prefix = '') => {
if (obj.count < totalRequired) return;
const thisResult = { count: obj.count, prefix };
if (!obj.children) {
return thisResult;
}
let bestChild = thisResult;
for (const [nextChar, child] of Object.entries(obj.children)) {
const result = getBestChild(child, totalRequired, prefix + nextChar);
if (result && result.prefix.length > bestChild.prefix.length) {
bestChild = result;
}
}
return bestChild;
};
const longestPrefix = (arrStr) => {
const root = {};
for (const str of arrStr) {
let obj = root;
for (const char of str) {
if (!obj.children) {
obj.children = {};
}
const { children } = obj;
if (!children[char]) {
children[char] = { count: 0 };
}
children[char].count++;
obj = children[char];
}
}
const { length } = arrStr;
const totalRequired = length % 2 === 0 ? (1 + length / 2) : Math.ceil(length / 2);
return getBestChild(root, totalRequired).prefix;
};
console.log(longestPrefix(["apple", "ape", "application", "item"]));

Information about to Array in JAvascript

I would like to get find elements having same characters but in different order in an array. I made javascript below,is there any way to create Javascript function more basic? Can you give me an idea? Thank you in advance..
<p id="demo"></p>
<script>
const arr1 = ["tap", "pat", "apt", "cih", "hac", "ach"];
var sameChars = 0;
var subArr1 = [];
for(var i = 0; i < arr1.length; i++){
for(var j = i+1; j < arr1.length; j++){
if(!subArr1.includes(arr1[i]) && !subArr1.includes(sortAlphabets(arr1[i]))){
subArr1.push(arr1[i]);
sameChars++;
}
if(sortAlphabets(arr1[i]) == sortAlphabets(arr1[j])){
if(!subArr1.includes(arr1[j])){
subArr1.push(arr1[j]);
}
}
}
}
function sortAlphabets(text1) {
return text1.split('').sort().join('');
};
document.getElementById("demo").innerHTML = sameChars;
</script>
I would just use reduce. Loop over split the string, sort it, join it back. Use it as a key in an object with an array and push the items onto it.
const arr1 = ["tap", "pat", "apt", "cih", "hac", "ach"];
const results = arr1.reduce((obj, str) => {
const key = str.split('').sort().join('');
obj[key] = obj[key] || [];
obj[key].push(str);
return obj;
}, {});
console.log(Object.values(results));
You can get the max frequency value by building a map and getting the max value of the values.
const frequencyMap = (data, keyFn) =>
data.reduce(
(acc, val) =>
(key => acc.set(key, (acc.get(key) ?? 0) + 1))
(keyFn(val)),
new Map());
const groupMap = (data, keyFn) =>
data.reduce(
(acc, val) =>
(key => acc.set(key, [...(acc.get(key) ?? []), val]))
(keyFn(val)),
new Map());
const
data = ["tap", "pat", "apt", "cih", "hac", "ach"],
sorted = (text) => text.split('').sort().join(''),
freq = frequencyMap(data, sorted),
max = Math.max(...freq.values()),
groups = groupMap(data, sorted);
document.getElementById('demo').textContent = max;
console.log(Object.fromEntries(freq.entries()));
console.log(Object.fromEntries(groups.entries()));
.as-console-wrapper { top: 2em; max-height: 100% !important; }
<div id="demo"></div>
Maybe split the code into two functions - one to do the sorting and return a new array, and another to take that array and return an object with totals.
const arr = ['tap', 'pat', 'apt', 'cih', 'hac', 'ach'];
// `sorter` takes an array of strings
// splits each string into an array, sorts it
// and then returns the joined string
function sorter(arr) {
return arr.map(str => {
return [...str].sort().join('');
});
}
// `checker` declares an object and
// loops over the array that `sorter` returned
// creating keys from the strings if they don't already
// exist on the object, and then incrementing their value
function checker(arr) {
const obj = {};
for (const str of arr) {
// All this line says is if the key
// already exists, keep it, and add 1 to the value
// otherwise initialise it with 0, and then add 1
obj[str] = (obj[str] || 0) + 1;
}
return obj;
}
// Call `checker` with the array from `sorter`
console.log(checker(sorter(arr)));
<p id="demo"></p>
Additional documentation
map
Loops and iteration
Spread syntax

Convert array of structured string into multi-level object in Javascript [duplicate]

This question already has answers here:
Create an object based on file path string
(3 answers)
Closed 1 year ago.
I have an array of structured strings with have connection | as a format which self-divided into levels. I want to convert it into a structured object with multiple levels.
Input:
[
"clothes|tshirt|tshirt-for-kids",
"clothes|coat|raincoat",
"clothes|coat|leather-coat",
"clothes|tshirt|tshirt-for-men",
"clothes|tshirt|tshirt-for-men|luxury-tshirt",
]
Expected output:
{
clothes: {
tshirt: {
tshirt-for-kids: {},
tshirt-for-men: {
luxury-tshirt: {}
}
},
coat: {
raincoat: {}
leather-coat: {}
}
}
}
Very simple task - just enumerate the array and create the relevant object keys:
var myArray = [
"clothes|tshirt|tshirt-for-kids",
"clothes|coat|raincoat",
"clothes|coat|leather-coat",
"clothes|tshirt|tshirt-for-men",
"clothes|tshirt|tshirt-for-men|luxury-tshirt",
]
var result = {}, levels, current, temp;
while(myArray.length > 0)
{
levels = myArray.pop().split('|');
temp = result;
while(levels.length > 0)
{
current = levels.shift();
if(!(current in temp)) temp[current] = {};
temp = temp[current];
}
}
console.log(result);
You could try this:
const input = [
"clothes|tshirt|tshirt-for-kids",
"clothes|coat|raincoat",
"clothes|coat|leather-coat",
"clothes|tshirt|tshirt-for-men",
"clothes|tshirt|tshirt-for-men|luxury-tshirt",
];
function convertStrToObject(str, sep, obj) {
const sepIndex = str.indexOf(sep);
if (sepIndex == -1) {
obj[str] = obj[str] || {};
} else {
const key = str.substring(0, sepIndex);
obj[key] = obj[key] || {};
convertStrToObject(str.substring(sepIndex + 1), sep, obj[key]);
}
}
const all = {};
for (let i = 0; i < input.length; ++i) {
convertStrToObject(input[i], "|", all);
}
console.log(all);
Assuming you intend to collect properties, all having an empty object as leaf node.
// input
const input = [
"clothes|tshirt|tshirt-for-kids",
"clothes|coat|raincoat",
"clothes|coat|leather-coat",
"clothes|tshirt|tshirt-for-men",
"clothes|tshirt|tshirt-for-men|luxury-tshirt",
];
// Here, we collect the properties
const out = {};
// map the input array, splitting each line at |
input.map(i => i.split('|'))
.filter(a => a.length > 0) // lets not entertain empty lines in input
.forEach(a => { // process each array of property names
// start at outermost level
let p = out;
// iterate properties
for(const v of a){
// create a property if it is not already there
if(!p.hasOwnProperty(v)){
p[v] = {};
}
// move to the nested level
p = p[v];
}
});
// lets see what we have created
console.log(out);
A number of solutions have been suggested already, but I'm surprised none involves reduce() - which would seem the more idiomatic solution to me.
var array = [
"clothes|tshirt|tshirt-for-kids",
"clothes|coat|raincoat",
"clothes|coat|leather-coat",
"clothes|tshirt|tshirt-for-men",
"clothes|tshirt|tshirt-for-men|luxury-tshirt",
]
var object = array.reduce(function (object, element) {
var keys = element.split("|")
keys.reduce(function (nextNestedObject, key) {
if (!nextNestedObject[key]) nextNestedObject[key] = {}
return nextNestedObject[key]
}, object)
return object
}, {})
console.log(object)
One Liner With eval
Used eval to evaluate strings like the following:
'o["clothes"]??={}'
'o["clothes"]["tshirt"]??={}'
'o["clothes"]["tshirt"]["tshirt-for-kids"]??={}'
const
data = ["clothes|tshirt|tshirt-for-kids", "clothes|coat|raincoat", "clothes|coat|leather-coat", "clothes|tshirt|tshirt-for-men", "clothes|tshirt|tshirt-for-men|luxury-tshirt"],
arr = data.map((d) => d.split("|")),
res = arr.reduce((r, a) => (a.forEach((k, i) => eval(`r["${a.slice(0, i + 1).join('"]["')}"]??={}`)), r), {});
console.log(res)
One Liner Without eval
const
data = ["clothes|tshirt|tshirt-for-kids", "clothes|coat|raincoat", "clothes|coat|leather-coat","clothes|tshirt|tshirt-for-men", "clothes|tshirt|tshirt-for-men|luxury-tshirt"],
res = data.reduce((r, d) => (d.split("|").reduce((o, k) => (o[k] ??= {}, o[k]), r), r), {});
console.log(res)

Categories

Resources