小程序中的大道理之三--对称性和耦合问题

本文探讨了编程中的对称性概念,如何通过调整代码实现结构对称,并结合抽象和MVC设计模式,强调了程序结构与问题结构的关系。同时,文章提及了数据模型的解耦和灵活性在软件设计中的重要性。

再继续扒

继续 前一篇 的话题, 在那里, 提到了抽象, 耦合及 MVC, 现在继续探讨这些, 不过在此之前先说下第一篇里提到的对称性.

注: 以下讨论建立在前面的基础之上, 为控制篇幅起见, 这里将不再重复前面说到的部分, 如果您还没看过前两篇章, 阅读起来可能会有些困难.

这是第一篇的链接小程序中的大道理

对称性(Symmetry)

这里先说下对称性的问题. 问题中的图案是左右对称的, 但到目前为此, 我们的代码却不是对称的, 输出之所以对称原因在于空格与背景之间难以区分.

让我们换个符号, 比如脱字符"^", 再输出一下, 就可以看出不对称来, 因为在右边我们输出星号后就直接换行了:

star and caret not symmetry

只要稍微调整一下程序, 在输出换行之前再输出一下空格(或者现在的脱字符"^"), 就能满足对称的输出了:

private String getLineContent(int lineCount, int lineNumber) {
    StringBuilder content = new StringBuilder();
    
    String firstPart = getFirstPart(lineCount, lineNumber);
    // 1. 空格部分
    content.append(firstPart);
    // 2. 星号部分
    content.append(getSecondPart(lineNumber));
    // 3. 空格部分
    content.append(firstPart);
    // 4. 换行部分
    content.append(System.lineSeparator());
    
    return content.toString();
}

这样之后, 图案是左右对称, 反映在程序上就呈现出上下对称了. 当然也可以把三个语句写在一行里, 这样它也左右对称了:

star symmetry compare to code symmetry

程序是对客观世界的问题的一种映射, 因此程序的结构反映了问题的结构.

当然了, 如果你的程序写得很松散, 结构, 层次不清晰, 就不太容易看出这种对称来.

不知道是否有人意识到了, 在前面我有意无意地忽略了另一个对称问题. 这就是星号的对称, 在上图中只是把它当成对称轴看待.

撇开什么脱字符"^"或者空格, 其实单纯由星号构成的三角形也是对称的, 这是更主要的一个对称:

star symmetry

这种对称又来自哪里呢? 让我们深入到 getSecondPart 里面去:

private String getSecondPart(int lineNumber) {
    int count = getElementCountOfSecondPart(lineNumber);
    StringBuilder part = new StringBuilder();
    for (int i = 0; i < count; i++) {
        part.append(" ");
    }
    return part.toString();
}

很遗憾, 你看不到什么对称. 继续深入到 getElementCountOfSecondPart 去:

public int getElementCountOfSecondPart(int lineNumber) {
    return lineNumber * 2 + 1;
}

好了, 你已经到头了, 可好像还是找不到对称的影子呀? 前面说"程序的结构反映了问题的结构", 难道这个结论并不总是成立的?

有这么一个故事, 我估计很多人都听说过(文字摘自以下网址 http://www.niwota.com/submsg/5682754/):

有一位牧师在某个星期六的早晨正在为明天的讲道稿大伤脑筋. 他的太太外出买东西了, 外面正下着雨, 小儿子失去了户外活动的机会, 在屋里折腾. 牧师的思路一再被儿子打断.

牧师手边正好有本旧杂志, 为了把儿子从身边支开, 他撕下一张彩色世界地图, 再撕成碎片, 丢到客厅地板上对儿子说: "Johnny, 你把它拼成原样, 我就给你一个 Nickle(25 美分镍币). "

儿子有事可做, 又有报酬可得, 积极性很高, 立刻拼了起来. 牧师想, 这下子我可以安静一个上午, 构思自己的讲道稿了. 谁知道只隔了十分钟, 儿子就来敲书房的门了, 说已经拼好. 牧师不相信, 跑到客厅一看, 果然整幅地图完整无缺. 牧师又懊恼又惊奇地问儿子: "你怎么那么快就拼好了呢? "

儿子得意地说, 这再简单不过了, 这张地图的背后印着一幅人物肖像, 我想, 如果这个人拼对了, 世界地图也就拼对了.

牧师忍不住笑了起来, 很高兴地给了儿子一个镍币, 说, 你替我把明天讲道的题目也准备好了: 如果一个人是对的, 那么这个人的世界也是对的.

现在我们的程序能够输出一个对称的图案来, 肯定不是巧合, 而且我也可以肯定地告诉你, 这里面是有对称的. 你可以再仔细找找看, 如果你已经找到或者实在找不出来, 那么可以往下看了:

line num symmetry

现在看来是不是很明显呢? 也许你早也看出来了. 我们能得到有什么启示呢?

运用**直觉(Intuition)**去思考!

如果你的代码中怎么变换也找不出对称来, 当你的人都是错的时候, 你的世界还可能正确吗? 所以你甚至不用费心去上机验证了.

发明了**差分机(difference engine)的计算机先驱查尔斯·巴贝奇(Charles Babbage)**说:

我曾两次被(议员)问到, "巴贝奇先生, 请问假如您往机器里输入了错误的数据, 还会出来正确的答案吗? "我实在无法恰当地理解是怎样的逻辑混乱才会(使他们)提出这样的一个问题.

On two occasions I have been asked [by members of Parliament], ‘Pray, Mr. Babbage, if you put into the machine wrong figures, will the right answers come out?’ I am not able rightly to apprehend the kind of confusion of ideas that could provoke such a question.

看得出来巴贝奇的评论还是很客气的, 他当时似乎在寻求议员拨款来支持他的差分机研究, 我估计他当时内心其实想说: “你丫脑子进水了, 问这种问题!”

巴贝奇谈的是往正确的机器里输入错误的数据的情况, 在这里, 如果连机器里面(即代码)都已经是错的, 那么即便给出了正确输入, 也不可能有正确的输出了.

在某些特殊情况下, 你也许会碰到"负负得正"的情况, 但这不过是巧合而已.

你可能遇到过这样的 bug:

你加了一个新特性, 系统出错了, 你找到一处 bug, 然后你很奇怪, 为何之前居然没问题? 而在你改正这个 bug 后, 之前的一些功能反而不正常了!你可能打死也想不到系统中还有另一个你没发现的 bug 在默默地抵消了它!

诚然, 这样的"负负得正"情况即便能工作也是非常脆弱的.

当事实与直觉不符时, 那么一定有什么地方出错了.

回到我们的问题, 再单独地拿输出空格部分来看:

public int getElementCountOfFirstPart(int lineCount, int lineNumber) {
    return lineCount - lineNumber - 1;
}

有对称吗? 它有两个不同变量, 单独的一份随便你怎么去变换, 你都找不到对称. 这也是为何你需要二份一左一右围绕在星号旁边才能形成对称.

关于直觉, 如果你还有兴趣, 可以见我之前写的另一篇文章(有些读者可能已经看过), 深入图解字符集与字符集编码–定长与变长, 也有谈到用天平之类的模型来直观地思考问题.

本质, 证明以及直觉

按前面那图:

line num symmetry part

如果有人试图从输出的 1, 3, 5, 7 的展开式的对称性来向你证明表达式"lineNumber*2+1"的对称性, 那么呢? 这不过是本末倒置. 正如前面所说, 你把表达式做个变换就可以看出表达式是对称的, 对称是该表达式的本质属性, 所以输出的1, 3, 5, 7 的对称性恰恰是表达式所决定的.

相信很多人都跟作者类似, 在大学的数学课上, 被那些形形色色的恐怖的证明弄得苦不堪言:

这里是在对""弹琴, 数学大""们请走开或请无视(要鄙视, 俺也认了), 这里不是在说你们!你们不会懂的!

如果你的老师只能通过证明来告诉你一件事情为什么是这样, 通常你还是很难明白它为什么是这样, 你甚至可以怀疑老师是否真的深刻理解了这件事情, 要么他就是不打算告诉你真正的原因:

好好反思一下你是否在什么事情上得罪了他? 你写作业是不是全靠"Ctrl+C, Ctrl+V"?

又或者他想让你自己去领悟:

真是用心良苦!你体会到了没有? 你领悟了吗? 要是没有请继续, 学费是不会退滴, 你别领悟到其它地方去了~

而如果你的老师可以通过直觉告诉你事情为什么是这样, 你也许就会说: "啊哈, 原来如此(Aha moment)!"从此你就记住了事情为什么是这样了, 那些冗长的证明你都可以丢到一边去了.

独立的数据模型(Isolated Data Model)

前面说到 getElementCountOfFirstPartgetElementCountOfSecondPart 两个方法达到了抽象的极致, 从而与具体的表现形式解耦. 在有了对称性之后, 我们甚至可以反转两个表现形式, 依然可以呈现出所谓的"三角形"出来:

star triangle compare to space triangle

对比两种情况, 不变的是三个部分中的那些数字:

star triangle number compare to space triangle number

所以这才是图案的"魂", 或者说是它的"意", 抽象出意, 我们就能"得意而忘形".

前面说过也许有一天, 客户的需求可能扩展到 web service 上, 对于一个较大的行数, 需要传输较大的数据, 但有了这种解耦, 我们可以把一个纯粹的数组传递过去:

[3][1][3]
[2][3][2]
[1][5][1]
[0][7][0]

另一方可以在收到这个数据模型后, 再把图形还原.

因为存在对称性, 第三列甚至也可以不传.

灵活性(Flexibility)与松耦合(Loose Couple)

而这个还原过程则可以很灵活地处理, 下图演示了在本地直接利用上述两方法获取图案模式并用 icon 图片展示的效果:

红薯三角形

代码如下(我对 swing 之类的编程也不是很熟, 随便在网上搜来的代码改了下):

public class PatternPic extends JPanel {

    private static final long serialVersionUID = 1L;
    
    private Image image;
    
    public PatternPic(Image image) {
        this.image = image;
    };
    
    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        int width = image.getWidth(this);
        int height = image.getHeight(this);
    
        int lineCount = 4;
        Pattern pattern = new Pattern();
    
        for (int lineNumber = 0, y = 0; lineNumber < lineCount; lineNumber++, y += height) {
            // 直接获取各部分的数目
            int elementCountOfFirst = pattern.getElementCountOfFirstPart(lineCount, lineNumber);
            int elementCountOfSecond = pattern.getElementCountOfSecondPart(lineNumber);
    
            for (int i = 0, x = elementCountOfFirst * width; i < elementCountOfSecond; i++, x += width) {
                g.drawImage(image, x, y, width, height, this);
            }
        }
    }
    
    public static void main(String[] args) throws IOException {
        Image image = ImageIO.read(PatternPic.class.getResource("/logo-git-oschina.png"));
        JFrame frame = new JFrame();
        frame.add(new PatternPic(image));
        frame.setSize(800, 600);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }
}

所以这样的抽象是有很大好处的, 我们说高层的一些抽象如果觉得啰嗦可以去掉一些(如上述就没作过多的抽象), 但这一层的抽象反而要保留. 我们把它作为公共的 API 提供出去, 调用者就不再受限于那些写死的"空格与星号", 这种灵活性与前面说到的可重用性及可扩展性都是紧密相关的.

MVC

MVC 中重要的一点即是强调模型(M, Model)与视图(V, View)的分离. 所谓的模型即是一种独立于视图的数据, 这与我们抽象出来的数据模型是很相似的.

真正的 MVC 与我们这里的例子间或许还有很大差异, 但在解耦合这一点, 两者是一致.

可以对比下最初的玩具式的代码, 那种情况就是一种 紧耦合(tight couple), 而这里则达到了 松耦合(loose couple) 的目的.

在当下, 常常一方面要处理传统的 web 界面, 另一方面又要处理移动端的界面, 抽取出纯粹的数据模型无疑将带给我们很大方便.

在下一篇将谈谈单元测试的问题.

内容概要:本文介绍了基于噪声抑制半监督学习的锂离子电池SOH(State of Health,健康状态)估计方法的Python代码实现。该方法融合半监督学习框架与先进的噪声抑制机制,旨在利用少量标注样本大量未标注数据,有效提升电池健康状态预测的精度与模型鲁棒性,特别适用于实际工程中电池老化数据标注成本高、样本稀缺的挑战性场景。通过设计高效的特征提取网络与可靠的伪标签生成及优化策略,模型能够有效识别并抑制训练过程中的噪声干扰,增强在复杂工况数据波动下的泛化能力与稳定性。; 适合人群:具备一定机器学习理论基础Python编程能力,从事电池管理系统(BMS)、新能源汽车、储能系统等领域的科研人员、工程师,以及专注于电池寿命预测、设备状态监测与智能运维等方向的硕博研究生;; 使用场景及目标:①解决锂离子电池SOH估计中标注数据获取困难、成本高昂的核心痛点;②提升模型在存在测量误差、传感器漂移或异常数据等噪声环境下的预测准确性与可靠性;③为相关科研课题提供可复现、可扩展的算法基准与开源代码框架,加速算法迭代与工程落地; 阅读建议:此资源以Python代码为核心载体,强调算法的完整复现与实验验证过程,建议读者结合代码逐模块剖析模型架构、损失函数设计与训练流程细节,并积极在自有电池数据集上进行迁移学习、参数调优与性能对比,以深入掌握半监督学习与噪声抑制技术在电池退化建模中的关键应用。
内容概要:本文系统阐述了高速串行信号在传输过程中因信道损耗导致的眼图闭合问题,并介绍了主流的均衡技术解决方案。信道作为低通滤波器,会显著衰减高频分量,造成码间干扰(ISI),尤其在速率高于1GHz时更为严重。为恢复信号质量,发送端采用FFE(前馈均衡器)实施预加重或去加重,提前补偿高频损耗;接收端则结合CTLE与DFE进行二次均衡:CTLE通过模拟高通滤波器“削峰填谷”提升高频响应,而DFE利用反馈机制消除后向码间干扰,提高判决准确性。文章详细解析了各均衡模块的工作原理、电路结构、关键参数及其频率响应特性,并通过PCIe 3.0实例展示均衡前后眼图的改善效果,最后对比了不同技术的适用场景与优缺点。; 适合人群:从事高速电路设计、信号完整性分析等相关领域的工程师技术人员,以及具备一定通信原理电子工程基础的研发人员; 使用场景及目标:①理解高速信号传输中的损耗机理与眼图退化成因;②掌握FFE、CTLE、DFE等均衡技术的设计原理与应用场景;③为高速接口(如PCIe、USB等)的信号完整性优化提供理论支持与实践指导; 阅读建议:建议结合仿真工具观察不同均衡参数对眼图的影响,深入理解抽头系数、零极点配置等关键参数的调节逻辑,并关注实际硬件实现中的功耗与噪声权衡问题
内容概要:本文详细介绍了基于光伏逆变器调节的中低压配电网电压分层协调控制策略的Matlab代码实现方法,旨在应对高渗透率光伏发电接入导致的配电网电压越限问题。该策略构建了分层控制架构,上层采用集中式优化算法进行全局决策,下层则依赖本地测量信息实现快速动态响应,从而实现对多个分布式光伏逆变器的协同调控。文中系统阐述了控制模型的数学建模过程、优化求解算法的设计流程以及仿真验证方案,并基于标准测试系统开展了仿真实验,结果表明该方法在抑制电压波动、提升电压稳定性、降低网络损耗方面具有显著优势,具备良好的工程应用前景。; 适合人群:具备电力系统分析、自动控制理论及新能源并网技术等相关专业知识背景,熟悉Matlab/Simulink仿真平台,从事相关领域科研工作的高校研究生、科研院所研究人员及电力行业工程技术开发人员。; 使用场景及目标:①深入研究高比例分布式光伏接入背景下中低压配电网的电压控制难题;②掌握分层协调控制体系的设计理念与实现机制;③学习并实践基于Matlab的电力系统建模、优化算法编程与仿真分析技术;④为实际配电网电压调控项目提供可复现、可拓展的技术参考与解决方案支撑。; 阅读建议:建议读者结合所提供的Matlab代码与典型配电系统模型开展仿真实践,重点剖析控制架构的层级划分逻辑、通信机制设计以及优化目标函数的构建方法,同时可尝试将其拓展至多时间尺度协调控制、考虑通信延迟与数据丢包等更贴近实际的复杂应用场景中进行深入研究。
源码下载地址: https://pan.quark.cn/s/2e09cbca2d82 【针对青龙面板wxpusher推送问题的修复包】是一个为青龙面板(QL Panel)设计的更新或改进版本,其目的是纠正与wxpusher相关的推送故障。青龙面板是一个被广泛应用的自动化部署及管理解决方案,它赋予用户在图形化环境中执行多种IT运维操作的能力,涵盖脚本操作、周期性任务执行以及文件处理等。wxpusher作为青龙面板的一个附加组件,承担着实现微信消息推送的任务,用以保障用户可以即时获取系统关键状态变动或异常警报。 sendNotify.js 是此修复包内含的关键模块,属于wxpusher插件的一部分,其核心职责是发送各类通知。在JavaScript编程语言中,该脚本或许内含了处理推送流程、调用微信推送端点以及消息内容的格式化校验等操作。一旦青龙面板监测到新的事件状况,例如任务成功完成、发生错误或系统进入维护阶段,sendNotify.js便会被激活,将相关数据传送至用户的微信账户。 ql.js 另一个重要的构成部分,可能代表了青龙面板的核心代码库或专用于wxpusher的设定文档。它或许包含了整合wxpusher的相关参数,例如启动过程、授权核实、故障应对日志管理等功能。ql.js与sendNotify.js协同工作,确保wxpusher能够顺利与青龙面板进行数据交互,并可靠地向用户传递通知信息。 CK_WxPusherUid.json 这一文档很可能是储存用户信息或配置参数的JSON格式文件。"CK"通常指代Cookie或某种缓存机制,而"WxPusherUid"可能意指wxpusher用户的专属识别码。该文件用于保存用户微信推送服务的连接详情,让青龙面板可以...
源码链接: https://pan.quark.cn/s/a4b39357ea24 在Java开发中,处理大批量数据时,合理利用线程池可以显著提高程序的执行效率资源利用率。 本文将深入探讨如何在Java中使用线程池来查询大量数据,以及这样做的好处实现方法。 理解线程池的概念至关重要。 线程池是一种多线程处理形式,预先创建了若干个线程,当有任务需要执行时,会从线程池中取出一个线程来执行任务,任务执行完毕后,线程返回线程池中等待新的任务。 这种机制避免了频繁创建销毁线程带来的性能开销,提高了系统的响应速度并发处理能力。 Java中的线程池主要由`java.util.concurrent`包下的`ExecutorService`接口及其实现类提供。 最常见的实现是`ThreadPoolExecutor`,它允许我们自定义线程池的核心参数,如核心线程数、最大线程数、线程存活时间等。 使用线程池查询大批量数据的基本步骤如下: 1. **创建线程池**:我们需要创建一个线程池实例。 通常,我们可以根据实际需求设置线程池参数。 例如,如果数据量大且系统资源充足,可以适当增加线程数量;反之,若系统资源有限,应控制线程数量,避免资源过度消耗。 ```java int corePoolSize = 4; int maximumPoolSize = 8; long keepAliveTime = 60L; TimeUnit unit = TimeUnit.SECONDS; BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(); ThreadFactory threadFactory = Executors.defa...
内容概要:本文围绕基于Wasserstein生成对抗网络(W-GAN)的光伏场景生成程序展开研究,提出了一种利用W-GAN生成高精度、高波动性光伏出力场景的方法,以应对新能源发电中的不确定性挑战。研究通过构建生成器与判别器之间的对抗训练机制,有效捕捉光伏出力的时间序列特征与统计分布规律,生成符合实际运行条件的多样化场景数据,弥补实测数据稀缺问题。相较于传统GAN,W-GAN引入Wasserstein距离作为损失函数,显著提升了模型训练的稳定性与梯度传播的连续性,增强了生成样本的质量与多样性。文中还提供了完整的Python代码实现,便于读者复现与拓展。; 适合人群:具备一定Python编程能力、深度学习基础的研究生、科研人员,以及从事新能源电力系统规划、优化调度、不确定性建模等相关领域的工程师技术人员。; 使用场景及目标:①用于电力系统中可再生能源出力的不确定性建模与风险评估;②支撑微电网、综合能源系统等场景下的随机优化、鲁棒优化与分布鲁棒优化研究;③为风光互补系统、储能配置、需求响应等应用提供高质量、多样化的输入场景;④帮助研究人员掌握深度学习在能源时序数据生成中的前沿应用,推动模型迁移至风电、负荷等其他场景生成任务。; 阅读建议:建议读者结合提供的Python代码进行动手实践,深入理解W-GAN的网络架构设计、损失函数构造、训练技巧及超参数调优策略,重点关注Wasserstein距离在缓解模式崩溃与梯度消失问题中的作用,并尝试将该框架拓展至多变量、多站点或多能源联合场景生成,提升模型的泛化能力与工程实用价值。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值