How to compare two variables, line by line, and capture only lines that differ from each other?

Howdy folks,

I’m trying to figure something that out that is likely quite simple, but at the moment eludes me. I would like to compare two variables, which are almost identical. Both will consist of 7 lines, and in almost all cases, all but one line will match. Every now and then there will be two or three lines that differ from each other, and obviously I would like to capture those as well. Here’s a sample of what I’m talking about:

↓ Sample Variable 1 ↓							↓ Sample variable 2 ↓
Sample text line 1... this matches				Sample text line 1... this matches
Sample text line 2... this matches				Sample text line 2... this matches
Sample text line 3... this matches				Sample text line 3... this matches
Sample text line 4... this one is different		Sample text line 4... capture this!
Sample text line 5... this matches				Sample text line 5... this matches
Sample text line 6... this matches				Sample text line 6... this matches
Sample text line 7... this matches				Sample text line 7... this matches

As you can see, line 4 differs in the two variables... I want to capture this particular line in both variables.

Thanks in advance for any help!

-Chris

Here is one way you can do it.

It definitely assumes both variables contain exactly 7 lines to simplify the process.

Find Mismatched Lines.kmmacros (7.0 KB)

1 Like

and another alternative, is of course, a script action of some kind.

In a JavaScript for Automation action, you could use a function like:

// diffLines = String -> String -> [(Int, String, String)]
const diffLines = textA =>
    // One-indexed list of any differing lines in
    // two given strings.
    // Where line counts differ, all surplus lines are
    // included in the difference list.
    textB => zipWithLong(
        (a, b) => a !== b
            ? [a, b]
            : []
    )(
        lines(textA)
    )(
        lines(textB)
    )
    .flatMap(
        (ab, i) => 0 < ab.length
            ? [[1 + i, ...ab]]
            : []
    );

To generate JSON difference lists like:

[
  [
    4,
    "Sample text line 4... this one is different",
    "Sample text line 4... capture this!"
  ],
  [
    6,
    "Sample text line 6... this matches",
    "Sample text line 6... does this match ?"
  ],
  [
    8,
    "",
    "Sample text line 8 ... this one has no pair ..."
  ]
]

For example:

Find Mismatched Lines by diff.kmmacros (5.2 KB)


Expand disclosure triangle to view JS source
(() => {
    "use strict";

    // Rob Trew @2023
    // Ver 0.1

    // ------------------- DIFF LINES --------------------

    // diffLines = String -> String -> [(Int, String, String)]
    const diffLines = textA =>
    // One-indexed list of any differing lines in
    // two given strings.
    // Where line counts differ, all surplus lines are
    // included in the difference list.
        textB => zipWithLong(
            (a, b) => a !== b
                ? [a, b]
                : []
        )(
            lines(textA)
        )(
            lines(textB)
        )
        .flatMap(
            (ab, i) => 0 < ab.length
                ? [[1 + i, ...ab]]
                : []
        );


    // ---------------------- TEST -----------------------
    const main = () => {
        const kmv = kmValue(kmInstance());

        return JSON.stringify(
            diffLines(
                kmv("local_Alpha")
            )(
                kmv("local_Beta")
            ),
            null, 2
        );
    };


    // ---------------- KEYBOARD MAESTRO -----------------

    // kmInstance :: () -> IO String
    const kmInstance = () =>
        ObjC.unwrap(
            $.NSProcessInfo.processInfo.environment
            .objectForKey("KMINSTANCE")
        ) || "";


    // kmValue :: KM Instance -> String -> IO String
    const kmValue = instance =>
        k => Application("Keyboard Maestro Engine")
        .getvariable(k, {instance});


    // --------------------- GENERIC ---------------------

    // lines :: String -> [String]
    const lines = s =>
        // A list of strings derived from a single string
        // which is delimited by \n or by \r\n or \r.
        0 < s.length
            ? s.split(/\r\n|\n|\r/u)
            : [];


    // zipWithLong :: (a -> a -> a) -> [a] -> [a] -> [a]
    const zipWithLong = f => {
        // A list with the length of the *longer* of
        // xs and ys, defined by zipping with a
        // custom function, rather than with the
        // default tuple constructor.
        // Any unpaired values, where list lengths differ,
        // are simply appended.
        const go = xs =>
            ys => 0 < xs.length
                ? 0 < ys.length
                    ? [f(xs[0], ys[0])].concat(
                        go(xs.slice(1))(ys.slice(1))
                    )
                    : xs
                : ys;

        return go;
    };

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