refactor results reducer for cleanliness
This commit is contained in:
parent
afbd9d9bdf
commit
3d0bde1c55
1 changed files with 89 additions and 80 deletions
|
@ -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 } }
|
|
||||||
}
|
}
|
Loading…
Reference in a new issue