And of course JavaScript String
values come ready-equipped with a bag of tricks too.
If we give a constant name to your source String
above:
const source = `ABCD1.
baseball bat
carla jones
r2d2
Samantha Who
jersey-mike
CAT
a
Rita Diaz-T’wine
31.2 men
FREE
carlton
oak Tree
Lake Titicaca
LALA
francis
Session 9 Days
oak Tree
beetle mania`;
and use String.split
to get a list (Array
) of lines:
const separateLines = source.split("\n");
then separateLines
will look like this:
[
"ABCD1.",
" baseball bat",
" carla jones",
" r2d2",
" Samantha Who",
" jersey-mike",
"",
" CAT",
" a",
"",
" Rita Diaz-T’wine",
" 31.2 men",
" FREE",
" carlton",
" oak Tree",
" Lake Titicaca",
" LALA",
" francis",
" Session 9 Days",
" oak Tree",
" beetle mania"
]
but we probably don't want that leading whitespace, so let's give a name to a version of the list in which each line is trimmed.
We can define our trimmed copy (or 'map') like this:
const trimmedLines = separateLines.map(
line => line.trim()
);
and trimmedLines
looks like this:
[
"ABCD1.",
"baseball bat",
"carla jones",
"r2d2",
"Samantha Who",
"jersey-mike",
"",
"CAT",
"a",
"",
"Rita Diaz-T’wine",
"31.2 men",
"FREE",
"carlton",
"oak Tree",
"Lake Titicaca",
"LALA",
"francis",
"Session 9 Days",
"oak Tree",
"beetle mania"
]
We can also make use of your existing knowledge of regular expressions.
We're going to need to define a test for whether a string is all upper case.
Let's start with a test which returns true
if a single character is upper case, and otherwise returns false
.
const isUpper = c =>
// True if c is an upper case character.
(/[A-Z]/u).test(c);
and now let's use that test as a Lego brick to build another test which returns true
if and only if all of the characters in a string are upper case:
const isAllUpper = someString =>
[...someString].every(isUpper);
Where [...someString]
breaks the string down into a list (Array
) of individual characters, and JS Arrays have a built-in .every(test)
function which return true if and only if every item in the list passes the test.
Now we can try out JavaScript Array.filter
, applying our isAllUpper
test.
We can generally define the sub-list of those lines which are all upper case as follows:
const onlyLinesWhichAreNonEmptyAndUpperCase = someLines =>
someLines.filter(
line => line.length > 0 && isAllUpper(line)
);
Let's give a name to the filtered version of our trimmed list:
const justUpperCaseLines = onlyLinesWhichAreNonEmptyAndUpperCase(
trimmedLines
);
It looks like this:
[
"CAT",
"FREE",
"LALA"
]
If we generally wanted a Key:Value dictionary (JS Object
) in which some chosen words are the keys, and the value of each key is, for the moment, an empty list, we could define the pattern as something like:
const dictionaryFromWords = chosenWords =>
Object.fromEntries(
chosenWords.map(
word => [word, []]
)
);
An initially empty dictionary based our upper case words could be defined as:
const emptyDictionary = dictionaryFromWords(
justUpperCaseLines
);
and emptyDictionary
looks like this:
{
"CAT": [],
"FREE": [],
"LALA": []
}
How would we define a filled dictionary in terms of
- A dictionary in which the keys are the empty headings, and
- our ordered list of all the terms
We could define it in terms of Array.reduce, which takes a list, and a starter value, and returns a summary value to which every item in the list has contributed.
Here, our start value is a pair of things:
- The dictionary with headers but no entries
- The current header (initially we don't have one, so just an empty string "")
How does each line (term) contribute to building the summary value ?
- If the line is one of the keys in the dictionary, it is adopted as the current heading, under which a few following items can be added
- if the line/term is not itself a dictionary header, and we do have a non-empty current header, then that term is added to the current header's list.
const filledDictionary = emptyDictionary =>
termList => termList.reduce(
([dictionary, heading], term) =>
term in dictionary
? [dictionary, term]
: heading.length > 0
? [
Object.assign(
dictionary,
{
[heading]: dictionary[heading]
.concat(term)
}
),
heading
]
: [dictionary, heading],
[emptyDictionary, ""]
)[0];
So we can now define a filled dictionary and give it a constant name:
const headingsWithFollowingTerms = filledDictionary(
emptyDictionary
)(
trimmedLines
);
and headingsWithFollowingTerms
turns out to look like this:
{
"CAT": [
"a",
"",
"Rita Diaz-T’wine",
"31.2 men"
],
"FREE": [
"carlton",
"oak Tree",
"Lake Titicaca"
],
"LALA": [
"francis",
"Session 9 Days",
"oak Tree",
"beetle mania"
]
}
We can refer to items in JavaScript lists (Arrays) with a zero-base numeric (integer) index.
so for example:
headingsWithFollowingTerms.FREE[1]
is "oak Tree"
Lines Gathered under UPPER CASE Headings.kmmacros (6,3 Ko)
Expand disclosure triangle to view KM-independent testing version of JS source
(() => {
"use strict";
const kmvar = {"local_Source": `ABCD1.
baseball bat
carla jones
r2d2
Samantha Who
jersey-mike
CAT
a
Rita Diaz-T’wine
31.2 men
FREE
carlton
oak Tree
Lake Titicaca
LALA
francis
Session 9 Days
oak Tree
beetle mania`};
// MAIN
const main = () => {
const separateLines = kmvar.local_Source.split("\n");
const trimmedLines = separateLines.map(
line => line.trim()
);
const justUpperCaseLines = onlyLinesWhichAreNonEmptyAndUpperCase(
trimmedLines
);
const emptyDictionary = dictionaryFromWords(
justUpperCaseLines
);
const headingsWithFollowingTerms = filledDictionary(
emptyDictionary
)(
trimmedLines
);
return headingsWithFollowingTerms;
};
// ------- TERMS FOLLOWING UPPER CASE HEADINGS -------
const dictionaryFromWords = chosenWords =>
Object.fromEntries(
chosenWords.map(
word => [word, []]
)
);
const filledDictionary = emptyDict =>
termList => termList.reduce(
([dictionary, currentHeading], term) =>
term in dictionary
? [dictionary, term]
: currentHeading.length > 0
? [
Object.assign(
dictionary,
{
[currentHeading]: dictionary[
currentHeading
]
.concat(term)
}
),
currentHeading
]
: [dictionary, currentHeading],
[emptyDict, ""]
)[0];
const onlyLinesWhichAreNonEmptyAndUpperCase = someLines =>
someLines.filter(
line => line.length > 0 && isAllUpper(line)
);
// --------------------- GENERIC ---------------------
// isUpper :: Char -> Bool
const isUpper = c =>
// True if c is an upper case character.
(/[A-Z]/u).test(c);
// const isAllUpper :: String -> Bool
const isAllUpper = s =>
[...s].every(isUpper);
return JSON.stringify(
main(),
null,
2
);
})();