Related
I have an array of Java-style package names and want to sort it in a specific way. The sort order I want is generally alphabetically, but with the additional requirement that lowercase always goes either before or after uppercase, depending on a user supplied flag to the sorting function (which allows also a third option of leaving the original order alone).
Example:
const importStrings = [
"foo.bar.Zonk",
"lorem.ipsum.acme.Rocket",
"foo.bar.BazFoo",
"lorem.ipsum.Blah",
"foo.bar.baz.Gnarf",
"lorem.ipsum.acme.Amboss",
];
The order I want is either uppercase first on any given .:
[
"foo.bar.BazFoo",
"foo.bar.Zonk",
"foo.bar.baz.Gnarf",
"lorem.ipsum.Blah",
"lorem.ipsum.acme.Amboss",
"lorem.ipsum.acme.Rocket",
]
or lowercase first:
[
"foo.bar.baz.Gnarf",
"foo.bar.BazFoo",
"foo.bar.Zonk",
"lorem.ipsum.acme.Amboss",
"lorem.ipsum.acme.Rocket",
"lorem.ipsum.Blah",
]
Effectively, I want the sort order to be A-Za-z/a-zA-Z instead of AaBb..Zz/aAbB..zZ, which seems to be surprisingly difficult to achieve unless I'm missing something really, really obvious. All my previous experiments with various custom Array.sort() functions and localeCompare() options didn't yield the output I need.
My current approach kinda-works, but contains a ton of boilerplate and cruft, and I'm thinking that there has to be a better way of doing this. I currently split the individual strings on ., recursively build a object graph containing each "level" of the package name in an array, and then sort that array. That gives me the ordering I want, which I can then merge back together:
function createImportData(data, parts, sortFunction, reduceFunction) {
const packageName = parts.shift();
let localData = data.find((obj) => obj.value === packageName);
if (!localData) {
localData = { value: packageName, children: [] };
data.push(localData);
}
if (parts.length) {
createImportData(localData.children, parts, sortFunction, reduceFunction);
localData.children = localData.children
.reduce(reduceFunction, [[], []])
.flatMap((array) => array.sort(sortFunction));
}
}
function getSortFunction(options) {
if (!shouldOrganizeImports(options)) {
// leave all
return (obj1, obj2) => 0;
} else {
return (obj1, obj2) => {
obj1.value.localeCompare(obj2.value);
};
}
}
function getReduceFunction(options) {
if (!shouldOrganizeImports(options)) {
// just put it in a single array without changing anything
return (groups, obj) => {
groups[0].push(obj);
return groups;
};
} else {
const upperIndex = shouldOrganizeImportsUppercaseFirst(options) ? 0 : 1;
const lowerIndex = shouldOrganizeImportsUppercaseFirst(options) ? 1 : 0;
const reduce = (upperIndex, lowerIndex) => (groups, obj) => {
obj.value.charAt(0).toUpperCase() === obj.value.charAt(0)
? groups[upperIndex].push(obj)
: groups[lowerIndex].push(obj);
return groups;
};
return reduce(upperIndex, lowerIndex);
}
}
//initial call looks like this:
const importData = [];
const sortFunction = getSortFunction(options);
const reduceFunction = getReduceFunction(options);
for (const importString of importStrings) {
createImportData(
importData,
importString.split("."),
sortFunction,
reduceFunction
);
}
The generated structure looks like this, with each level's children array sorted the way I need:
[
{
value: "foo",
children: [
{
value: "bar",
children: [
{ value: "baz", children: [{ value: "Gnarf", children: [] }] },
{ value: "BazFoo", children: [] },
{ value: "Zonk", children: [] },
],
},
],
},
{value: "lorem", children: [/* etc.etc. */]}
]
How can I improve on my logic, or is this already the way to do it?
That's a fairly idiosyncratic sort, but it's possible to do it a bit more concisely using Intl.Collator along with breaking the package names into their individual parts and custom logic to always put something starting with upper case before/after something starting with lower case regardless of length:
// Tells us whether a string starst with an English uppercase char
// (may need tweaking for non-English)
const startsUppercase = /^[A-Z]/;
function sort(array, upperFirst) {
// Get a collator for the current locale with the appropriate case first flag,
// and grab its `compare` function
const collator = new Intl.Collator(undefined, { caseFirst: upperFirst ? "upper" : "lower"});
const { compare } = collator;
// Get a direction flag based on the upper-first/lower-first flag
const direction = upperFirst ? -1 : 1;
// Sort
array.sort((a, b) => {
// Get the two package names split into their parts
const aparts = a.split(".");
const bparts = b.split(".");
// Assume they're equal...
let result = 0;
// Loop through as long as A) they're still equal, and
// B) we have both `a` and `b` parts
const length = Math.min(a.length, b.length);
for (let i = 0; result === 0 && i < length; ++i) {
const apart = aparts[i];
const bpart = bparts[i];
const aupper = startsUppercase.test(apart);
const bupper = startsUppercase.test(bpart);
if (aupper && !bupper) {
// `a` comes first/last
result = direction;
} else if (!aupper && bupper) {
// `b` comes first/last
result = -direction;
} else {
// Same case, compare remainder
result = compare(apart, bpart);
}
}
// If still no difference, compare on segment length
if (result === 0) {
return aparts.length - bparts.length;
}
return result;
});
return array;
}
Live Example:
const importStrings = [
"foo.bar.Zonk",
"lorem.ipsum.acme.Rocket",
"foo.bar.BazFoo",
"lorem.ipsum.Blah",
"foo.bar.baz.Gnarf",
"lorem.ipsum.acme.Amboss",
];
// Tells us whether a string starst with an English uppercase char
// (may need tweaking for non-English)
const startsUppercase = /^[A-Z]/;
function sort(array, upperFirst) {
// Get a collator for the current locale with the appropriate case first flag,
// and grab its `compare` function
const collator = new Intl.Collator(undefined, { caseFirst: upperFirst ? "upper" : "lower"});
const { compare } = collator;
// Get a direction flag based on the upper-first/lower-first flag
const direction = upperFirst ? -1 : 1;
// Sort
array.sort((a, b) => {
// Get the two package names split into their parts
const aparts = a.split(".");
const bparts = b.split(".");
// Assume they're equal...
let result = 0;
// Loop through as long as A) they're still equal, and
// B) we have both `a` and `b` parts
const length = Math.min(a.length, b.length);
for (let i = 0; result === 0 && i < length; ++i) {
const apart = aparts[i];
const bpart = bparts[i];
const aupper = startsUppercase.test(apart);
const bupper = startsUppercase.test(bpart);
if (aupper && !bupper) {
// `a` comes first/last
result = direction;
} else if (!aupper && bupper) {
// `b` comes first/last
result = -direction;
} else {
// Same case, compare remainder
result = compare(apart, bpart);
}
}
// If still no difference, compare on segment length
if (result === 0) {
return aparts.length - bparts.length;
}
return result;
});
return array;
}
console.log(`Upper first:`);
console.log(sort(importStrings.slice(), true));
console.log(`Lower first:`);
console.log(sort(importStrings.slice(), false));
.as-console-wrapper {
max-height: 100% !important;
}
If the array is large, it might be worth pre-splitting the package names so you do it only once rather than repeatedly (since the same string is often presented to the sort callback in the process of sorting large arrays). It's not a big change, see ***:
// Tells us whether a string starst with an English uppercase char
// (may need tweaking for non-English)
const startsUppercase = /^[A-Z]/;
function sort(array, upperFirst) {
// Get a collator for the current locale with the appropriate case first flag,
// and grab its `compare` function
const collator = new Intl.Collator(undefined, { caseFirst: upperFirst ? "upper" : "lower"});
const { compare } = collator;
// Get a direction flag based on the upper-first/lower-first flag
const direction = upperFirst ? -1 : 1;
// *** Get the pre-split package names
const names = new Map(array.map(name => [name, name.split(".")]));
// Sort
array.sort((a, b) => {
// Get the two package names split into their parts
const aparts = names.get(a); // ***
const bparts = names.get(b); // ***
// Assume they're equal...
let result = 0;
// Loop through as long as A) they're still equal, and
// B) we have both `a` and `b` parts
const length = Math.min(a.length, b.length);
for (let i = 0; result === 0 && i < length; ++i) {
const apart = aparts[i];
const bpart = bparts[i];
const aupper = startsUppercase.test(apart);
const bupper = startsUppercase.test(bpart);
if (aupper && !bupper) {
// `a` comes first/last
result = direction;
} else if (!aupper && bupper) {
// `b` comes first/last
result = -direction;
} else {
// Same case, compare remainder
result = compare(apart, bpart);
}
}
// If still no difference, compare on segment length
if (result === 0) {
return aparts.length - bparts.length;
}
return result;
});
return array;
}
Live Example:
const importStrings = [
"foo.bar.Zonk",
"lorem.ipsum.acme.Rocket",
"foo.bar.BazFoo",
"lorem.ipsum.Blah",
"foo.bar.baz.Gnarf",
"lorem.ipsum.acme.Amboss",
];
// Tells us whether a string starst with an English uppercase char
// (may need tweaking for non-English)
const startsUppercase = /^[A-Z]/;
function sort(array, upperFirst) {
// Get a collator for the current locale with the appropriate case first flag,
// and grab its `compare` function
const collator = new Intl.Collator(undefined, { caseFirst: upperFirst ? "upper" : "lower"});
const { compare } = collator;
// Get a direction flag based on the upper-first/lower-first flag
const direction = upperFirst ? -1 : 1;
// *** Get the pre-split package names
const names = new Map(array.map(name => [name, name.split(".")]));
// Sort
array.sort((a, b) => {
// Get the two package names split into their parts
const aparts = names.get(a); // ***
const bparts = names.get(b); // ***
// Assume they're equal...
let result = 0;
// Loop through as long as A) they're still equal, and
// B) we have both `a` and `b` parts
const length = Math.min(a.length, b.length);
for (let i = 0; result === 0 && i < length; ++i) {
const apart = aparts[i];
const bpart = bparts[i];
const aupper = startsUppercase.test(apart);
const bupper = startsUppercase.test(bpart);
if (aupper && !bupper) {
// `a` comes first/last
result = direction;
} else if (!aupper && bupper) {
// `b` comes first/last
result = -direction;
} else {
// Same case, compare remainder
result = compare(apart, bpart);
}
}
// If still no difference, compare on segment length
if (result === 0) {
return aparts.length - bparts.length;
}
return result;
});
return array;
}
console.log(`Upper first:`);
console.log(sort(importStrings.slice(), true));
console.log(`Lower first:`);
console.log(sort(importStrings.slice(), false));
.as-console-wrapper {
max-height: 100% !important;
}
This is an approach where the character are extended to two characters, depening on their case:
lower, after sorting
---------------------------------------------
f o o . b a r . b a z .G n a r f
f o o . b a r .B a zF o o
f o o . b a r .Z o n k
l o r e m . i p s u m . a c m e .A m b o s s
l o r e m . i p s u m . a c m e .R o c k e t
l o r e m . i p s u m .B l a h
upper, after sorting
---------------------------------------------
f o o . b a r . Ba z Fo o
f o o . b a r . Zo n k
f o o . b a r . b a z . Gn a r f
l o r e m . i p s u m . Bl a h
l o r e m . i p s u m . a c m e . Am b o s s
l o r e m . i p s u m . a c m e . Ro c k e t
The the original data is mapped by the sorted indices.
const
sort = (array, priority) => {
const order = priority === 'lower';
return array
.map((s, index) => {
const o = { index, value: '' };
for (const c of s) o.value += c === c.toLowerCase() === order ? ' ' + c : c + ' ';
return o;
})
.sort((a, b) => a.value.localeCompare(b.value))
.map(({ index }) => array[index]);
},
data = ["foo.bar.Zonk", "lorem.ipsum.acme.Rocket", "foo.bar.BazFoo", "lorem.ipsum.Blah", "foo.bar.baz.Gnarf", "lorem.ipsum.acme.Amboss"],
sorted1 = sort(data, 'lower'),
sorted2 = sort(data, 'upper');
console.log(sorted1);
console.log(sorted2);
.as-console-wrapper { max-height: 100% !important; top: 0; }
i am trying to use recursion to return each character in a string. However, the output is not
//We define a function with input parameter.
function countCharInString(string) {
//vi Define an empty objec
const result = {};
//we loop through the length of string
for (let i = 0; i < string.length; i++) {
//create another variable for each element in string
const ch = string[i];
//BASE CASE: if string is empty, return Object with nothing
if (!result[ch]) {
return result[ch]=0;
} else {
//RECURSION: 1 plus whatever the length of the substring from the next character onwards is
return countCharInString(result[ch] + 1)
}
}
}
console.log(countCharInString("Vi skal tælle bogstaver"))
the output should be the following:
var result = {
l : 3,
a : 2,
e : 2,
s : 2,
t : 2,
v : 2,
b: 1,
i : 1,
k : 1,
o : 1,
r : 1,
æ : 1
};
i would suggest to do it with a simple reduce like so
var inputString = 'donald duck';
var result = inputString.split('').reduce((acc, char, index) => {
if (acc[char] !== undefined) {
acc[char] = acc[char] + 1;
}
else {
acc = { ...acc, [char]: 1 }
}
return acc
}, {})
see: https://jsfiddle.net/yswu91zh/21/
Only recursion would not give you the output that you are asking for. After recursively counting character you have to sort it by frequency and then by character. I have excluded a bunch of punctuation with space from counting, if you want exclude more just add it to the punctuation string. You have to use String.prototype.localeCompare() method to compare the characters. This method compares two strings in the current locale. As you are using Danish language you have to specify locale as da.
const punctuations = '.,:;!? ';
const countCharInString = (str, p = {}) => {
if (str.length === 0) return p;
const key = str[0].toLowerCase();
if (!punctuations.includes(key)) {
if (!p[key]) p[key] = 1;
else p[key] += 1;
}
return countCharInString(str.slice(1), p);
};
const cmp = (x, y) => {
if (x[1] === y[1]) {
return x[0].localeCompare(y[0], 'da');
}
return x[1] < y[1] ? 1 : -1;
};
const ret = Object.fromEntries(
Object.entries(countCharInString('Vi skal tælle bogstaver')).sort(cmp)
);
console.log(ret);
I am trying to find values that commonly appear next to each other in an array.
E.G. given the array:
["dog","cat","goat","dog","cat","elephant","dog","cat","pig","seal","dog","cat","pig","monkey"]
it should return something similar to:
[[["dog","cat"],4],[["cat","pig"],2],[["dog","cat","pig"],2]]
Here is some better data: https://pastebin.com/UG4iswrZ
Help would be greatly appreciated. Here is my current failed attempt at doing something similar:
function findAssociations(words){
var temp = [],tempStore = [],store = [],found = false;
//loop through the words counting occurrances of words together with a window of 5
for(var i = 0;i<words.length-1;i++){
if(i % 5 == 0){
//on every fith element, loop through store attempting to add combinations of words stored in tempStore
for(var j = 0;j<5;j++){
temp = []
//create the current combination
for(var k = 0;k<j;k++){
temp.push(tempStore[k]);
}
//find if element is already stored, if it is, increment the occurrence counter
for(var k = 0;k<store.length;k++){
if(store[k][0]===temp){
found = true;
store[k][1] = store[k][1]+1;
}
}
//if it isn't add it
if(found == false){
store.push([temp,1]);
}
found == false;
}
tempStore = [];
} else {
//add word to tempStore if it i isnt a multiple of 5
tempStore.push(words[i]);
}
}
}
This script is doesn't remove combinations that appear once,it doesn't sort the output by occurrences, nor does it work. It is just an outline of how a possible solution might work (as suggested by benvc).
Here is a generic solution working with multiple group sizes.
You specify a range of group sizes, for example [2,4] for groups of 2 to 4 elements and a minimum number of occurrences.
The function then generates all groups of neighbours of the given sizes, sorts each group and counts the duplicates. The sorting step can be removed is the order in the groups matters.
The duplicates are counted by creating a dictionary whose keys are the group elements sorted and jointed with a special marker. The values in the dictionary are the counts.
It then returns the groups sorted by occurences and then by group size.
const data = ["dog","cat","goat","dog","cat","elephant","dog","cat","pig","seal","dog","cat","pig","monkey"];
function findSimilarNeighbors(groupSizeRange, minOccurences, data) {
const getNeighbors = (size, arr) => arr.reduce((acc, x) => {
acc.push([]);
for (let i = 0; i < size; ++ i) {
const idx = acc.length - i - 1;
(acc[idx] || []).push(x);
}
return acc;
}, []).filter(x => x.length === size);
const groups = [];
for (let groupSize = groupSizeRange[0]; groupSize <= groupSizeRange[1]; ++groupSize) {
groups.push(...getNeighbors(groupSize, data));
}
const groupName = group => group.sort().join('###'); // use a separator that won't occur in the strings
const groupsInfo = groups.reduce((acc, group) => {
const name = groupName(group);
acc[name] = acc[name] || {};
acc[name] = { group, count: (acc[name].count || 0) + 1 };
return acc;
}, {});
return Object.values(groupsInfo)
.filter(group => group.count >= minOccurences)
.sort((a, b) => {
const countDiff = b.count - a.count;
return countDiff ? countDiff : b.group.length - a.group.length;
})
.map(({ group, count }) => [group, count]);
};
console.log(findSimilarNeighbors([2, 4], 2, data));
console.log(findSimilarNeighbors([4, 4], 2, data));
Here is what I came up with. It only finds pairs, but you could modify it to find sets of 3, 4, etc, based on what you % by
const animals = ['dog','cat','goat','dog','cat','elephant','dog','cat','pig','seal','dog','cat','pig','monkey'];
let pairs = ',';
animals.forEach((animal, i) => {
let separator = ',';
if (i % 2 === 0) {
separator = ';'
}
pairs += animal + separator;
});
const evenPairs = pairs.split(',');
const oddPairs = pairs.split(';');
const allPairs = evenPairs.concat(oddPairs).map(pair => pair.replace(/[;,]/, ' '));
let result = {}
allPairs.forEach(pair => {
if (pair.length) {
if (result[pair] === undefined) {
result[pair] = 1;
} else {
result[pair]++;
}
}
});
results in:
dog: 1
cat elephant: 1
cat goat: 1
cat pig: 2
dog cat: 4
elephant dog: 1
goat dog: 1
monkey : 1
pig monkey: 1
pig seal: 1
seal dog: 1
https://stackblitz.com/edit/typescript-wvuvnr
You need to be clear what you mean by close and how close. Just looking at first neighbours you could try:
const findAssociations = words => {
const associations = {}
for (let i = 0; i < words.length - 1; i++) {
const word = words[i]
const wordRight = words[i+1]
const wordOne = word < wordRight ? word : wordRight;
const wordTwo = word < wordRight ? wordRight : word;
const keys = Object.keys(associations)
const key = `${wordOne}:${wordTwo}`
if (keys.indexOf(key) >= 0) {
associations[key]++
} else {
associations[key] = 1
}
}
const keys = Object.keys(associations)
const values = Object.values(associations)
const zipped = keys.map((key, index) => [key, values[index]])
zipped.sort((a, b) => a[1] < b[1] ? 1 : -1);
return zipped;
}
https://stackblitz.com/edit/js-3ppdit
You can use this function inside another function and add every time an element to ["dog", "cat"]
const arr = ["dog", "cat", "goat", "dog", "cat", "dog", "cat", "elephant", "dog", "cat", "pig", "seal", "dog", "cat", "pig", "monkey"]
const findArrayInArray = (arr1, arr2) => {
let count = 0,
arrString1 = arr1.join(""),
arrString2 = arr2.join("");
while (arrString2.indexOf(arrString1) > -1) {
count += 1;
arrString2 = arrString2.replace(arrString1, '');
}
return count;
}
console.log(`["dog", "cat"] exist ${findArrayInArray(["dog", "cat"], arr)} times`)
Assuming each item in the list is a delimiter of a set, and each set counts once for each item (i.e. ["dog", "cat", "goat"] counts as ["dog", "cat"] and ["dog", "cat", "goat"], and assuming you don't want any single occurrences, then here's one way:
const full_list = ["dog","cat","goat","dog","cat","dog","cat","elephant","dog","cat","pig","seal","dog","cat","pig","monkey"];
// create list of unique items
const distinct = (value, index, self) => {
return self.indexOf(value) ===index;
}
const unique_items = full_list.filter(distinct);
// get all patterns
var pre_report = {};
for (var i in unique_items) {
item = unique_items[i];
var pattern = [item];
var appending = false;
for (var j = full_list.indexOf(item) + 1; j < full_list.length; ++j) {
const related_item = full_list[j];
if (item == related_item) {
pattern = [item]
continue;
}
pattern.push(related_item);
if (pattern in pre_report) {
++pre_report[pattern];
} else {
pre_report[pattern] = 1;
}
}
}
// filter out only single occurring patterns
var report = {};
for (key in pre_report) {
if (pre_report[key] > 1) {
report[key] = pre_report[key];
}
}
console.log(report);
produces:
{ 'dog,cat': 5, 'dog,cat,pig': 2, 'cat,pig': 2 }
I write a program to find the longest word made of other words that is also present in array.
sort_arr.forEach(word => {
if (isLargest(word, word)) {
console.log(word);
}
});
function isLargest(initialWord, word) {
let first_half = 0;
let second_half = 0;
let start = 0;
let end = 0;
for (let i = 0; i < word.length; i++) {
end++;
first_half = word.substring(start, end);
for (let j = 0; j < sort_arr.length; j++) {
if (first_half === sort_arr[j]) {
second_half = word.substring(end, word.length);
if(second_half === ''){
return word !== initialWord;
}
else{
return isLargest(initialWord, second_half);
}
}
}
}
}
But there is a problem when array words contain
[ 'catxdogcatsrat',
'catsdogcats',
'dogcatsdog',
'cats',
'cat',
'dog',
'rat' ]
It gives output null
But the result should comes catsdogcats
I know the problem is occuring when in catsdogcats, prefix is cat and suffix is sdogcats. But it is not checking for prefix cats and suffix dogcats.
Can please some one suggest me some ways to do this without using ties.
This is a bit more complicated than first anticipated. You have to see what other words are the same as the start of the current word and try with every of those words until you get the complete word made up of the other words.
const canMakeWord = (array, word) => {
//recursive function
const recur = (array, word) => {
//word passed is empty, so we could make current word
// with the list of other words
if (word === '') {
return true;
}
//see what words in the list of other words
// can start to make up this word
const candidates = array.filter(
(otherWord) => word.indexOf(otherWord) === 0,
);
if (!candidates.length) {
console.warn('giving up for word', word);
}
return (
//no candidates, return false
!!candidates.length &&
//try each candidate recursively
candidates.reduce(
(result, otherWord) =>
result || //try for every result until it's true
//use whole list of other words but remove
// the other word used for this check from
// current word
console.log(
'trying with word:',
word,
'using candidate:',
JSON.stringify(otherWord),
) ||
recur(array, word.replace(otherWord, '')),
false,
)
);
};
//return recursive function
return recur(
array
//do not use other words that are longer than current word
.filter((w) => w.length <= word.length)
//do not include current word
.filter((w) => w !== word),
word,
);
};
const words = ['x', 'xxs', 'xxsxxsxx'];
const result = words
.map((word) => [word.length, word])
.sort(([a], [b]) => b - a)
.map(([_, word]) => word)
.find(
(word, index, all) =>
canMakeWord(all, word),
);
// .map((word, index, all) => [canMakeWord(all, word), word])
// //take out all words that could not have been made up out of
// // other words
// .filter(([canMake]) => canMake)
// .map(
// //map to [wordLength,word]
// ([_, word]) => [word.length, word],
// )
// .sort(
// ([a], [b]) => b - a, //sort to longest word
// );
console.log('RESULT:');
console.log(result);
Not sure if this is what you want, but findLongestCombination(arr) returns the longest combination of a word being built out of other words (using each word just once) in an array. In this case: ["123, "111", "1", "3"]
It does that by trying out every possible way of building a word, using findLongestCombinationSingle(word, otherWords) recursively, which finds the longest combination of building one word out of the remaining ones.
If you have questions, or I did not understand the issue, feel free to comment.
arr = ['123', '111', '12311113', '1', '2', '3'];
console.log(findLongestCombination(arr));
function findLongestCombination(arr){
var result = [];
for(var i=0; i<arr.length; i++){
var arrOthers = arr.slice(0,i).concat(arr.slice(i+1));
var comb = findLongestCombinationSingle(arr[i], arrOthers);
if(comb.length > result.length) result = comb;
}
return result;
}
function findLongestCombinationSingle(word, otherWords){
var result = [];
for(var i=0; i<otherWords.length; i++){
if(word.startsWith(otherWords[i])){
var wordsLeft = otherWords.slice(0,i).concat(otherWords.slice(i+1));
var restWord = word.replace(otherWords[i], "");
var subresult = [otherWords[i]].concat(findLongestCombinationSingle(restWord, wordsLeft));
if(subresult.length > result.length) result = subresult;
}
}
return result;
}
It doesn't break if a word is non combinable... Gotta fix that, give me some time
Is there a short way to find the longest string in a string array?
Something like arr.Max(x => x.Length);?
Available since Javascript 1.8/ECMAScript 5 and available in most older browsers:
var longest = arr.reduce(
function (a, b) {
return a.length > b.length ? a : b;
}
);
Otherwise, a safe alternative:
var longest = arr.sort(
function (a, b) {
return b.length - a.length;
}
)[0];
A new answer to an old question: in ES6 you can do shorter:
Math.max(...(x.map(el => el.length)));
I would do something like this
var arr = [
'first item',
'second item is longer than the third one',
'third longish item'
];
var lgth = 0;
var longest;
for (var i = 0; i < arr.length; i++) {
if (arr[i].length > lgth) {
var lgth = arr[i].length;
longest = arr[i];
}
}
console.log(longest);
Maybe not the fastest, but certainly pretty readable:
function findLongestWord(array) {
var longestWord = "";
array.forEach(function(word) {
if(word.length > longestWord.length) {
longestWord = word;
}
});
return longestWord;
}
var word = findLongestWord(["The","quick","brown", "fox", "jumped", "over", "the", "lazy", "dog"]);
console.log(word); // result is "jumped"
The array function forEach has been supported since IE9+.
In ES6 this could be accomplished with a reduce() call in O(n) complexity as opposed to solutions using sort() which is O(nlogn):
const getLongestText = (arr) => arr.reduce(
(savedText, text) => (text.length > savedText.length ? text : savedText),
'',
);
console.log(getLongestText(['word', 'even-longer-word', 'long-word']))
var arr = [ 'fdgdfgdfg', 'gdfgf', 'gdfgdfhawsdgd', 'gdf', 'gdfhdfhjurvweadsd' ];
arr.sort(function (a, b) { return b.length - a.length })[0];
I was inspired of Jason's function and made a little improvements to it and got as a result rather fast finder:
function timo_longest(a) {
var c = 0, d = 0, l = 0, i = a.length;
if (i) while (i--) {
d = a[i].length;
if (d > c) {
l = i; c = d;
}
}
return a[l];
}
arr=["First", "Second", "Third"];
var longest = timo_longest(arr);
Speed results: http://jsperf.com/longest-string-in-array/7
I provide a functional+recursive approach. See comments to understand how it works:
const input1 = ['a', 'aa', 'aaa']
const input2 = ['asdf', 'qwer', 'zxcv']
const input3 = ['asdfasdf fdasdf a sd f', ' asdfsdf', 'asdfasdfds', 'asdfsdf', 'asdfsdaf']
const input4 = ['ddd', 'dddddddd', 'dddd', 'ddddd', 'ddd', 'dd', 'd', 'd', 'dddddddddddd']
// Outputs which words has the greater length
// greatestWord :: String -> String -> String
const greatestWord = x => y =>
x.length > y.length ? x : y
// Recursively outputs the first longest word in a series
// longestRec :: String -> [String] -> String
const longestRec = longestWord => ([ nextWord, ...words ]) =>
// ^^^^^^^^^^^^
// Destructuring lets us get the next word, and remaining ones!
nextWord // <-- If next word is undefined, then it won't recurse.
? longestRec (greatestWord (nextWord) (longestWord)) (words)
: longestWord
// Outputs the first longest word in a series
// longest :: [String] -> String
const longest = longestRec ('')
const output1 = longest (input1)
const output2 = longest (input2)
const output3 = longest (input3)
const output4 = longest (input4)
console.log ('output1: ', output1)
console.log ('output2: ', output2)
console.log ('output3: ', output3)
console.log ('output4: ', output4)
function max( input ) {
return input.reduce((a, b) => a.length <= b.length ? b : a)
}
I see the shortest solution
function findLong(s){
return Math.max.apply(null, s.split(' ').map(w => w.length));
}
If your string is already split into an array, you'll not need the split part.
function findLongestWord(str) {
str = str.split(' ');
var longest = 0;
for(var i = 0; i < str.length; i++) {
if(str[i].length >= longest) {
longest = str[i].length;
}
}
return longest;
}
findLongestWord("The quick brown fox jumped over the lazy dog");
In case you expect more than one maximum this will work:
_.maxBy(Object.entries(_.groupBy(x, y => y.length)), y => parseInt(y[0]))[1]
It uses lodash and returns an array.
With ES6 and it support a duplicate string
var allLongestStrings = arrayOfStrings => {
let maxLng = Math.max(...arrayOfStrings.map( elem => elem.length))
return arrayOfStrings.filter(elem => elem.length === maxLng)
}
let arrayOfStrings = ["aba", "aa", "ad", "vcd","aba"]
console.log(allLongestStrings(arrayOfStrings))
I would do something like this:
function findLongestWord(str) {
var array = str.split(" ");
var maxLength=array[0].length;
for(var i=0; i < array.length; i++ ) {
if(array[i].length > maxLength) maxLength = array[i].length
}
return maxLength;
}
findLongestWord("What if we try a super-long word such as otorhinolaryngology");
Modern browsers support a for...of loop. The fastest and shortest way to solve this problem in Chrome, Safari, Edge, and Firefox is also the clearest:
let largest = '';
for (let item of arr) {
if (item.length > largest.length) largest = item
}
In IE, you can use Array.forEach; that's still faster and clearer than sorting or reducing the array.
var largest = '';
arr.forEach(function(item) {
if (item.length > largest.length) largest = item
});
If you want to know the INDEX of the longest item:
var longest = arr.reduce(
(a, b, i) => arr[a].length < b.length ? i : a,
0
);
(which can be a one-liner for those that love that stuff.... but it's split up here for readabilty)