Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lib/attrsets: add cartesianProductOfSets function #110787

Merged
merged 3 commits into from Jan 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
39 changes: 39 additions & 0 deletions doc/functions/library/attrsets.xml
Expand Up @@ -1711,4 +1711,43 @@ recursiveUpdate
</example>
</section>

<section xml:id="function-library-lib.attrsets.cartesianProductOfSets">
<title><function>lib.attrsets.cartesianProductOfSets</function></title>

<subtitle><literal>cartesianProductOfSets :: AttrSet -> [ AttrSet ]</literal>
</subtitle>

<xi:include href="./locations.xml" xpointer="lib.attrsets.cartesianProductOfSets" />

<para>
Return the cartesian product of attribute set value combinations.
</para>

<variablelist>
<varlistentry>
<term>
<varname>set</varname>
</term>
<listitem>
<para>
An attribute set with attributes that carry lists of values.
</para>
</listitem>
</varlistentry>
</variablelist>

<example xml:id="function-library-lib.attrsets.cartesianProductOfSets-example">
<title>Creating the cartesian product of a list of attribute values</title>
<programlisting><![CDATA[
cartesianProductOfSets { a = [ 1 2 ]; b = [ 10 20 ]; }
=> [
{ a = 1; b = 10; }
{ a = 1; b = 20; }
{ a = 2; b = 10; }
{ a = 2; b = 20; }
]
]]></programlisting>
</example>
</section>

</section>
19 changes: 18 additions & 1 deletion lib/attrsets.nix
Expand Up @@ -183,6 +183,24 @@ rec {
else
[];

/* Return the cartesian product of attribute set value combinations.

Example:
cartesianProductOfSets { a = [ 1 2 ]; b = [ 10 20 ]; }
=> [
{ a = 1; b = 10; }
{ a = 1; b = 20; }
{ a = 2; b = 10; }
{ a = 2; b = 20; }
]
*/
cartesianProductOfSets = attrsOfLists:
lib.foldl' (listOfAttrs: attrName:
concatMap (attrs:
map (listValue: attrs // { ${attrName} = listValue; }) attrsOfLists.${attrName}
) listOfAttrs
) [{}] (attrNames attrsOfLists);


/* Utility function that creates a {name, value} pair as expected by
builtins.listToAttrs.
Expand Down Expand Up @@ -493,5 +511,4 @@ rec {
zipWithNames = zipAttrsWithNames;
zip = builtins.trace
"lib.zip is deprecated, use lib.zipAttrsWith instead" zipAttrsWith;

}
2 changes: 1 addition & 1 deletion lib/default.nix
Expand Up @@ -78,7 +78,7 @@ let
zipAttrsWithNames zipAttrsWith zipAttrs recursiveUpdateUntil
recursiveUpdate matchAttrs overrideExisting getOutput getBin
getLib getDev getMan chooseDevOutputs zipWithNames zip
recurseIntoAttrs dontRecurseIntoAttrs;
recurseIntoAttrs dontRecurseIntoAttrs cartesianProductOfSets;
inherit (self.lists) singleton forEach foldr fold foldl foldl' imap0 imap1
concatMap flatten remove findSingle findFirst any all count
optional optionals toList range partition zipListsWith zipLists
Expand Down
4 changes: 3 additions & 1 deletion lib/lists.nix
Expand Up @@ -629,7 +629,9 @@ rec {
crossLists (x:y: "${toString x}${toString y}") [[1 2] [3 4]]
=> [ "13" "14" "23" "24" ]
*/
crossLists = f: foldl (fs: args: concatMap (f: map f args) fs) [f];
crossLists = builtins.trace
"lib.crossLists is deprecated, use lib.cartesianProductOfSets instead"
(f: foldl (fs: args: concatMap (f: map f args) fs) [f]);


/* Remove duplicate elements from the list. O(n^2) complexity.
Expand Down
67 changes: 67 additions & 0 deletions lib/tests/misc.nix
Expand Up @@ -660,4 +660,71 @@ runTests {
expected = [ [ "foo" ] [ "foo" "<name>" "bar" ] [ "foo" "bar" ] ];
};

testCartesianProductOfEmptySet = {
expr = cartesianProductOfSets {};
expected = [ {} ];
};

testCartesianProductOfOneSet = {
expr = cartesianProductOfSets { a = [ 1 2 3 ]; };
expected = [ { a = 1; } { a = 2; } { a = 3; } ];
};

testCartesianProductOfTwoSets = {
expr = cartesianProductOfSets { a = [ 1 ]; b = [ 10 20 ]; };
expected = [
{ a = 1; b = 10; }
{ a = 1; b = 20; }
];
};

testCartesianProductOfTwoSetsWithOneEmpty = {
expr = cartesianProductOfSets { a = [ ]; b = [ 10 20 ]; };
expected = [ ];
};

testCartesianProductOfThreeSets = {
expr = cartesianProductOfSets {
a = [ 1 2 3 ];
b = [ 10 20 30 ];
c = [ 100 200 300 ];
};
expected = [
{ a = 1; b = 10; c = 100; }
{ a = 1; b = 10; c = 200; }
{ a = 1; b = 10; c = 300; }

{ a = 1; b = 20; c = 100; }
{ a = 1; b = 20; c = 200; }
{ a = 1; b = 20; c = 300; }

{ a = 1; b = 30; c = 100; }
{ a = 1; b = 30; c = 200; }
{ a = 1; b = 30; c = 300; }

{ a = 2; b = 10; c = 100; }
{ a = 2; b = 10; c = 200; }
{ a = 2; b = 10; c = 300; }

{ a = 2; b = 20; c = 100; }
{ a = 2; b = 20; c = 200; }
{ a = 2; b = 20; c = 300; }

{ a = 2; b = 30; c = 100; }
{ a = 2; b = 30; c = 200; }
{ a = 2; b = 30; c = 300; }

{ a = 3; b = 10; c = 100; }
{ a = 3; b = 10; c = 200; }
{ a = 3; b = 10; c = 300; }

{ a = 3; b = 20; c = 100; }
{ a = 3; b = 20; c = 200; }
{ a = 3; b = 20; c = 300; }

{ a = 3; b = 30; c = 100; }
{ a = 3; b = 30; c = 200; }
{ a = 3; b = 30; c = 300; }
];
};
}
6 changes: 3 additions & 3 deletions nixos/modules/services/x11/display-managers/default.nix
Expand Up @@ -444,8 +444,8 @@ in
in
# We will generate every possible pair of WM and DM.
concatLists (
crossLists
(dm: wm: let
builtins.map
({dm, wm}: let
sessionName = "${dm.name}${optionalString (wm.name != "none") ("+" + wm.name)}";
script = xsession dm wm;
desktopNames = if dm ? desktopNames
Expand All @@ -472,7 +472,7 @@ in
providedSessions = [ sessionName ];
})
)
[dms wms]
(cartesianProductOfSets { dm = dms; wm = wms; })
);

# Make xsessions and wayland sessions available in XDG_DATA_DIRS
Expand Down
8 changes: 6 additions & 2 deletions nixos/tests/predictable-interface-names.nix
Expand Up @@ -5,7 +5,11 @@

let
inherit (import ../lib/testing-python.nix { inherit system pkgs; }) makeTest;
in pkgs.lib.listToAttrs (pkgs.lib.crossLists (predictable: withNetworkd: {
testCombinations = pkgs.lib.cartesianProductOfSets {
predictable = [true false];
withNetworkd = [true false];
};
in pkgs.lib.listToAttrs (builtins.map ({ predictable, withNetworkd }: {
name = pkgs.lib.optionalString (!predictable) "un" + "predictable"
+ pkgs.lib.optionalString withNetworkd "Networkd";
value = makeTest {
Expand All @@ -30,4 +34,4 @@ in pkgs.lib.listToAttrs (pkgs.lib.crossLists (predictable: withNetworkd: {
machine.${if predictable then "fail" else "succeed"}("ip link show eth0")
'';
};
}) [[true false] [true false]])
}) testCombinations)
9 changes: 5 additions & 4 deletions pkgs/os-specific/solo5/default.nix
Expand Up @@ -50,10 +50,11 @@ in stdenv.mkDerivation {
homepage = "https://github.com/solo5/solo5";
license = licenses.isc;
maintainers = [ maintainers.ehmry ];
platforms = lib.crossLists (arch: os: "${arch}-${os}") [
[ "aarch64" "x86_64" ]
[ "freebsd" "genode" "linux" "openbsd" ]
];
platforms = builtins.map ({arch, os}: "${arch}-${os}")
(cartesianProductOfSets {
arch = [ "aarch64" "x86_64" ];
os = [ "freebsd" "genode" "linux" "openbsd" ];
});
};

}