使用 CSS 检测和计算素数

发布于 2022-07-09 21:16:08 字数 15772 浏览 946 评论 5

如果您不想阅读所有这些,最后的演示在这里: https ,如果您愿意,请查看源代码。

本文可能涉及以下内容:

  • 如何决定和选择素数
  • CSS 计数器及其范围
  • 伪元素
  • 生成的内容
  • 层叠样式
  • 弹性盒布局

灵感

有一天,当我阅读 nth-child伪类,我想到是否 nth-child伪类可用于确定素数。 条件是它可以用来 选择位于任意数的每个倍数中的特定元素

如果我选择多个位置中除它们自身之外的所有元素,则其余元素将位于主要位置。

多么有趣的事情! 我一想到就写下来。 这是第一个版本:

<style>
  li:first-child {
    color: grey;
  }
  li:nth-child(2n + 4) {
    color: grey;
  }
  li:nth-child(3n + 6) {
    color: grey;
  }
  li:nth-child(4n + 8) {
    color: grey;
  }
  li:nth-child(5n + 10) {
    color: grey;
  }
</style>
<ul>
  <li>01</li>
  <li>02</li>
  <li>03</li>
  <li>04</li>
  <li>05</li>
  <li>06</li>
  <li>07</li>
  <li>08</li>
  <li>09</li>
  <li>10</li>
</ul>

上面的代码将所有不在主要位置的元素变成灰色。

注意参数 nth-child伪类是 Xn + 2X代替 Xn + X, 因为 n开始于 0. 我们需要选择的就是所有 X的倍数,除了它自己。 所以最小选择的数字是 2X. 我们只需要写到 5n + 10,因为 6 的倍数大于 10。

上面所有选择器的声明块都是一样的,因此我们可以将所有的选择器组合成一个组合选择器。

<style>
  li:first-child,
  li:nth-child(2n + 4),
  li:nth-child(3n + 6),
  li:nth-child(4n + 8),
  li:nth-child(5n + 10) {
    color: grey;
  }
</style>
<ul>
  <li>01</li>
  <li>02</li>
  <li>03</li>
  <li>04</li>
  <li>05</li>
  <li>06</li>
  <li>07</li>
  <li>08</li>
  <li>09</li>
  <li>10</li>
</ul>

现在看起来好多了。

突出质数,淡化非质数

问题是如果我们想突出显示所有素数怎么办。 上面代码中的选择器没有选择素数。

我们可以做到,很简单。 将所有项目设为红色,将非素数项目设为灰色。 对非素数项的选择器处理具有更高的优先级,从而使素数突出显示。

<style>
  /*优先级为 0,0,0,1*/
  li {
    color: red;
  }

  /*优先级为 0,0,1,1*/
  li:first-child,
  li:nth-child(2n + 4),
  li:nth-child(3n + 6),
  li:nth-child(4n + 8),
  li:nth-child(5n + 10) {
    color: grey;
  }
</style>
<ul>
  <li>01</li>
  <li>02</li>
  <li>03</li>
  <li>04</li>
  <li>05</li>
  <li>06</li>
  <li>07</li>
  <li>08</li>
  <li>09</li>
  <li>10</li>
</ul>

但是还有一个问题,如果我们想用这种方法突出显示所有小于 100 的素数,第二个组合选择器会是 50 行,太多了。

减少选择器的数量

通过观察我们可以发现: li:nth-child(4n + 8)selector 不用写:它选择了除 4 以外的所有 4 的倍数,但实际上 li:nth-child(2n + 4)selector 已经选择了包括 4 在内的所有 4 的多项。类似地,我们可以推断如果写 li:nth-child(3n + 6)选择器,不必写 li:nth-child(6n + 12), li:nth-child(9n + 18)等等。 n 之后的 3 的倍数的 都不需要写。

实际上,如果删除所有不必要的选择器,您会发现 n 之前的系数在其余选择器中都是素数。 如果 n 之前的系数是非质数,则这个非质数的所有倍数都将被其质因数的倍数所选择。 即一个合数的所有倍数都是一个素因数的倍数的子集,这使得n之前的所有合数系数都不需要存在。 这类似于我们在小学学习的寻找素数的筛选方法。 这就是筛选过程 sieve of Eratosthenes方法。

但是,为了更快地过滤,我们可以从 Xn + X * X过滤掉一个因素的数字 X的倍数。 因为如果你过滤掉了所有小于的素数倍数 X, 所有小于的合数 X * X已被筛选出来。 由于任何合数小于 X * X必须能够找到至少一个素数的除数小于 X.

而根据上述规则,如果我们要过滤掉M内的所有素数,我们只需要过滤掉所有小于等于M平方根的素数的倍数即可。

因此,如果我们想过滤掉 100 以内的所有素数,下面的组合选择器就足够了:

<style>
  li {
    color: red;
  }
  li:first-child,
  li:nth-child(2n + 4),
  li:nth-child(3n + 6),
  li:nth-child(4n + 8),
  li:nth-child(5n + 10),
  li:nth-child(7n + 14) {
    color: grey;
  }
</style>

小于或等于 100 的平方根的最大素数是 7,所以我们的选择器写到 li:(7n + 14).

代码量复杂度(我发明了这个词)

事实上,绕了一大圈之后,素数筛选的原理以另一种形式得到了证明。

作为结论,我们只需要 其中的素数个数 Sqrt(M)选择器筛选所有小于 M 的数字。

那么,还有一个问题,有多少个质数小于某个数? 其实我们的前人已经研究过这个问题:

内的素数个数 n是关于 n/ln(n). 数字越大 n,素数的个数更接近这个公式的值。 的更多信息,请参见此处 Prime Counting Functions

所以我们大概可以使用 O(sqrt(n)/ln(sqrt(n))过滤掉所有素数的 CSS 代码(更具体地说,选择器) n. 对于小于 1000 的素数,我们只需要 12 个组合选择器的选择器:

<style>
  li {
    color: red;
  }
  li:first-child,
  li:nth-child(2n + 4),
  li:nth-child(3n + 6),
  li:nth-child(5n + 10),
  li:nth-child(7n + 14),
  li:nth-child(11n + 22),
  li:nth-child(13n + 26),
  li:nth-child(17n + 34),
  li:nth-child(19n + 38),
  li:nth-child(23n + 46),
  li:nth-child(29n + 58),
  li:nth-child(31n + 62) {
    color: #ddd;
  }
</style>
<ul>
  <li>01</li>
  <li>02</li>
  <li>03</li>
  <li>04</li>
  <li>05</li>
  ...
  <li>996</li>
  <li>997</li>
  <li>998</li>
  <li>999</li>
  <li>1000</li>
</ul>

在上面的代码中,伪类选择器参数没有写入 Xn + X * X, 因为使用 2X将使我们的代码量更少。 由于平方比它的双倍占据更多的数字,例如,4 乘以 2 是 8,但 4 的平方是 16,比 8 长。

自动计数

问题又出现了,上面的代码,我们还是要在里面放一个数字 li标签。 这些标签可以用 JS 生成。 但对于一个强迫症极客来说,这让我们感到不舒服。 更重要的是,我提到我们将使用 CSS 来决定和选择素数。

您可能认为我们可以替换 ul标记为 ol标签。 在这种情况下, li标签的列表标记将自动为数字。 确实有道理,但是目前CSS很难控制列表项编号的样式。 比如我想调整它的位置,就没有办法了。

此外,即使我们使用 ul标记而不是 ol标签 li标签的列表标记也可以设置为数字。 即,设置 list-style-type的属性 li元素到 decimal或者 decimal-leading-zero是答案。

那么,有没有办法用 CSS 生成这些数字呢?

我们可以使用 CSS 计数器 和生成的内容来插入这些数字。

<style>
  li {
    /*遍历 DOM 的过程中,每遇到 li 就让 nature-count 计数器变量的值加一*/
    /*Every time we encounter li, we make nature-count++*/
    counter-increment: nature-count;
  }
  li::before {
    /*在 li 的 before 伪元素中插入计数器变量 nature-count 当前的值*/
    /*insert the counter value as generated content by pseudo element*/
    content: counter(nature-count);
  }
</style>
<ul>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
</ul>

渲染结果如下所示:

关于 CSS 计数器,可以参考 这里的 MDN 文档

既然它可以数数,那我想知道它是否可以数出素数和非素数的个数。

我们可以很容易地数出非质数的个数,因为前面的 li:nth-child 选择器选择了那些非主要项目,我们只需将计数器增加 1当我们遇到他们时:

<style>
  li {
    counter-increment: nature-count
  }
  li::before {
    content: counter(nature-count);
  }
  li:first-child,
  li:nth-child(2n + 4),
  li:nth-child(3n + 6),
  li:nth-child(5n + 10),
  li:nth-child(7n + 14) {
    color: grey;
    counter-increment: nonprime-count;
  }
</style>
<ul>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
</ul>

但是,渲染结果与我们预期的不一样:

分析原因,我们会发现,是因为非素数选择器的 counter-increment 属性把 li 选择器对应的这个属性覆盖了,CSS 在发生属性覆盖时,是不会将两个相同属性值联合起来的,而是会选择最终生效的那一个,此处对于素数位置上的 li 元素,显然是 counter-increment: nonprime-count; 这一句会生效。所以导致了当解析器遇到合数位置上的 li 元素时,只给 nonprime-count 计数器加了一,知道了原因,就很好解决了,我们让遇到这个元素时同时给自然数计数器和非素数计数器都加一:counter-increment: nature-count nonprime-count;

我们发现,因为 counter-increment非主选择器的属性覆盖对应的属性 li选择器,也就是当我们遇到一个 li标记在非主要位置,只有 nonprime-counter会增加,但不会 nature-counternonprime-counter两者都增加。 当属性被覆盖时,CSS 不会组合相同的两个属性值 ,它会选择它的选择器具有更高优先级的一个。 在这里,对于 li非素数位置上的元素,显然是 counter-increment: nonprime-count;这将优先。 所以当解析器遇到 li位置上的元素,它只加一到 nonprime-count柜台。 知道原因后很容易解决这个问题。 我们可以加一到 nature-count计数器和 nonprime-count如果非主要位置则计数器 li遇到: counter-increment: nature-count nonprime-count;

而且,我们得到了正确的结果:

显示统计结果

我们可以在 ul 的后面加一个标签,以便统计数据显示在里面。

<style>
  li {
    counter-increment: nature-count
  }
  li::before {
    content: counter(nature-count);
  }
  li:first-child,
  li:nth-child(2n + 4),
  li:nth-child(3n + 6),
  li:nth-child(5n + 10),
  li:nth-child(7n + 14) {
    color: grey;
    counter-increment: nature-count nonprime-count;
  }
  p::before {
    content: '前' counter(nature-count) '个自然数中,有' counter(nonprime-count) '个合数' '' '' ;
  }
</style>
<ul>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
</ul>
<p></p>

但结果又出乎我们的意料:

这两个 CSS 计数器变量显然存在值,并将其插入到 li刚才的伪元素。 为什么会变成0?

CSS 计数器的范围

要理解这一点,我们需要了解 CSS 计数器范围的概念:计数器 的范围仅在最外层元素的父元素内,可以对其产生影响

在上面的例子中,两个计数器的计数元素是 li,所以这两个计数器只在父元素内有效 li,即在 ul. 要解决这个问题也很简单,我们只需要让最外面的元素影响计数器。 我们可以在遇到 body元素,通过这种方式,该计数器在整个页面中可用:

<style>
  body {
    counter-reset: nature-count nonprime-count;
  }
  li {
    counter-increment: nature-count
  }
  li::before {
    content: counter(nature-count);
  }
  li:first-child,
  li:nth-child(2n + 4),
  li:nth-child(3n + 6),
  li:nth-child(5n + 10),
  li:nth-child(7n + 14) {
    color: grey;
    counter-increment: nature-count nonprime-count;
  }
  p::before {
    content: '前' counter(nature-count) '个自然数中,有' counter(nonprime-count) '个合数' '' '' ;
  }
</style>
<ul>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
</ul>
<p></p>

这就是我们想要的结果

现在我们已经数完了自然数的个数和合数的个数,但是如何知道素数的个数呢?
我们都知道CSS不能做减法。 此外,这两个值存在于 CSS 计数器中, calc函数只能实现硬编码字面量值的计算,结果不能直接显示值。

所以我们必须要找到一种让素数计数器递增的方法。这就意味着,我们必须使用选择器选出素数项才可以!
好像有点无能为力了,nth-child 选出非素数好办,但是选出素数,肯定没有能够实现这件事的选择器了。

黑暗中总有一线光明

我们还有 not伪类选择器! 因为我们可以使用 nth-child选择所有合数的伪类,这些选择器可以充当 not伪类选择器的参数。 在这种情况下,可以选择所有素数。

li:not(:first-child):not(:nth-child(2n + 4)):not(:nth-child(3n + 6)) {
  color: red;
  counter-increment: prime-count nature-count;
}

伪类选择器可以组合在一起,所以我们可以组合一些 not伪类选择器,并使合数的选择器为 not的参数。 通过这种方法,可以达到只选择素数的目的。 然后我们在选择器中添加一个计数器,以达到计算素数的目的。

只有最后一个问题,即统计结果总是显示在底部。 如果数据相对较小,则效果很好。 但是如果数据很大,效果就不太好了,因为如果你想看到统计结果,你必须滚动到页面底部。

假设我们把p标签移到ul前面,统计数据会显示0,因为每个计数器变量的值还是0。这就是为什么在DOM结构中p标签必须出现在ul后面的原因。

我们当然可以使用绝对定位将p标签移动到顶部,但是控制起来并不容易。

如果可以让计数器有值,并且没有绝对定位,除了让p标签的内容出现在ul前面,那就太好了。

还有一个方法,就是我们可以使用flex布局的 order属性。 它可以在不改变 DOM 结构的情况下改变文档中显示的元素的顺序。 因为counter的计数只与DOM结构有关,不会影响统计结果的正确性。

最终代码如下:

<style>
  body {
    /*用body元素的counter-reset属性重置三个计数器以使它们的作用域在整个body内*/
    /*make the counters global by reset them by body tag*/
    counter-reset: nature-count prime-count nonprime-count;
    display: flex;
    flex-direction: column;
  }

  li {
    list-style-type: none;
    display: inline-block;
  }

  /*在before伪元素中插入计数器的值以实现数值递增*/
  /*insert counter value into li tag*/
  li::before {
    content: counter(nature-count) ',';
  }
  li:last-child::before {
    content: counter(nature-count);/*最后一个元素不需要逗号分隔*/
    /*the last element do not neet a comma after it*/
  }

  /*合数项选择器*/
  /*non-prime selector*/
  li:first-child,
  li:nth-child(2n + 4),
  li:nth-child(3n + 6),
  li:nth-child(5n + 10),
  li:nth-child(7n + 14) {
    /*递增自然数与合数计数器*/
    /*increase nature and non-prime counter*/
    counter-increment: nature-count nonprime-count;
    color: #ddd;/*合数变灰*/
    /*如果想只显示素数项,可以把合数全部隐藏起来*/
    /*display为none并不影响计数器的计数*/
    /*display: none;*/
  }
  /*素数项选择器*/
  /*prime selectors*/
  li:not(:first-child):not(:nth-child(2n + 4)):not(:nth-child(3n + 6)):not(:nth-child(5n + 10)) {
    /*递增自然数与素数计数器*/
    counter-increment: nature-count prime-count;
    color: red;/*素数变红*/
  }

  p {
    order: -1;/*让p元素显示在ul的前面*/
  }
  p::before {
    /*通过p标签的before伪元素插入统计结果*/
    content: '前 ' counter(nature-count) ' 个自然数中,有 ' counter(nonprime-count) ' 个合数,' counter(prime-count) ' 个素数' ;
  }
</style>
<ul>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
</ul>
<p></p>

您可以添加任意数量的 li 随时标记 ul 显示更广泛的素数和统计结果,而无需在其他任何地方更改代码。

渲染结果显示如下,题目是1000个数字的渲染效果:

Complete demo is here: CSS Prime

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

幽梦紫曦~ 2022-05-04 13:46:05

@joezimjs Wow, very clever idea!!! I have take a while understanding it~this is exactly how cascading works, thanks

瞎闹 2022-05-04 13:46:03

The :not selector part is completely unnecessary. Just put it in the normal li styles and the li:nth-child... stuff will override it when it's not a prime number.

body {
  counter-reset: nature-count prime-count nonprime-count;
  display: flex;
  flex-direction: column;
}

li {
  list-style-type: none;
  display: inline-block;

  /* Everything for prime numbers can go here because non-primes will override it */
  counter-increment: nature-count prime-count;
  color: red;
}

li::before {
  content: counter(nature-count) ',';
}
li:last-child::before {
  content: counter(nature-count);
}

li:first-child,
li:nth-child(2n + 4),
li:nth-child(3n + 6),
li:nth-child(5n + 10),
li:nth-child(7n + 14) {
  counter-increment: nature-count nonprime-count;
  color: #ddd;
}

p::before {
  content: 'Count:' counter(nature-count) '; Non-Primes:' counter(nonprime-count) '; Primes:' counter(prime-count);
}
我不咬妳我踢妳i 2022-05-04 13:45:58

Of course you could automate this bit with Sass:

  li:first-child,
  li:nth-child(2n + 4),
  li:nth-child(3n + 6),
  li:nth-child(5n + 10),
  li:nth-child(7n + 14)
北城孤痞 2022-05-04 13:20:58

I suppose you could just use an <ol> ordered list to get the numbers.

I wonder if you could use selectors + css counter to only output prime numbers without hidding stuff?

回忆凄美了谁 2022-05-04 10:39:17

Yeah I tried highlighting primes about 2 years ago and came up with a similar thing! https://codepen.io/matthewfelgate/pen/VYazKa?editors=1100

~没有更多了~

关于作者

随心而道

暂无简介

0 文章
0 评论
24 人气
更多

推荐作者

我们的影子

文章 0 评论 0

素年丶

文章 0 评论 0

南笙

文章 0 评论 0

18215568913

文章 0 评论 0

qq_xk7Ean

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文