refactor results reducer for cleanliness

This commit is contained in:
Sorrel Bri 2020-02-16 15:11:36 -08:00
parent afbd9d9bdf
commit 3d0bde1c55

View file

@ -65,128 +65,137 @@ const findFeaturesFromGrapheme = (phones: {}, lexeme:string): [] => {
const decomposeRule = (rule: string): ruleBundle => { const decomposeRule = (rule: string): ruleBundle => {
// splits rule at '>' '/' and '_' substrings resulting in array of length 4 // splits rule at '>' '/' and '_' substrings resulting in array of length 4
const decomposedChange = rule.split(/>|\/|_/g); const [position, newFeatures, pre, post] = rule.split(/>|\/|_/g);
return {
const ruleBundle = { environment: { pre, position, post },
environment: { newFeatures
pre: decomposedChange[2],
position: decomposedChange[0],
post: decomposedChange[3]
},
newFeatures: decomposedChange[1]
} }
return ruleBundle;
} }
const getPositiveFeatures = phoneme => {
const positiveFeatures = phoneme.match(/(?=\+.).*(?<=\-)|(?=\+.).*(?!\-).*(?<=\])/g)
return positiveFeatures ? positiveFeatures[0]
.trim().match(/\w+/g)
.reduce((map, feature) => ({...map, [feature]: true}), {})
: {}
}
const getNegativeFeatures = phoneme => {
const negativeFeatures = phoneme.match(/(?=\-.).*(?<=\+)|(?=\-.).*(?!\+).*(?<=\])/g)
return negativeFeatures ? negativeFeatures[0]
.trim().match(/\w+/g)
.reduce((map, feature) => ({...map, [feature]: false}), {})
: {}
}
const mapToPositiveAndNegativeFeatures = phoneme => (
{ ...getPositiveFeatures(phoneme), ...getNegativeFeatures(phoneme) } )
const mapStringToFeatures = (ruleString, phones) => { const mapStringToFeatures = (ruleString, phones) => {
if (ruleString) { if (ruleString) {
if (ruleString === '.') return []; if (ruleString === '.') return [];
const ruleBrackets = ruleString.match(/\[.*\]/) const ruleBrackets = ruleString.match(/\[.*\]/)
if (ruleBrackets) { if (ruleBrackets) {
const ruleFeatures = ruleString return ruleString
.split('[') .split('[')
// filter out empty strings // filter out empty strings
.filter(v => v) .filter(v => v)
.map((phoneme) => { .map(mapToPositiveAndNegativeFeatures)
const positiveFeatures = phoneme.match(/(?=\+.).*(?<=\-)|(?=\+.).*(?!\-).*(?<=\])/g)
const positiveFeaturesMap = positiveFeatures ? positiveFeatures[0]
.trim().match(/\w+/g)
.reduce((map, feature) => ({...map, [feature]: true}), {})
: {}
const negativeFeatures = phoneme.match(/(?=\-.).*(?<=\+)|(?=\-.).*(?!\+).*(?<=\])/g)
const negativeFeaturesMap = negativeFeatures ? negativeFeatures[0]
.trim().match(/\w+/g)
.reduce((map, feature) => ({...map, [feature]: false}), {})
: {}
return {...positiveFeaturesMap, ...negativeFeaturesMap}
})
return ruleFeatures;
} }
const grapheme = ruleString; return findFeaturesFromGrapheme(phones, ruleString);
return findFeaturesFromGrapheme(phones, grapheme);
} }
return {}; return {};
} }
const mapRuleBundleToFeatureBundle = (ruleBundle, phones) => { const mapRuleBundleToFeatureBundle = phones => ruleBundle => {
// for each object in ruleBundle, map values to array of objects with feature-boolean key-value pairs // for each object in ruleBundle, map values to array of objects with feature-boolean key-value pairs
const featureBundle = {...ruleBundle}; const { newFeatures, environment:{ pre, position, post } } = ruleBundle;
featureBundle.environment.pre = mapStringToFeatures(featureBundle.environment.pre, phones); return {
featureBundle.environment.position = mapStringToFeatures(featureBundle.environment.position, phones); environment: {
featureBundle.environment.post = mapStringToFeatures(featureBundle.environment.post, phones); pre: mapStringToFeatures(pre, phones),
featureBundle.newFeatures = mapStringToFeatures(featureBundle.newFeatures, phones); position: mapStringToFeatures(position, phones),
return featureBundle; post: mapStringToFeatures(post, phones),
},
newFeatures: mapStringToFeatures(newFeatures, phones)
}
} }
export const decomposeRules = (epoch: epochType, phones: {[key: string]: phoneType}): decomposedRulesType => { export const decomposeRules = (epoch: epochType, phones: {[key: string]: phoneType}): decomposedRulesType => {
let ruleBundle = [...epoch.changes] const { changes } = epoch
ruleBundle = epoch.changes.map(rule => decomposeRule(rule)); return changes.map(decomposeRule).map(mapRuleBundleToFeatureBundle(phones));
const featureBundle = ruleBundle.map(rule => mapRuleBundleToFeatureBundle(rule, phones));
return featureBundle;
} }
const isPhonemeBoundByRule = (phonemeFeatures, ruleFeatures) => { const isPhonemeBoundByRule = phonemeFeatures => (ruleFeature, index) => {
if (!ruleFeatures) return true;
const match = ruleFeatures.filter((ruleFeature, index) => {
const phoneme = phonemeFeatures[index].features; const phoneme = phonemeFeatures[index].features;
return Object.entries(ruleFeature).reduce((bool, entry) => { return Object.entries(ruleFeature).reduce((bool, [feature, value]) => {
if (!bool) return false; if (!bool) return false;
if (!phoneme[entry[0]] && !entry[1]) return true; if (!phoneme[feature] && !value) return true;
if (phoneme[entry[0]] !== entry[1]) return false; if (phoneme[feature] !== value) return false;
return true; return true;
}, true); }, true);
})
return match.length === ruleFeatures.length ? true : false;
} }
const isEnvironmentBoundByRule = (phonemeFeatures, ruleFeatures) => {
if (!ruleFeatures) return true;
return ruleFeatures.filter(isPhonemeBoundByRule(phonemeFeatures)).length === ruleFeatures.length
? true : false;
}
const swapPhoneme = (phoneme, newFeatures, features) => { const swapPhoneme = (phoneme, newFeatures, features) => {
const newPhonemeFeatures = Object.entries(newFeatures).reduce((newPhoneme, [newFeature, newValue]) => { const newPhonemeFeatures = Object.entries(newFeatures)
return { ...newPhoneme, [newFeature]: newValue } .reduce((newPhoneme, [newFeature, newValue]) => ({ ...newPhoneme, [newFeature]: newValue })
}, {...phoneme.features}) , {...phoneme.features});
const newPhonemeCandidates = Object.entries(newPhonemeFeatures).map(([newFeature, newValue]) => { const newPhonemeCandidates = Object.entries(newPhonemeFeatures)
return features[newFeature][newValue ? 'positive': 'negative'] .map(([newFeature, newValue]) => features[newFeature][newValue ? 'positive': 'negative']);
}) return newPhonemeCandidates
const newPhoneme = newPhonemeCandidates.reduce((candidates, value, index, array) => { .reduce((candidates, value, index, array) => candidates.filter(candidate => value.map(val => val.grapheme).includes(candidate.grapheme))
return candidates.filter(candidate => value.map(val => val.grapheme).includes(candidate.grapheme)) , newPhonemeCandidates[newPhonemeCandidates.length - 1])[0];
}, newPhonemeCandidates[newPhonemeCandidates.length - 1])
return newPhoneme[0];
} }
export const transformLexeme = (lexemeBundle, rule, features) => { export const transformLexeme = (lexemeBundle, rule, features) => {
const {pre, post, position} = rule.environment; const {pre, post, position} = rule.environment;
const newLexeme = lexemeBundle.reduce((newLexeme, phoneme, index) => { const newLexeme = lexemeBundle.reduce((newLexeme, phoneme, index) => {
if ( index < pre.length || index >= lexemeBundle.length - post.length ) return [...newLexeme, phoneme]; if ( index < pre.length || index >= lexemeBundle.length - post.length ) return [...newLexeme, phoneme];
if (!isPhonemeBoundByRule(lexemeBundle.slice(index - pre.length, index), pre)) return [...newLexeme, phoneme]; if (!isEnvironmentBoundByRule(lexemeBundle.slice(index - pre.length, index), pre)) return [...newLexeme, phoneme];
if (!isPhonemeBoundByRule([phoneme], rule.environment.position)) return [...newLexeme, phoneme]; if (!isEnvironmentBoundByRule([phoneme], rule.environment.position)) return [...newLexeme, phoneme];
if (!isPhonemeBoundByRule(lexemeBundle.slice(index, index + post.length), post)) return [...newLexeme, phoneme]; if (!isEnvironmentBoundByRule(lexemeBundle.slice(index, index + post.length), post)) return [...newLexeme, phoneme];
const newPhoneme = swapPhoneme(phoneme, rule.newFeatures[0], features); const newPhoneme = swapPhoneme(phoneme, rule.newFeatures[0], features);
return [...newLexeme, newPhoneme]; return [...newLexeme, newPhoneme];
}, []) }, [])
return newLexeme; return newLexeme;
} }
const formBundleFromLexicon = lexicon => phones => lexicon.map(({lexeme}) => findFeaturesFromLexeme(phones, lexeme))
const transformLexicon = lexiconBundle =>
ruleBundle =>
features =>
lexiconBundle.map(lexemeBundle => ruleBundle.reduce(
(lexeme, rule) => transformLexeme(lexeme, rule, features)
, lexemeBundle
))
const getGraphemeFromEntry = ([_, phoneme]) => phoneme.grapheme
const stringifyResults = lexemeBundle => Object.entries(lexemeBundle).map(getGraphemeFromEntry).join('')
export const run = (state: stateType, action: resultsAction): stateType => { export const run = (state: stateType, action: resultsAction): stateType => {
// for each epoch
// TODO iterate through each epoch // TODO iterate through each epoch
const epoch = state.epochs[0]; const epoch = state.epochs[0];
const phones = state.phones;
const lexicon = state.lexicon; const { phones, lexicon, features } = state;
const features = state.features;
const ruleBundle = decomposeRules(epoch, phones); const ruleBundle = decomposeRules(epoch, phones);
const lexiconBundle = lexicon.map(lexeme => findFeaturesFromLexeme(phones, lexeme.lexeme)) const lexiconBundle = formBundleFromLexicon(lexicon)(phones);
const passResults = transformLexicon(lexiconBundle)(ruleBundle)(features);
const stringifiedPassResults = passResults.map(stringifyResults);
const results = lexiconBundle.map(lexemeBundle => { const pass = {
return ruleBundle.reduce((lexeme, rule) => { pass: epoch.name,
return transformLexeme(lexeme, rule, features); results: stringifiedPassResults
}, lexemeBundle) }
})
const stringifiedResults = results.map(lexemeBundle => { return {...state, results: pass }
return Object.entries(lexemeBundle).map(phoneme => phoneme[1].grapheme).join('')
})
return {...state, results: { pass: state.epochs[0].name, results: stringifiedResults } }
} }