Giter Site home page Giter Site logo

difference's Introduction

Difference

Difference is a Kotlin multiplatform differencing library. Given two lists, Difference will compute the insert and delete operations required to transform the starting list into the final list. Difference can also optionally detect items that have moved to new indices in the list.

Behind the scenes, Difference uses Eugene Myer's Differencing Algorithm. This is the same algorithm used by Android's DiffUtil class, which you may be familiar with if you're an Android developer. Difference is very similar to Android's DiffUtil, but is completely platform agnostic and has more receiver-agnostic APIs to consume the diff result.

Setup

Gradle can automatically resolve which variation of Difference is appropriate for the platform and architecture you're compiling for. To use the universal dependency, add this dependency to your module's build.gradle:

dependencies {
    implementation 'dev.andrewbailey.difference:difference:1.0.0'
}

If you want to explicitly specify which platform variant of the library you want to depend on, you can use any of the following dependencies as appropriate:

dev.andrewbailey.difference:difference-jvm:1.0.0
dev.andrewbailey.difference:difference-js:1.0.0
dev.andrewbailey.difference:difference-linux-x64:1.0.0
dev.andrewbailey.difference:difference-macos-x64:1.0.0
dev.andrewbailey.difference:difference-ios-x64:1.0.0
dev.andrewbailey.difference:difference-ios-arm64:1.0.0
dev.andrewbailey.difference:difference-mingw-x86:1.0.0
dev.andrewbailey.difference:difference-mingw-x64:1.0.0

Generating a diff

To generate a diff, you can call differenceOf with the following arguments:

val listOfBffs = listOf("Merengue", "Sherb", "Tammy")
val revisedListOfBffs = listOf("Merengue", "Sprinkle", "Sherb")

val diff = differenceOf(
    original = listOfBffs,
    updated = revisedListOfBffs,
    detectMoves = false
)

In Java, you can write this code as:

List<String> listOfBffs = Arrays.asList("Merengue", "Sherb", "Tammy");
List<String> revisedListOfBffs = Arrays.asList("Merengue", "Sprinkle", "Sherb");

DiffResult<String> diff = Difference.differenceOf(listOfBffs, revisedListOfBffs, false);

The diff that will be generated for this input will look like this:

Insert(index = 1, item = "Sprinkle")
Remove(index = 3)

Please keep in mind that calling differenceOf is an expensive operation, so it's recommended to offload this call to a background thread. See the Performance section for more info.

Using a DiffResult

differenceOf returns a DiffResult<T> object, typed based on the input lists. To consume a diff, you can call DiffResult<T>.applyDiff. There are several overloads that can increase the performance of how your diff is applied, but the most basic call looks something like this:

...

val diff = differenceOf(
    original = listOfBffs,
    updated = revisedListOfBffs,
    detectMoves = true
)

// When `applyDiff` returns, `output` will be equal to `revisedListOfBffs`
val output = listOfBffs.toMutableList()
diff.applyDiff(
    remove = { index: Int ->
        output.removeAt(index)
    },
    insert = { item: T, index: Int ->
        output.add(index, item)
    },
    move = { oldIndex: Int, newIndex: Int ->
        // You can leave this blank if you set `detectMoves` to false
        output.add(
            element = removeAt(oldIndex),
            index = if (newIndex < oldIndex) {
                newIndex
            } else {
                newIndex - 1
            }
        )
    }
)

If you're using Difference in Java, using applyDiff can be a bit awkward to use because it exposes Kotlin's FunctionN interfaces, named parameters, and default arguments to be easy to call. You can alternatively use DiffReceiver to improve the readability of your diff callbacks in a Java project.

The same receiver logic can be expressed like this in Java:

DiffResult<String> diff = differenceOf(...);

// When `applyDiff` returns, `output` will be equal to `revisedListOfBffs`
List<String> output = ArrayList<>(listOfBffs);
DiffReceiver<String> receiver = new DiffReceiver<>() {
    @Override
    public void remove(int index) {
        output.removeAt(index);
    }

    @Override
    public void insert(String item, int index) {
        output.add(index, item);
    }

    @Override
    public void move(int oldIndex, int newIndex) {
        // You can omit this override if you set `detectMoves` to false
        int index = newIndex;
        if (newIndex >= oldIndex) {
            index--;
        }

        output.add(index, output.removeAt(oldIndex));
    }
};

receiver.applyDiff(diff);

Note that DiffReceiver is only available in Java projects. If your project is a 100% Kotlin project, then you should stick to applyDiff and ignore DiffReceiver.

Performance

Difference uses a linear-space non-recursive implementation of Eugene Myer's Differencing algorithm. The algorithm takes O((M+N)×D + D log D) operations, where M and N are the lengths of the input lists, and D is the minimum number of edits it takes to transform the original list into the final list. If you enable movement detection, this runtime increases by O(D²).

Diff generation can take a while, so for UI-centric applications, you should avoid calling differenceOf on your main thread. Depending on conditions and the client's hardware, diff generation for arbitrary lists can easily cause your application to block the main thread and cause frame drops if you aren't careful.

Benchmarks

Difference has benchmark tests that can run on an Android device. Below is the measured time it takes to compute diffs of various sizes on a Pixel 3 running Android Q.

These values should not be used to estimate how long any call to differenceOf will take. Real-world performance will vary device-to-device, and can be influenced by conditions such as the device's hardware, CPU load, temperature, and battery level, among other factors. You should take these values with a grain of salt and make note of how the time taken to generate a diff grows exponentially based on the size of the inputs.

Pixel 3 Benchmark (without movement detection)

Starting list size Number of operations Time taken
100 10 0.059 ms
1000 100 0.983 ms
5000 500 17.6 ms
10000 1000 55.6 ms

Pixel 3 Benchmark (with movement detection)

Starting list size Number of operations Time taken
100 10 0.055 ms
1000 100 1.158 ms
5000 500 22.0 ms
10000 1000 73.5 ms

difference's People

Contributors

andrewbailey avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.