about summary refs log tree commit diff
path: root/lib
diff options
context:
space:
mode:
authorSilvan Mosberger <silvan.mosberger@tweag.io>2023-07-14 19:26:08 +0200
committerSilvan Mosberger <silvan.mosberger@tweag.io>2023-07-20 22:42:01 +0200
commit7f61b01600ecbdaa2a57c51097f2ece9e421c4fe (patch)
tree4b18b3b06cb8f0771688757af353b45b56b0dbef /lib
parent53dcfd24ad71bb1a26c9d17cbd2af1f83a2da7a3 (diff)
downloadnixlib-7f61b01600ecbdaa2a57c51097f2ece9e421c4fe.tar
nixlib-7f61b01600ecbdaa2a57c51097f2ece9e421c4fe.tar.gz
nixlib-7f61b01600ecbdaa2a57c51097f2ece9e421c4fe.tar.bz2
nixlib-7f61b01600ecbdaa2a57c51097f2ece9e421c4fe.tar.lz
nixlib-7f61b01600ecbdaa2a57c51097f2ece9e421c4fe.tar.xz
nixlib-7f61b01600ecbdaa2a57c51097f2ece9e421c4fe.tar.zst
nixlib-7f61b01600ecbdaa2a57c51097f2ece9e421c4fe.zip
lib.lists.commonPrefix: init
Diffstat (limited to 'lib')
-rw-r--r--lib/lists.nix28
-rw-r--r--lib/tests/misc.nix33
2 files changed, 60 insertions, 1 deletions
diff --git a/lib/lists.nix b/lib/lists.nix
index c3e889644b3e..3e058b3f1aef 100644
--- a/lib/lists.nix
+++ b/lib/lists.nix
@@ -3,7 +3,7 @@
 { lib }:
 let
   inherit (lib.strings) toInt;
-  inherit (lib.trivial) compare min;
+  inherit (lib.trivial) compare min id;
   inherit (lib.attrsets) mapAttrs;
 in
 rec {
@@ -663,6 +663,32 @@ rec {
        else if start + count > len then len - start
        else count);
 
+  /* The common prefix of two lists.
+
+  Type: commonPrefix :: [a] -> [a] -> [a]
+
+  Example:
+    commonPrefix [ 1 2 3 4 5 6 ] [ 1 2 4 8 ]
+    => [ 1 2 ]
+    commonPrefix [ 1 2 3 ] [ 1 2 3 4 5 ]
+    => [ 1 2 3 ]
+    commonPrefix [ 1 2 3 ] [ 4 5 6 ]
+    => [ ]
+  */
+  commonPrefix =
+    list1:
+    list2:
+    let
+      # Zip the lists together into a list of booleans whether each element matches
+      matchings = zipListsWith (fst: snd: fst != snd) list1 list2;
+      # Find the first index where the elements don't match,
+      # which will then also be the length of the common prefix.
+      # If all elements match, we fall back to the length of the zipped list,
+      # which is the same as the length of the smaller list.
+      commonPrefixLength = findFirstIndex id (length matchings) matchings;
+    in
+    take commonPrefixLength list1;
+
   /* Return the last element of a list.
 
      This function throws an error if the list is empty.
diff --git a/lib/tests/misc.nix b/lib/tests/misc.nix
index dfa6a94ee48e..ab97409d8567 100644
--- a/lib/tests/misc.nix
+++ b/lib/tests/misc.nix
@@ -488,6 +488,39 @@ runTests {
     expected = { a = [ 2 3 ]; b = [7]; c = [8];};
   };
 
+  testListCommonPrefixExample1 = {
+    expr = lists.commonPrefix [ 1 2 3 4 5 6 ] [ 1 2 4 8 ];
+    expected = [ 1 2 ];
+  };
+  testListCommonPrefixExample2 = {
+    expr = lists.commonPrefix [ 1 2 3 ] [ 1 2 3 4 5 ];
+    expected = [ 1 2 3 ];
+  };
+  testListCommonPrefixExample3 = {
+    expr = lists.commonPrefix [ 1 2 3 ] [ 4 5 6 ];
+    expected = [ ];
+  };
+  testListCommonPrefixEmpty = {
+    expr = lists.commonPrefix [ ] [ 1 2 3 ];
+    expected = [ ];
+  };
+  testListCommonPrefixSame = {
+    expr = lists.commonPrefix [ 1 2 3 ] [ 1 2 3 ];
+    expected = [ 1 2 3 ];
+  };
+  testListCommonPrefixLazy = {
+    expr = lists.commonPrefix [ 1 ] [ 1 (abort "lib.lists.commonPrefix shouldn't evaluate this")];
+    expected = [ 1 ];
+  };
+  # This would stack overflow if `commonPrefix` were implemented using recursion
+  testListCommonPrefixLong =
+    let
+      longList = genList (n: n) 100000;
+    in {
+      expr = lists.commonPrefix longList longList;
+      expected = longList;
+    };
+
   testSort = {
     expr = sort builtins.lessThan [ 40 2 30 42 ];
     expected = [2 30 40 42];