No Three Snowflakes Are Alike

This article was published on .

I developed a Sass function to generate a list of unique pairs given a List or Map of data, so I extrapolated the idea and refactored the function to generate a series of unique groups of size n.

In Unique Pairs in Sass I outlined a Sass function I developed in pursuit of generating a list of unique pairs from a given list of data. While this technique certainly isn’t so powerful or far-reaching as to cause you to refactor all of your Sass, once you find yourself needing to dynamically generate unique pairs, the Sass function shoulders the weight of any complexity. Things like between X and Y are perfectly suited to leverage this function.

But what about when you want to generate unique groups of more than 2 items? Multiples of 3, 4, n?

We’ll need to make sure our dataset contains more than n items—making unique groups of three items from a dataset of only three items would make our function entirely moot. From there, refactoring the function mostly comes down to keep track of how we’re iterating through the items in the dataset and repeating this iteration in a recursive-like way the same number of times as items per unique group.

Let’s just jump right into it

@function unique-groups($data, $size: 2) {
@if not $data or not (type-of($data) == list or type-of($data) == map) {
@warn "`unique-groups()` expects either a single List or single Map for `$data`.";
@return false;
}

$unique-groups: ();

@if type-of($data) == list {
$seen-first: ();
@each $first in $data {
$seen-first: append($seen-first, $first);
$seen-second: ();
@each $second in $data {
$seen-second: append($seen-second, $second);
@if $first != $second and not index($seen-first, $second) {
@if $size >= 3 {
@each $third in $data {
@if $second != $third and not index($seen-second, $third) {
$unique-group: (
$first,
$second,
$third
);
$unique-groups: append($unique-groups, $unique-group);
}
}
}
@else {
$unique-group: (
$first,
$second
);
$unique-groups: append($unique-groups, $unique-group);
}
}
}
}
}
@else if type-of($data) == map {
$seen-first: ();
@each $first-key, $first-value in $data {
$seen-first: append($seen-first, $first-key);
$seen-second: ();
@each $second-key, $second-value in $data {
$seen-second: append($seen-second, $second-key);
@if $first-key != $second-key and not index($seen-first, $second-key) {
@if $size >= 3 {
@each $third-key, $third-value in $data {
@if $second-key != $third-key and not index($seen-second, $third-key) {
$unique-group: (
($first-key: $first-value),
($second-key: $second-value),
($third-key: $third-value)
);
$unique-groups: append($unique-groups, $unique-group);
}
}
}
@else {
$unique-group: (
($first-key: $first-value),
($second-key: $second-value)
);
$unique-groups: append($unique-groups, $unique-group);
}
}
}
}
}

@else {
@warn "`unique-groups()` expects either a List or Map `$data` parameter.";
@return false;
}

@return $unique-groups;
}

As with the previous version of the function, it can accept the required dataset as either a List or Map. Using our mathematical formula from before, we can plug in our variables and figure out how many unique multiples to expect from a dataset.

Let n = size of dataset
Let m = items / group

n(n−1)⁄m

So, from a dataset of size 4, if we want a group size of 3, we can expect 4 unique groups:

4(4−1)⁄3 = 4

In Action

$border-styles: 5px solid black;

$list:
top,
right,
bottom,
left;

@each $unique-group in unique-groups($list, 3) {
$unique-group-first: nth($unique-group, 1);
$unique-group-second: nth($unique-group, 2);
$unique-group-third: nth($unique-group, 3);

.border--#{$unique-group-first}-and-#{$unique-group-second}-and-#{$unique-group-third} {
border-#{$unique-group-first}: $border-styles;
border-#{$unique-group-second}: $border-styles;
border-#{$unique-group-third}: $border-styles;
}
}
.border--top-and-right-and-bottom {
border-top: 5px solid black;
border-right: 5px solid black;
border-bottom: 5px solid black;
}

.border--top-and-right-and-left {
border-top: 5px solid black;
border-right: 5px solid black;
border-left: 5px solid black;
}

.border--top-and-bottom-and-left {
border-top: 5px solid black;
border-bottom: 5px solid black;
border-left: 5px solid black;
}

.border--right-and-bottom-and-left {
border-right: 5px solid black;
border-bottom: 5px solid black;
border-left: 5px solid black;
}

If you find a clever use for this @function, I’d love to see it in action on your own projects, so hit me up and let me know!