How Do I Combine Two Sets of Data?

Hello,
I am working on a music project. At one step, I have two text files. One looks something like this:

()Æ()tér()ne() re()rum() Cón()di()tor,() ()
Noc()tem() di()ém()que() qui() re()gis,() ()
Et() tém()po()rum() das() tém()po()ra,() ()
Ut() ál()le()ves() fas()tí()di()um() ()  

And the other looks something like this:

(c4)({fr}f)(f')(g)(h)(ixi)(h')(g)(h'_)(,)
({er}h)(g')(j)(j)(g)(h')(ixi)({fr}h.)(;)
({dr}g)(h')(g)(h)(g)({c}e')(f)(g'_)(,)
({bxbr}g)(h')(g)(f)(e)({ar}fg)(fe)(d.)(::)

[sorry about the smiley faces. I don't know why it's doing that]

Now, what I want to do is tell KM to put those elements in each set of parentheses of the second file into the first file, in order.

The first word, for example, would come out like this:
(c4)Æ({fr}f)tér(f')ne(g)

I tried to write a macro, but I can't figure out why it doesn't work. Here is what it looks like. I think the problem is at the last 'for each' block. I'm calling variables defined outside the 'for each' block' and I think it gets stuck. I don't know. I looked at it for 2 days now.

Any help is appreciated.
Thanks,
--Ryan
gregoriofillnotes.kmmacros (14.4 KB)

It appears that why when just paste special text into the Forum Editor.
It is always best to put your source data, and desired output data, into Code Blocks.
Use "text" for the language in this case.

I have edited your post to make these changes.

This ensures that your data is displayed exactly as you have it on your local system.
And will make it much easier for others to understand your data.

@R_B_Jawad, I hope you don't mind that I have revised your topic title to better reflect the question you have asked.

FROM:
Macro for sequential text insertion

TO:
How Do I Combine Two Sets of Data?

This will greatly help you attract more experienced users to help solve your problem, and will help future readers find your question, and the solution.

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

I'll try to get back later with some suggestions.

One way to do this kind of thing would be with a zipWith function in a script action:

Musical zipper.kmmacros (21 KB)

JavaScript Source

(() => {
    'use strict';

    const main = () => {
        const
            kme = Application('Keyboard Maestro Engine'),
            kmValue = k => kme.getvariable(k),
            txtGaps = kmValue('musicGaps'),
            txtFills = kmValue('musicFills'),

            xs = splitOn(/\)\(/g, txtFills.slice(1, -1)),
            ys = splitOn(/\(\)/g, txtGaps);

        return concat(
            zipWith(
                (a, b) => '(' + a + ')' + b,
                xs, tail(ys)
            )
        );
    };

    // GENERIC FUNCTIONS ----------------------------
    // https://github.com/RobTrew/prelude-jxa

    // Tuple (,) :: a -> b -> (a, b)
    const Tuple = (a, b) => ({
        type: 'Tuple',
        '0': a,
        '1': b,
        length: 2
    });

    // concat :: [[a]] -> [a]
    // concat :: [String] -> String
    const concat = xs =>
        0 < xs.length ? (() => {
            const unit = 'string' !== typeof xs[0] ? (
                []
            ) : '';
            return unit.concat.apply(unit, xs);
        })() : [];

    // length :: [a] -> Int
    const length = xs =>
        (Array.isArray(xs) || 'string' === typeof xs) ? (
            xs.length
        ) : Infinity;

    // snd :: (a, b) -> b
    const snd = tpl => tpl[1];

    // splitOn :: String -> String -> [String]
    const splitOn = (pat, src) =>
        src.split(pat);

    // tail :: [a] -> [a]
    const tail = xs => 0 < xs.length ? xs.slice(1) : [];

    // take :: Int -> [a] -> [a]
    // take :: Int -> String -> String
    const take = (n, xs) =>
        'GeneratorFunction' !== xs.constructor.constructor.name ? (
            xs.slice(0, n)
        ) : [].concat.apply([], Array.from({
            length: n
        }, () => {
            const x = xs.next();
            return x.done ? [] : [x.value];
        }));

    // Use of `take` and `length` here allows zipping with non-finite lists
    // i.e. generators like cycle, repeat, iterate.

    // zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
    const zipWith = (f, xs, ys) => {
        const
            lng = Math.min(length(xs), length(ys)),
            as = take(lng, xs),
            bs = take(lng, ys);
        return Array.from({
            length: lng
        }, (_, i) => f(as[i], bs[i], i));
    };

    // MAIN ---
    return main();
})();

FWIW in this context we could get away with a less general zipWith, specialised for arrays only:

// zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
const zipWith = (f, xs, ys) =>
    Array.from({
        length: Math.min(xs.length, xs.length)
    }, (_, i) => f(xs[i], ys[i], i));

and a less general concat (specialised for strings only).

// concat :: [String] -> String
const concat = xs =>
    ''.concat.apply('', xs);

So an alternative (briefer) source for the draft script action might be:

(() => {
    'use strict';

    const main = () => {
        const
            kme = Application('Keyboard Maestro Engine'),
            kmValue = k => kme.getvariable(k),
            txtGaps = kmValue('musicGaps'),
            txtFills = kmValue('musicFills'),

            xs = txtFills.slice(1, -1).split(/\)\(/g),
            ys = txtGaps.split(/\(\)/g);

        return concat(
            zipWith(
                (a, b) => '(' + a + ')' + b,
                xs, ys.slice(1)
            )
        );
    };

    // GENERIC FUNCTIONS ----------------------------
    // https://github.com/RobTrew/prelude-jxa

    // concat :: [String] -> String
    const concat = xs =>
        ''.concat.apply('', xs);

    // zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
    const zipWith = (f, xs, ys) =>
        Array.from({
            length: Math.min(xs.length, xs.length)
        }, (_, i) => f(xs[i], ys[i], i));

    // MAIN ---
    return main();
})();

Which could certainly be collapsed down to something more code-golfy, on a rainy August morning in the northern hemisphere:

(() => {
    'use strict';

    const
        kme = Application('Keyboard Maestro Engine'),
        [txtGaps, txtFills] = ['musicGaps', 'musicFills']
        .map(k => kme.getvariable(k)),

        xs = txtFills.slice(1, -1).split(/\)\(/g),
        ys = txtGaps.split(/\(\)/g);

    return ''.concat.apply(
        '', Array.from({
            length: Math.min(xs.length, xs.length)
        }, (_, i) => ((a, b) => '(' + a + ')' + b)(
            xs[i], ys[i], i
        ))
    );
})();

but it makes life easier, and code more readily reorganised, if we keep things a bit more legible, and mainly stick to prefabricated units.

1 Like

ComplexPoint,
Thank you for the help. That was a lot of work, I'm sure.

The macro you posted above works pretty good, but it has some problems.
The first line looks good, but by the second line, things are off. Specifically, ({er}h) should come after Noc. I would try to edit it, but I am not familiar with Java.

Actually, nevermind. The macro I posted above works. It's just, the last step "set clipboard to variable" fails, for whatever reason. I changed it to 'display text in window'. Thanks anyway for your help.

1 Like

That's fortunate – Java is a dreadful thing – this is JavaScript (unrelated to Java, oddly enough) and much easier to learn and use :slight_smile:

Here is a completed version, if anyone else is interested. It takes two text files as input. It finds the first pair of parentheses in the first file and inserts the content of this into the first pair of parentheses in the second file.

I'm not a programmer, and I don't think it is very efficient. Takes the computer probably 10 sec, which is way better than my old cheap way, which probably took 20 min (I had the computer clicking back and forth), which was way better than doing it manually. I basically just used a bunch of Regex commands and a 'for each' command. The way it works is, it converts each text file into a string of characters. The two strings are each prepended with an identifier (@). Then it finds the first ( ) after @ and stores that. Then it finds the complement after @ in the second file. Then it glues those together. Then it deletes the stuff it just selected in each file, so that now, the second ( ) becomes the first. And just repeats that over and over, adding on the glued segments to what it already has. Finally, some formatting is done, specific to my file to get it back like it was originally.
It also has a check at the beginning, to make sure the number of parentheses agree. Because of elisions in my texts, I had to build a small modification in, which basically removes these extra parentheses, and then puts them back in at the end.
gregoriofillnotes.kmmacros (34.7 KB)