iOS SKU规格组合算法

写在前面

本篇文章主要是讲 SKU 商品规格组合的 问题、解决思路及算法优化。 最后 将提供一个SKU算法的通配方案 - SKUDataFiltergit

本篇文章分析较为详细,针对于对SKU问题不甚了解的童鞋。github

不想听我瞎BB的,这边是干货地址算法

文章最后是 使用说明数组

更新日志

2019.04.12
  • 加入是否默认选中第一组SKU的控制
    此处是选中第一组SKU, 并不必定包含第一个属性bash

    // needDefaultValue _filter.needDefaultValue = YES; [self.collectionView reloadData]; //更新UI显示 [self action_complete:nil]; //更新结果查询 
2018.07.11 ~ cocoapods version 1.0.1
  • 支持cocopods 导入服务器

    pod 'SKUDataFilter' 
  • 升级数据防崩溃过滤,即便sku-condition彻底对不上号,也不会闪退了。(针对某些极端测试人员)数据结构

2018.06.21 -
  • 最近收到不少由于部分sku信息不完整,致使崩溃的反馈。因此新增了sku-condition的检测,过滤并提示了不完整的condition。已更新
2017.12.16 -
  • 因为以前的疏忽,在更新算法的时候,漏了一个点,致使一个很是严重的bug,感谢简书网友@毕小强指出,已更新

关于SKU

维基百科:
最小库存管理单元(Stock Keeping Unit, SKU)是一个会计学名词,定义为库存管理中的最小可用单元。
最小库存管理单元就是“单品” 最小库存单元是指包含特定的天然属性与社会属性的商品种类,在零售连锁门店管理中一般称为“单品”。对于一种商品而言,当他的品牌、型号、配置、花色、容量、生产日期、保质期、用途、价格、产地等属性与其余商品存在不一样时,就是一个不一样的最小存货单元。测试

通俗来说,一个SKU 就是商品在规格上的一种组合,好比说,一件衣服 有红色 M号的 也有蓝色 L号的 ,不一样的组合就是不一样的SKU优化

下图DEMO演示效果图ui

 
003.gif

问题与思路

咱们所说的SKU 组合算法,就是对商品规格组合的一种筛选和过滤。即 根据已选中的一个或多个属性过滤出 剩余属性的 可选性,以及选完全部属性以后对应的 结果(库存、价格等)

这里的问题就有两个

  • 根据已选中的一个或多个属性过滤出 剩余属性的 可选性
  • 根据选中的全部属性查询对应的结果(库存、价格等)

第二个问题较简单,只须要遍历一遍SKU,找到对应的结果便可,重点在第一个

简单举个例子

**商品规格  :
款式 : F    M
颜色 : R    G    B
尺寸 : L    X    S    

SKU: M,G,X - 66元,10件 F,G,S - 88元,12件 F,R,X - 99元,15件 

咱们把 一组知足条件的属性 叫作条件式 ,那么这里就有三个条件式
用个图来表示他们之间的关系 (红线为F-G-S)


 
condition.png

这里的属性的状态只有两种 可选和不可选。(已选是属于可选)
那么B、L自始至终就为不可选状态

当咱们选中某个属性时,好比G -- 那么对应的
可选择属性即为 :
G自己;
兄弟节点(同类可选属性可切换) R(B已淘汰);
对应条件式中的其余节点 M、X、F、S;

乍一看,除了条件式外的均可选,这是由于故意弄成这个条件式以便于后面的讲解。

实际上咱们是经过遍历他的兄弟属性, 遍历他所在的条件式,拿出对应的属性。在多个条件式中会有重复的属性,为了过滤重复的值能够利用集合来添加保存的(NSSet,NSMutableSet)

当咱们选中多个属性, 好比 F、 R, 因为已选属性之间的相互牵制、这里状况就要复杂的多了

根据上面的分析,咱们一般都会想到,遍历各自的兄弟节点,以及条件式,最后各自所取的属性值 去一个交集

(ps1:有的小伙伴可能看不懂,最终可选属性,最傻的办法就是你把可选属性带入条件式里面知足条件便可,兄弟属性在替换以后知足条件式也是可选,好比选中R、他会替换原先的G,也知足已选中属性X,即为可选)

(ps2:排列顺序为:自己、可选兄弟属性、条件式)

F-可选:F、M、R、X、G、S
R-可选:R、G、F、X
交集:F、R、G、X
手动验证,彻底OK

But这种方法有漏洞

选择 G、X
G-可选:G、R、F、S、M、X
X-可选:X、S、F、R、M、G
交集:G、X、R、S、R、F、M
手动验证,错误 - F应该为不可选

手动缘由:F既在G的条件式F-G-S中,又在X的条件式F-R-X中,可是却不一样时知足G、X

这里首先要搞明白 问题绝对不会出如今已选属性的兄弟属性上,由于兄弟节点,在任何一个兄弟属性存在的条件式中 其余兄弟属性都不会出现,有F的条件式就不会有M。因此问题仍是在条件式中。当有多个属性被选中时,判断一个非可选属性的兄弟属性是否可选,必需要知足全部可选属性的条件式。那么总体结论即断定某个属性的可选性:该属性要么同时知足全部已选属性的条件式,要么和已选中的某个属性是兄弟属性

算法优化

基于上面的思路,再来讲一下算法的优化

  • 下降已选属性的遍历

将上诉理论应用到实际代码中,通常是这样的,再求可选属性集合时:每次一个新的属性操做(选中、取消、切换),都会根据上诉结论 分别为每个已选属性的筛选出对应的 可选属性,而后在作交集。
这样的话,每次一个新的属性操做,均可能会把的已选属性重复查询一遍。

优化方案构想-每次新的属性操做,只筛选当前属性的可选属性,而后在已选属性的基础上进行增删操做。
看到构想,感受也不是很难,下面是实际状况——
实际操做分为三种状况:

一、选中新属性 二、切换兄弟属性 三、取消已选中属性

第一种状况,和咱们前面的思路吻合,只须要将筛选出新属性对应的可选属性集合,而后与当前的 可选集合 求得 交集便可

后面两种状况,都含有一个取消操做(切换兄弟属性、须要取消上一个兄弟属性),取消操做,意味着,你要把该属性所过滤掉的可选属性,还回来,也就是恢复取交集前的 原集合。那问题的关键即为如何找到这些被该属性过滤掉的可选属性集合或是直接恢复原集合

恢复:能够经过找到全部的原可选集合,并记录下来,而后根据取消属性的匹配原集合恢复(这里不能单纯的记录选中操做的可选集合,由于取消的顺序不同)
查找过滤掉的可选集合:操做等同于从新计算可选集合

以上两种方案都不可行。这里就很少作赘述,实际操做起来,遍历查询的次数更多了,不只达不到优化的效果,还增长了算法的复杂程度(这里若是小伙伴有更好的想法,欢迎讨论)。那么最终结论是:在对属性有新操做时,只有新增属性能够基于当前可选属性集合过滤,其余状况须要从新计算

  • 优化条件式

若是说上一个优化方案只是在瞎扯蛋的话,那这里就是整个优化所在的关键了,同时也是SKUDataFilter的核心

认真看了整篇文章就会发现,整个算法思路的核心,在于条件式,不论是查询结果,仍是查询可选属性集合,实际上都是依赖于条件式的,咱们在查询某个属性时候可选,其实是要遍历他所在的条件式列表,这个列表又要求咱们去遍历全部的条件式,判断这个属性是否在条件式中,拿到列表以后,获取非兄弟属性又要遍历这个属性,是否同时知足全部已选属性的条件式。那么咱们整个算法循环次数最多的地方即是判断某个属性是否存在于某个条件式中

**商品规格  :
为规格属性加一个坐标,记录他们的位置

    0 1 2 0 F M 1 R G B 2 L X S SKU: 用下标表示条件式 M,G,X - 66元,10件 --- (1,1,1) F,G,S - 88元,12件 --- (0,1,2) F,R,X - 99元,15件 --- (0,0,1) 

在上一个例子中,为每一个属性加一个坐标,如L表示为(0, 2)
条件式中用坐标的某一部分表示

这样一来

判断某个属性是否存在于某个条件式:
正常的操做是遍历条件式中的属性,分别和该属性作判断(containsObject方法 本质上也是在作遍历) 。

而这里只须要一次判断就够了 ,设该属性的坐标为(x,y)判断条件式里的第y个值是否等于x便可(这里的判断取决于条件式存入的是x、仍是y)

如 判断M(1,0) 是否在F-G-S(0,1,2)条件式中 即条件式的第0个值是否等于1就OK了(程序猿都是从0开始数的)

好比说,总共有5个条件式,每一个条件式中有5个属性,你要找出某个知足某个属性的全部条件式
若是你不去中断遍历,就要判断25次,这种方式只须要断定5次就够了,因此它的优化性其实是很是高的。

实际上,真正神奇之处就在于这样的下标条件式能够清楚的 知道他所拥有的 任何一个属性 的坐标,进而知道属性的值

解决方案-SKUDataFilter

SKUDataFilter 正是基于以上分析和算法优化实现的,使用NSIndexPath记录每一个属性的坐标,更加直观。表示为 第section 种属性类下面的 第item个 属性 (从0计数)
条件式下标(conditionIndexs)中记录的属性indexPath的 item

// 判断属性是否存在于条件式中 conditionIndexs[indexPath.section] == indexPath.row 

数据通配
conditionIndexs 和indexPath的结合 为SKUDataFilter 不只算法上取得了优点,同时也在 数据通配 上起了莫大的做用

不一样的后台,不一样的需求,返回的数据结构都不同。

然而SKUDataFilter真正关心的是属性的坐标,而不是属性自己的的值,那么无论你从后台获取的数据结构是怎样的,也无论你是如何解析的。固然,你也不须要去关心坐标和条件式下标等等乱七八糟的。你须要作的只是把对应的数据放入对应的代理方法里面去就好了,无论数据是model,属性ID、字典仍是其余的。

使用说明

使用能够参考SKUDataFilterDemo

SKUDataFilter最终直接反映的是属性的indexPath, 若是你的属性在UI显示上使用UICollectionView实现,那么indexPath是一一对应的,若是用的循环建立,找到对应的行和列便可。

一、初始化Filter 并设置代理

- (instancetype)initWithDataSource:(id<ORSKUDataFilterDataSource>)dataSource; //当数据更新的时候 从新加载数据 - (void)reloadData; 

二、经过代理方法 ,将数据传给Filter

如下方法都必需实现,分别告诉Filter,属性种类个数、每一个种类的全部属性(数组),条件式个数、每一个条件式包含的全部属性、以及每一个条件式对应的结果(能够参考本文案例)

//属性种类个数 - (NSInteger)numberOfSectionsForPropertiesInFilter:(ORSKUDataFilter *)filter; /* * 每一个种类全部的的属性值 * 这里不关心具体的值,能够是属性ID, 属性名,字典、model */ - (NSArray *)filter:(ORSKUDataFilter *)filter propertiesInSection:(NSInteger)section; //知足条件 的 个数 - (NSInteger)numberOfConditionsInFilter:(ORSKUDataFilter *)filter; /* * 对应的条件式 * 这里条件式的属性值,须要和filter:propertiesInSection里面的数据 类型保持一致 */ - (NSArray *)filter:(ORSKUDataFilter *)filter conditionForRow:(NSInteger)row; //条件式 对应的 结果数据(库存、价格等) - (id)filter:(ORSKUDataFilter *)filter resultOfConditionForRow:(NSInteger)row; 

三、点击某个属性的时候 把对应属性的indexPath传给Filter

- (void)didSelectedPropertyWithIndexPath:(NSIndexPath *)indexPath; 

四、查询结果(与代理方法resultOfConditionForRow:对应)-条件不完整会返回nil

@property (nonatomic, strong, readonly) id currentResult; 

五、可选属性集合列表、已选属性坐标列表

//当前 选中的属性indexPath @property (nonatomic, strong, readonly) NSArray <NSIndexPath *> *selectedIndexPaths; //当前 可选的属性indexPath @property (nonatomic, strong, readonly) NSArray <NSIndexPath *> *availableIndexPaths; 

使用注意
一、虽然SKUDataFilter不关心具体的值,可是条件式本质是由属性组成,故代理方法filter:propertiesInSection:和方法filter:conditionForRow:数据类型应该保持一致
二、由于SKUDataFilter关心的是属性的坐标,那么在代理方法传值的时候,代理方法filter:propertiesInSection:和方法filter:conditionForRow:各自的数据顺序要保持一致 而且两个方法的数据也要对应
如本文案例条件式是从上往下(M,G,X),传过去的 属性值 也都是从左到右(F、M)-各自保持一致。 同时
条件式为从上到下,那么propertiesInSection: 也应该是从上到下,先是(F、M)最后是(L、X、S)
实际项目中,这两种状况发生的几率都很是小,由于 第一数据统一返回统一解析,格式99%都是同样。第二数据是从服务器返回,服务器的数据要进行筛选和过滤,顺序也不能弄错,一旦错误,首先服务器就会出问题

 

做者:OrangeAL连接:https://www.jianshu.com/p/295737e2ac77来源:简书简书著做权归做者全部,任何形式的转载都请联系做者得到受权并注明出处。

相关文章
相关标签/搜索