今天碰到的一个看似简单的算法问题卡的我头疼:余额和构成余额的交易的问题,问 GPT 也没结果

302 天前
nzynzynzy  nzynzynzy

我抽象一下这个问题:invoices 是销售行为,transaction 是对余额的操作行为,一通存取反复拉扯之后形成的余额,尝试去扣款,如果余额足够就允许交易,如果余额不足就拒绝交易。

const invoices = [
  { id: 'INV001', type: 'Buy', amount: 100 }
];

const balanceTransactions = [
  { id: 'JNL001', type: 'transaction', amount: 100 },
  { id: 'JNL002', type: 'transaction', amount: -100 },
  { id: 'JNL003', type: 'transaction', amount: 100 },
  { id: 'JNL004', type: 'transaction', amount: -100 },
  { id: 'JNL005', type: 'transaction', amount: 130}
];

以上情况中,应该是 JNL001 到 JNL005 都被使用了,截至 JNL005 余额还剩 30 块,invoice 是 100 块。换句话说,就是尽可能多地查找余额的操作行为,确定哪些行为可以归结到这张 invoice 下。

请问这叫什么算法,以下是 GPT 的结果肯定是不对的

function performVerification() {
  const verifications = []; // 存储核销结果

  let remainingAmount = 0; // 剩余待核销金额

  // 按日期排序单据数组
  const sortedInvoices = invoices.sort((a, b) => a.date - b.date);
  const sortedJournals = journals.sort((a, b) => a.date - b.date);

  // 遍历单据数组进行核销
  for (const invoice of sortedInvoices) {
    let verifiedAmount = 0; // 当前单据已核销金额
    const verifiedJournals = []; // 当前单据核销的 journal 数组

    // 倒序遍历 journals 进行核销
    for (let i = sortedJournals.length - 1; i >= 0; i--) {
      const journal = sortedJournals[i];

      if (journal.amount < 0 && Math.abs(journal.amount) <= remainingAmount) {
        // 负数调整余额
        verifiedAmount += Math.abs(journal.amount);
        verifiedJournals.push({ id: journal.id, amount: Math.abs(journal.amount) });
        remainingAmount -= Math.abs(journal.amount);
        sortedJournals.splice(i, 1); // 从数组中移除已核销的 journal
      }

      // 如果当前单据已核销金额达到单据金额,则退出循环
      if (verifiedAmount >= invoice.amount) {
        break;
      }
    }

    // 添加核销结果到数组
    verifications.push({ invoice, verifiedAmount, verifiedJournals });

    // 如果已核销所有 journals ,则跳出循环
    if (sortedJournals.length === 0) {
      break;
    }
  }

  return verifications;
}
3138 次点击
所在节点   程序员  程序员
34 条回复
ivvei
ivvei
302 天前
问题描述得乱七八槽。transaction 只能叫发生,不能叫使用。而且你这 JNL005 之后不是还有 130 吗,怎么就 100 了?要关联 invoice 为何不直接在 transaction 里面带上 invoice 的编号?正常人都这么做的啊。你根据金额倒推,遇到同样金额的 invoice 不是懵逼了?
nzynzynzy
nzynzynzy
302 天前
@ivvei #1 叫发生没什么问题。这个是我抽象的结果,就是打个比方。用户希望通过一笔消费能看到这笔能对应上几笔发生。

这个 transaction 和 invoice 不是一一对应的,你可以想象为客户既可以消费也可以存取,存取并不是指定消费的。

我疏漏了一些描述:
- 就是一顿存取之后,只有一笔 invoice ,不会是多笔。
- 没用到的单子就会自动结转等待下一次使用。

这个数据,算法希望对应的结果应该是

JNL001 ,金额 100 ,使用 100 ,余额 0
JNL002 ,金额-100 ,使用-100 ,余额 0
JNL003 ,金额 100 ,使用 100 ,余额 0
JNL004 ,金额-100 ,使用-100 ,余额 0
JNL005 ,金额 130 ,使用 100 ,余额 30
zizon
302 天前
看着像币圈侧链/次级账户交易合并回主链的操作...

你合并 transaction 的时候检查更新余额看有没超不就行了?
NoOneNoBody
302 天前
我理解就叫结算啊,或者集中结算
JNL*是可实施或不实施?只是有序订单?赊账?

你这问题问得不清楚,太难明了,不怪 GPT 答错
RecursiveG
302 天前
建议楼主先去把自己的思路捋顺了再问。
Chad0000
302 天前
你把 transaction 理解为记账就理解了。transaction 加起来就是余额,余额你可以缓存/冗余起来。
rekulas
302 天前
有点像数字货币的 utxo 的概念

不过这个逻辑很简单,不清楚你有什么疑惑
wingor2015
302 天前
额, 看完之后感觉没完全看懂,建议楼主详细描述一下,比如举个例子账号从初始状态开始开始,然后每执行一次行为,invoices 和 transaction 各为什么状态,
nzynzynzy
302 天前
感谢各位耐心阅读。

比如以下事件按顺序发生

刚开始余额是 0
==============
操作余额#JNL001 ,金额 100
操作余额#JNL002 ,金额-100
操作余额#JNL003 ,金额 100
操作余额#JNL004 ,金额-100
操作余额#JNL005 ,金额 130
===============
截至目前账户余额是 130 元,此时发生了一单交易 INV001 ,价值 100 元
这一单需说明“这 100 块是从哪些余额操作中来的”,这时候目前面临无数种算法列举 3 种可能性

可能性 A:先进先出,够用就停
INV001 的 100 元来自 JNL001

可能性 B:先进先出,尽可能地记录
INV001 的 100 元来自 JNL001-JNL005 的叠加
JNL001 ,金额 100 ,核销 100 ,余额 0
JNL002 ,金额-100 ,核销-100 ,余额 0
JNL003 ,金额 100 ,核销 100 ,余额 0
JNL004 ,金额-100 ,核销-100 ,余额 0
JNL005 ,金额 130 ,核销 100 ,余额 30

可能性 C:
选一个够用的最大的,即 JNL005 去核销,其他无视

这个例子中追求的算法是以上的 B ,即尽可能记录 INV001 的来源
nzynzynzy
302 天前
确实这个东西昨天让我有点心烦意乱的,我打了个草稿放在 V2 然后今天发了,可能真的懂得只有我。

=============================
然后还有一个规则就是“贪婪”先进先出地消耗 JNL ,耗完就停止,举个例子

刚开始余额是 0
----
操作余额#JNL001 ,金额 100
操作余额#JNL002 ,金额-100
操作余额#JNL003 ,金额 100
操作余额#JNL004 ,金额-100
操作余额#JNL005 ,金额 130
销售 INV001 ,金额 100=>操作核销掉 JNL001-004 全部余额,因为他们叠加起来余额是 0 ,然后核销掉 JNL005 的 100 元,剩 30 元
-----
操作余额#JNL006 ,金额 100
这时候发生交易 INV002 ,金额 130 ,此时需要记录 INV002 的来源:
JNL005 ,金额 30 ,核销 30 ,余额 0 ,
JNL006 ,金额 100 ,核销 100 ,余额 0

情况大概就是这么个情况,看看还有什么没讲明白

=============================

@zizon #3
@NoOneNoBody #4
@RecursiveG #5
@Chad0000 #6
@rekulas #7
@wingor2015 #8

烦请各位如果有思路分享一下,有兴趣也可以插眼回来看
Sawyerhou
302 天前
我看了 15min 都没看明白,这是行情数据吗?

inv 代表 K 线的成交额
trans 代表每笔订单的成交额
每根 K 线成交额由数量不确定的订单成交额加总得到

现在想做数据对齐
找出指定 K 线由哪些订单合成的?

如果是想问以上的话
这个对齐几乎不可能
NessajCN
302 天前
我也完全没看懂,你说的是中文吗
JNL 是啥?为啥要纠结余额从哪里扣,
是不是你有跟这个楼主 https://v2ex.com/t/1033839 类似的需求,也就是余额会过期?
NoOneNoBody
302 天前
pandas 有个函数叫 cumsum ,就是对一个数列,每个元素,向首个元素方向所有包含的元素求和,得出一个新数列
例如 1,2,3,4,5 结果为 1,3,6,10,15

现有一个数 5 ,满足 x>=5 ,求 x 的位置
对应 A ,从首位开始,6 为第一个满足的,位置为 3 ,有效的就是前三条
对应 B ,从末尾开始,15 为第一个满足的,位置为 5 ,则全部有效
C 就是 1,2,3,4,5 中选一个,与 cumsum 无关,位置为 5 ,有效的为仅第五条
NoOneNoBody
302 天前
@all
OP 实际需要求的是一个条件 mask ,覆盖若干条 JNL 构成 True
如果上述 JNL 有序,可按#13 方案;如果无序,那就复杂了,需要排列组合
Chad0000
302 天前
@NessajCN
op 应该在做 invoice 结算系统。invoice 提前发给客户,然后客户在某个时间早期转账支付了就行。那个 transaction 就是支付记录。国外很多系统都是这么运行。


如果是这样,客户打款直接记录进账 transaction ,然后触发余额检查,如果够就产生一笔扣 invoice 的 transaction ,如果不够可以有多少扣多少。如果客户多转钱了,还是扣 invoice 那么多,但会导致所有 transaction 加起来为正数,也就是用户的账户处于 credit 状态(有余额)。

不知我怎么说 op 明白了没有?还是你的系统不是这样的?
@nzynzynzy
@nzynzynzy
@nzynzynzy
nzynzynzy
302 天前
@Chad0000 #15 原理有点类似,或者你可以理解为预存/预支 + 购买,而且这个购买的时候要找到对应的预存/预支记录,而且预存/预支是有可能像我描述的可能性 A 这样被第一个记录拦截住,而后面其实还有预支。理想情况是能读取到第五张单据

@NoOneNoBody #13 谢谢,cumsum 确实是这个问题目前最贴切的描述,就是贪婪版的 cumsum ,我去朝着这个方向再探一探

也欢迎其他朋友继续看看!
nzynzynzy
302 天前
@NessajCN #12 多谢这个还真的很类似!我感觉余额会过期是一种类似的情况,都是有序的先进先出地消耗,我继续去读一下
NessajCN
302 天前
@nzynzynzy 那你就也参考区块链只记录交易信息呗。
nzynzynzy
302 天前
@Chad0000 #15 而且你的例子中假设是 invoice 是可以 pending 状态先进来等待付款,和我的情况有差别,我的更像是进来时候实时判断钱够不够,不够这笔就废止了。我觉得老哥说的 cumsum 是有道理的,而且是正负都存在的 cumsum 。

这个就变成了:cumsum 的数组中找值大于 invoice 的情况,其中最靠队尾的、值大于等于 invoice 的一个或者一群值中最靠前的一个。累死我了这个描述。。。
ddzzhen
302 天前
既然有冲有送,应该把金额按来源分类打标签,金额与 transaction 独立,这样遇到 invoices 时候才容易明确来源和条件判断

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://tanronggui.xyz/t/1039989

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX