Merge pull request #298680 from gvolpe/lib/transposeMap

lib/attrsets: add mapCartesianProduct function
This commit is contained in:
Rick van Schijndel 2024-04-19 08:26:09 +02:00 committed by GitHub
commit e00a40a257
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 115 additions and 45 deletions

View File

@ -5,7 +5,7 @@
let let
inherit (builtins) head length; inherit (builtins) head length;
inherit (lib.trivial) mergeAttrs warn; inherit (lib.trivial) isInOldestRelease mergeAttrs warn warnIf;
inherit (lib.strings) concatStringsSep concatMapStringsSep escapeNixIdentifier sanitizeDerivationName; inherit (lib.strings) concatStringsSep concatMapStringsSep escapeNixIdentifier sanitizeDerivationName;
inherit (lib.lists) foldr foldl' concatMap elemAt all partition groupBy take foldl; inherit (lib.lists) foldr foldl' concatMap elemAt all partition groupBy take foldl;
in in
@ -885,15 +885,15 @@ rec {
# Type # Type
``` ```
cartesianProductOfSets :: AttrSet -> [AttrSet] cartesianProduct :: AttrSet -> [AttrSet]
``` ```
# Examples # Examples
:::{.example} :::{.example}
## `lib.attrsets.cartesianProductOfSets` usage example ## `lib.attrsets.cartesianProduct` usage example
```nix ```nix
cartesianProductOfSets { a = [ 1 2 ]; b = [ 10 20 ]; } cartesianProduct { a = [ 1 2 ]; b = [ 10 20 ]; }
=> [ => [
{ a = 1; b = 10; } { a = 1; b = 10; }
{ a = 1; b = 20; } { a = 1; b = 20; }
@ -904,7 +904,7 @@ rec {
::: :::
*/ */
cartesianProductOfSets = cartesianProduct =
attrsOfLists: attrsOfLists:
foldl' (listOfAttrs: attrName: foldl' (listOfAttrs: attrName:
concatMap (attrs: concatMap (attrs:
@ -913,6 +913,40 @@ rec {
) [{}] (attrNames attrsOfLists); ) [{}] (attrNames attrsOfLists);
/**
Return the result of function f applied to the cartesian product of attribute set value combinations.
Equivalent to using cartesianProduct followed by map.
# Inputs
`f`
: A function, given an attribute set, it returns a new value.
`attrsOfLists`
: Attribute set with attributes that are lists of values
# Type
```
mapCartesianProduct :: (AttrSet -> a) -> AttrSet -> [a]
```
# Examples
:::{.example}
## `lib.attrsets.mapCartesianProduct` usage example
```nix
mapCartesianProduct ({a, b}: "${a}-${b}") { a = [ "1" "2" ]; b = [ "3" "4" ]; }
=> [ "1-3" "1-4" "2-3" "2-4" ]
```
:::
*/
mapCartesianProduct = f: attrsOfLists: map f (cartesianProduct attrsOfLists);
/** /**
Utility function that creates a `{name, value}` pair as expected by `builtins.listToAttrs`. Utility function that creates a `{name, value}` pair as expected by `builtins.listToAttrs`.
@ -1999,4 +2033,8 @@ rec {
# DEPRECATED # DEPRECATED
zip = warn zip = warn
"lib.zip is a deprecated alias of lib.zipAttrsWith." zipAttrsWith; "lib.zip is a deprecated alias of lib.zipAttrsWith." zipAttrsWith;
# DEPRECATED
cartesianProductOfSets = warnIf (isInOldestRelease 2405)
"lib.cartesianProductOfSets is a deprecated alias of lib.cartesianProduct." cartesianProduct;
} }

View File

@ -86,8 +86,8 @@ let
zipAttrsWithNames zipAttrsWith zipAttrs recursiveUpdateUntil zipAttrsWithNames zipAttrsWith zipAttrs recursiveUpdateUntil
recursiveUpdate matchAttrs mergeAttrsList overrideExisting showAttrPath getOutput recursiveUpdate matchAttrs mergeAttrsList overrideExisting showAttrPath getOutput
getBin getLib getDev getMan chooseDevOutputs zipWithNames zip getBin getLib getDev getMan chooseDevOutputs zipWithNames zip
recurseIntoAttrs dontRecurseIntoAttrs cartesianProductOfSets recurseIntoAttrs dontRecurseIntoAttrs cartesianProduct cartesianProductOfSets
updateManyAttrsByPath; mapCartesianProduct updateManyAttrsByPath;
inherit (self.lists) singleton forEach foldr fold foldl foldl' imap0 imap1 inherit (self.lists) singleton forEach foldr fold foldl foldl' imap0 imap1
concatMap flatten remove findSingle findFirst any all count concatMap flatten remove findSingle findFirst any all count
optional optionals toList range replicate partition zipListsWith zipLists optional optionals toList range replicate partition zipListsWith zipLists

View File

@ -1688,16 +1688,32 @@ rec {
## `lib.lists.crossLists` usage example ## `lib.lists.crossLists` usage example
```nix ```nix
crossLists (x:y: "${toString x}${toString y}") [[1 2] [3 4]] crossLists (x: y: "${toString x}${toString y}") [[1 2] [3 4]]
=> [ "13" "14" "23" "24" ] => [ "13" "14" "23" "24" ]
``` ```
The following function call is equivalent to the one deprecated above:
```nix
mapCartesianProduct (x: "${toString x.a}${toString x.b}") { a = [1 2]; b = [3 4]; }
=> [ "13" "14" "23" "24" ]
```
::: :::
*/ */
crossLists = warn crossLists = warn
"lib.crossLists is deprecated, use lib.cartesianProductOfSets instead." ''lib.crossLists is deprecated, use lib.mapCartesianProduct instead.
(f: foldl (fs: args: concatMap (f: map f args) fs) [f]);
For example, the following function call:
nix-repl> lib.crossLists (x: y: x+y) [[1 2] [3 4]]
[ 4 5 5 6 ]
Can now be replaced by the following one:
nix-repl> lib.mapCartesianProduct ({x,y}: x+y) { x = [1 2]; y = [3 4]; }
[ 4 5 5 6 ]
''
(f: foldl (fs: args: concatMap (f: map f args) fs) [f]);
/** /**
Remove duplicate elements from the `list`. O(n^2) complexity. Remove duplicate elements from the `list`. O(n^2) complexity.

View File

@ -33,7 +33,7 @@ let
boolToString boolToString
callPackagesWith callPackagesWith
callPackageWith callPackageWith
cartesianProductOfSets cartesianProduct
cli cli
composeExtensions composeExtensions
composeManyExtensions composeManyExtensions
@ -71,10 +71,10 @@ let
makeIncludePath makeIncludePath
makeOverridable makeOverridable
mapAttrs mapAttrs
mapCartesianProduct
matchAttrs matchAttrs
mergeAttrs mergeAttrs
meta meta
mkOption
mod mod
nameValuePair nameValuePair
optionalDrvAttr optionalDrvAttr
@ -117,7 +117,6 @@ let
expr = (builtins.tryEval expr).success; expr = (builtins.tryEval expr).success;
expected = true; expected = true;
}; };
testingDeepThrow = expr: testingThrow (builtins.deepSeq expr expr);
testSanitizeDerivationName = { name, expected }: testSanitizeDerivationName = { name, expected }:
let let
@ -1415,7 +1414,7 @@ runTests {
}; };
testToPrettyMultiline = { testToPrettyMultiline = {
expr = mapAttrs (const (generators.toPretty { })) rec { expr = mapAttrs (const (generators.toPretty { })) {
list = [ 3 4 [ false ] ]; list = [ 3 4 [ false ] ];
attrs = { foo = null; bar.foo = "baz"; }; attrs = { foo = null; bar.foo = "baz"; };
newlinestring = "\n"; newlinestring = "\n";
@ -1429,7 +1428,7 @@ runTests {
there there
test''; test'';
}; };
expected = rec { expected = {
list = '' list = ''
[ [
3 3
@ -1467,13 +1466,10 @@ runTests {
expected = "«foo»"; expected = "«foo»";
}; };
testToPlist = testToPlist = {
let
deriv = derivation { name = "test"; builder = "/bin/sh"; system = "aarch64-linux"; };
in {
expr = mapAttrs (const (generators.toPlist { })) { expr = mapAttrs (const (generators.toPlist { })) {
value = { value = {
nested.values = rec { nested.values = {
int = 42; int = 42;
float = 0.1337; float = 0.1337;
bool = true; bool = true;
@ -1686,17 +1682,17 @@ runTests {
}; };
testCartesianProductOfEmptySet = { testCartesianProductOfEmptySet = {
expr = cartesianProductOfSets {}; expr = cartesianProduct {};
expected = [ {} ]; expected = [ {} ];
}; };
testCartesianProductOfOneSet = { testCartesianProductOfOneSet = {
expr = cartesianProductOfSets { a = [ 1 2 3 ]; }; expr = cartesianProduct { a = [ 1 2 3 ]; };
expected = [ { a = 1; } { a = 2; } { a = 3; } ]; expected = [ { a = 1; } { a = 2; } { a = 3; } ];
}; };
testCartesianProductOfTwoSets = { testCartesianProductOfTwoSets = {
expr = cartesianProductOfSets { a = [ 1 ]; b = [ 10 20 ]; }; expr = cartesianProduct { a = [ 1 ]; b = [ 10 20 ]; };
expected = [ expected = [
{ a = 1; b = 10; } { a = 1; b = 10; }
{ a = 1; b = 20; } { a = 1; b = 20; }
@ -1704,12 +1700,12 @@ runTests {
}; };
testCartesianProductOfTwoSetsWithOneEmpty = { testCartesianProductOfTwoSetsWithOneEmpty = {
expr = cartesianProductOfSets { a = [ ]; b = [ 10 20 ]; }; expr = cartesianProduct { a = [ ]; b = [ 10 20 ]; };
expected = [ ]; expected = [ ];
}; };
testCartesianProductOfThreeSets = { testCartesianProductOfThreeSets = {
expr = cartesianProductOfSets { expr = cartesianProduct {
a = [ 1 2 3 ]; a = [ 1 2 3 ];
b = [ 10 20 30 ]; b = [ 10 20 30 ];
c = [ 100 200 300 ]; c = [ 100 200 300 ];
@ -1753,6 +1749,30 @@ runTests {
]; ];
}; };
testMapCartesianProductOfOneSet = {
expr = mapCartesianProduct ({a}: a * 2) { a = [ 1 2 3 ]; };
expected = [ 2 4 6 ];
};
testMapCartesianProductOfTwoSets = {
expr = mapCartesianProduct ({a,b}: a + b) { a = [ 1 ]; b = [ 10 20 ]; };
expected = [ 11 21 ];
};
testMapCartesianProcutOfTwoSetsWithOneEmpty = {
expr = mapCartesianProduct (x: x.a + x.b) { a = [ ]; b = [ 10 20 ]; };
expected = [ ];
};
testMapCartesianProductOfThreeSets = {
expr = mapCartesianProduct ({a,b,c}: a + b + c) {
a = [ 1 2 3 ];
b = [ 10 20 30 ];
c = [ 100 200 300 ];
};
expected = [ 111 211 311 121 221 321 131 231 331 112 212 312 122 222 322 132 232 332 113 213 313 123 223 323 133 233 333 ];
};
# The example from the showAttrPath documentation # The example from the showAttrPath documentation
testShowAttrPathExample = { testShowAttrPathExample = {
expr = showAttrPath [ "foo" "10" "bar" ]; expr = showAttrPath [ "foo" "10" "bar" ];

View File

@ -284,7 +284,7 @@ in
in in
# We will generate every possible pair of WM and DM. # We will generate every possible pair of WM and DM.
concatLists ( concatLists (
builtins.map lib.mapCartesianProduct
({dm, wm}: let ({dm, wm}: let
sessionName = "${dm.name}${optionalString (wm.name != "none") ("+" + wm.name)}"; sessionName = "${dm.name}${optionalString (wm.name != "none") ("+" + wm.name)}";
script = xsession dm wm; script = xsession dm wm;
@ -312,7 +312,7 @@ in
providedSessions = [ sessionName ]; providedSessions = [ sessionName ];
}) })
) )
(cartesianProductOfSets { dm = dms; wm = wms; }) { dm = dms; wm = wms; }
); );
}; };

View File

@ -5,7 +5,7 @@
let let
inherit (import ../lib/testing-python.nix { inherit system pkgs; }) makeTest; inherit (import ../lib/testing-python.nix { inherit system pkgs; }) makeTest;
testCombinations = pkgs.lib.cartesianProductOfSets { testCombinations = pkgs.lib.cartesianProduct {
predictable = [true false]; predictable = [true false];
withNetworkd = [true false]; withNetworkd = [true false];
systemdStage1 = [true false]; systemdStage1 = [true false];

View File

@ -63,16 +63,15 @@ in stdenv.mkDerivation {
runHook postCheck runHook postCheck
''; '';
meta = { meta = with lib; {
description = "Sandboxed execution environment"; description = "Sandboxed execution environment";
homepage = "https://github.com/solo5/solo5"; homepage = "https://github.com/solo5/solo5";
license = lib.licenses.isc; license = licenses.isc;
maintainers = with lib.maintainers; [ ehmry ]; maintainers = [ maintainers.ehmry ];
platforms = builtins.map ({arch, os}: "${arch}-${os}") platforms = mapCartesianProduct ({ arch, os }: "${arch}-${os}") {
(lib.cartesianProductOfSets { arch = [ "aarch64" "x86_64" ];
arch = [ "aarch64" "x86_64" ]; os = [ "freebsd" "genode" "linux" "openbsd" ];
os = [ "freebsd" "genode" "linux" "openbsd" ]; };
});
}; };
} }

View File

@ -15,14 +15,13 @@ let
''); '');
in in
builtins.listToAttrs ( builtins.listToAttrs (
map lib.mapCartesianProduct texTest
texTest {
(lib.attrsets.cartesianProductOfSets {
tex = [ "xelatex" "lualatex" ]; tex = [ "xelatex" "lualatex" ];
fonttype = [ "ttf" "otf" ]; fonttype = [ "ttf" "otf" ];
package = [ "junicode" ]; package = [ "junicode" ];
file = [ ./test.tex ]; file = [ ./test.tex ];
}) }
++ ++
[ [
(texTest { (texTest {

View File

@ -9,9 +9,8 @@ let
palette = [ "Frappe" "Latte" "Macchiato" "Mocha" ]; palette = [ "Frappe" "Latte" "Macchiato" "Mocha" ];
color = [ "Blue" "Dark" "Flamingo" "Green" "Lavender" "Light" "Maroon" "Mauve" "Peach" "Pink" "Red" "Rosewater" "Sapphire" "Sky" "Teal" "Yellow" ]; color = [ "Blue" "Dark" "Flamingo" "Green" "Lavender" "Light" "Maroon" "Mauve" "Peach" "Pink" "Red" "Rosewater" "Sapphire" "Sky" "Teal" "Yellow" ];
}; };
product = lib.attrsets.cartesianProductOfSets dimensions;
variantName = { palette, color }: (lib.strings.toLower palette) + color; variantName = { palette, color }: (lib.strings.toLower palette) + color;
variants = map variantName product; variants = lib.mapCartesianProduct variantName dimensions;
in in
stdenvNoCC.mkDerivation rec { stdenvNoCC.mkDerivation rec {
pname = "catppuccin-cursors"; pname = "catppuccin-cursors";

View File

@ -7,14 +7,13 @@ let
thickness = [ "" "Slim_" ]; # Thick or slim edges. thickness = [ "" "Slim_" ]; # Thick or slim edges.
handedness = [ "" "LH_" ]; # Right- or left-handed. handedness = [ "" "LH_" ]; # Right- or left-handed.
}; };
product = lib.cartesianProductOfSets dimensions;
variantName = variantName =
{ color, opacity, thickness, handedness }: { color, opacity, thickness, handedness }:
"${handedness}${opacity}${thickness}${color}"; "${handedness}${opacity}${thickness}${color}";
variants = variants =
# (The order of this list is already good looking enough to show in the # (The order of this list is already good looking enough to show in the
# meta.longDescription.) # meta.longDescription.)
map variantName product; lib.mapCartesianProduct variantName dimensions;
in in
stdenvNoCC.mkDerivation rec { stdenvNoCC.mkDerivation rec {
pname = "comixcursors"; pname = "comixcursors";

View File

@ -70,7 +70,7 @@ stdenv.mkDerivation rec {
maintainers = [ maintainers.sternenseemann ]; maintainers = [ maintainers.sternenseemann ];
homepage = "https://github.com/mirage/ocaml-freestanding"; homepage = "https://github.com/mirage/ocaml-freestanding";
platforms = builtins.map ({ arch, os }: "${arch}-${os}") platforms = builtins.map ({ arch, os }: "${arch}-${os}")
(cartesianProductOfSets { (cartesianProduct {
arch = [ "aarch64" "x86_64" ]; arch = [ "aarch64" "x86_64" ];
os = [ "linux" ]; os = [ "linux" ];
} ++ [ } ++ [