Created
February 23, 2016 20:37
-
-
Save jmacqueen/b8db614814fada543aa8 to your computer and use it in GitHub Desktop.
Functional approach to subdividing list of option strings into optgroups based on greedy matching the beginning of the strings
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* jshint esversion: 6 */ | |
// Two strings enter...one string leaves | |
const longerString = (a,b) => { | |
if (a.length > b.length) { return a; } | |
return b; | |
}; | |
// The longest possible match from the beginnning of the strings is returned | |
const stringHeadMatch = function(a,b) { | |
var i = 0; | |
while (a.charAt(i) && b.charAt(i) && a.charAt(i) === b.charAt(i)) { i++; } | |
return a.substr(0, i); | |
}; | |
// Match string at current array index against another relative entry | |
// includes guards to keep us in the array | |
const matchWith = (index, shift, strArr) => { | |
if ( index + shift < 0 || index + shift >= strArr.length ) return ''; | |
return stringHeadMatch(strArr[index], strArr[index + shift]); | |
}; | |
// return new array correlating each string with | |
// a) longer of the match with previous or match with next | |
// b) if no matches, use the string itself as the match | |
// looks like [[match, string], [match, string], [string, string], ...] | |
const maxMatches = (stringArray) => stringArray.map( (str, index, strArr) => { | |
const match = matchPrevOrNextMax(strArr, index); | |
return [(match === '') ? str : match, str]; | |
}); | |
// returns a single array with all options sharing a "group"...or in this case, the second part of all pairs sharing the same first part | |
// maxMatchArray expects the form [[string, option],[string, option],[string, option],...] like we get from maxMatches | |
const optionsMatchingGroup = (groupName, maxMatchArray) => maxMatchArray.filter( (item) => item[0] === groupName ).map( (item) => item[1] ); | |
// returns the longer string head match with the previous and next strings in the array | |
const matchPrevOrNextMax = (strArr, index) => { | |
return longerString( matchWith(index, 1, strArr), matchWith(index, -1, strArr) ); | |
}; | |
// Pretty much what it says. The string gets the first group.length characters removed | |
const trimHeadOfStringByStringLength = (str1, str2) => { | |
const str3 = str2 || ''; | |
return str1.slice(str3.length); | |
}; | |
// returns an array of objects describing the option groups and options | |
// [ {group: string, options: [string, string]}, {group: null, options: [string]} ] | |
const groupedOptions = function(stringArray) { | |
const maxMatchArray = maxMatches(stringArray); | |
// yields {groupName: count} | |
const groupCounts = maxMatchArray.reduce( (memo, item) => { | |
memo[item[0]] = memo[item[0]] || 0; | |
memo[item[0]] += 1; | |
return memo; | |
}, {}); | |
return Object.keys(groupCounts).reduce( (memo, group) => { | |
const groupName = (groupCounts[group] > 1) ? group : null; | |
memo.push({group: groupName, options: optionsMatchingGroup(group, maxMatchArray)}); | |
return memo; | |
}, []); | |
}; | |
// converts a single option group object to <optgroup> and <option>s HTML string | |
const optionsGroupToHTML = (optGroup) => { | |
const optionHTML = optGroup.options.reduce( (memo, opt) => { | |
memo += '<option>' + trimHeadOfStringByStringLength(opt, optGroup.group) + '</option>'; | |
return memo; | |
}, ''); | |
if (optGroup.group) { | |
return `<optgroup label="${optGroup.group}">${optionHTML}</optgroup>`; | |
} | |
return optionHTML; | |
}; | |
// iterate over the whole array of option groups and concatenate them into a string of HTML | |
const makeSelect = (stringArray) => { | |
return groupedOptions(stringArray).reduce( (memo, group) => memo += optionsGroupToHTML(group) , '<select>' ) + '</select>'; | |
}; | |
const arr = [ | |
'ABC - abc', | |
'ABC - def', | |
'ABCDEF - abc', | |
'ABCDEF - def', | |
'ABCDEF - ghi', | |
'ABCDEF - jkl', | |
'ABCDEFGH - abc', | |
'CBA - def', | |
'DEF - def' | |
]; | |
// console.log(JSON.stringify( groupedOptions(arr), null, 2 )); | |
console.log( makeSelect(arr) ); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment