对于做移动端开发而言,算法,数据结构似乎只用于面试装B用,数据的查找,排序,增加,删除,如果一个for循环不能解决,那么再嵌套一层循环,如果再不行,那就再嵌套...。前段时间看到一篇文章说,面试招的是高级工程师,做的事情是一个初级程序员的事,似乎有点道理,但也不完全对,有时候还真的需要一些思路解决特殊的问题。
在做商城sku库存判断开发的过程中,就发现了一个使用很多层循环不能解决的问题,需要有特殊的思路。
1.想要实现的效果
选中某个元素的时候,如果其他元素和当前选择的元素组合成的sku没有库存,则把其他元素置灰,不可点击。
2.关键字定义
- 商品SKU,SKU=Stock Keeping Unit(库存量单位),就是用于描述库存量的最小单元,比如一个商品有红色,蓝色,那么红色为一种SKU,蓝色为另外一种SKU。
- 规格 商品的属性,比如鞋子有颜色属性,尺寸属性
- 元素 商品的属性描述,比如鞋子的颜色有白色,蓝色,尺寸有40码,41码
具体情况是这样的, 在购物袋中(商品详情页中),用户在点击购买或者加入购物车时需要选择所有的规则,但是不是所有规则下的属性都是可选的,比如上面的图片如果选择了黑色,二段,那么鞋码的36,41是不可选的,因为没有库存,如果按照上面的图中的所有可以产生的sku数总共有多少种呢
黑色 白色
三段 二段
40 36 41
黑色 三段 50
黑色 三段 36
黑色 三段 41
黑色 二段 40
黑色 二段 36
黑色 二段 41
白色 三段 50
白色 三段 36
白色 三段 41
白色 二段 40
白色 二段 36
白色 二段 41
总共有2x2x3 = 12 种
3.服务器返回json格式
skus是所有属性元素拼接可能产生的sku个数,包括有库存和没有库存的sku,单个sku使用sku对象中的store参数判断是否有库存,sku对象下的select_spec_id是specs对象对应的spec_value_id的多个元素使用‘_’拼接组合,比如黑色,二段,那么拼接的select_spec_id组合为3_5。
4.常规解决思路
初始化时当我们想要点击黑色,判断三段和二段可不可以选,如果判断三段可以选了,又要鞋码40,36,41码是否可以选,回头还别忘了白色是否可选。貌似需要使用三层循环判断,结果应该可以判断出来,但是问题是如果鞋子又增加了一个属性,那么理论上又得增加一层循环,常规思路好像很不好解决,逻辑难厘清,较劲脑汁,想不出什么好办法,但是又觉得应该有办法,毕竟这个问题做过sku库存判断的前段同学都实现过,然后在网上搜了一篇简书的文章,得到了提示https://www.jianshu.com/p/7a17b4179225
5.合理的解决思路
初始化时当我们想要点击黑色,判断三段是否可以点击,那么只需要判断黑色_三段
是否可以点击,不需要做其他判断,如果已经选则了黑色_三段,判断40是否可以点击,那么只需要判断黑色_三段_40
是否可以点击,不需要判断其他的元素。
思路似乎就清晰了许多
结论是,当我们想要判断某个元素是否可以点击,那么只需判断当前已经选择的元素和想要选择的元素拼成的一个路径是否存在有库存的sku的子集中。
现在服务端返回的12个sku中,列举一下元素spec_value_id拼接和是否有库存
1 黑色 三段 40 3_16_19有库存
2 黑色 三段 36 3_6_15 有库存
3 黑色 三段 41 3_6_20无库存
4 黑色 二段 40 3_5_19有库存
5 黑色 二段 36 3_5_15无库存
6 黑色 二段 41 3_5_20无库存
7 白色 三段 40 2_6_19无库存
8 白色 三段 36 2_6_15有库存
9 白色 三段 41 2_6_20无库存
10 白色 二段 40 2_5_19无库存
11 白色 二段 36 2_5_15无库存
12 白色 二段 41 2_5_20有库存
第一步,过滤有库存的sku
/**
计算所有包含库存的sku
@param skus 服务器返回的所有的sku
@return 有库存的sku
*/
+ (NSMutableArray *)enoughStoreSkusWithSkus:(NSArray *)skus {
NSMutableArray *enoughStoreSkus = [NSMutableArray array];
for (NSInteger i = 0; i < skus.count; i ++) {
TRCMallShopCartSkuModel *skuModel = skus[i];
if (skuModel.store > 0) {
[enoughStoreSkus addObject:skuModel];
}
}
return enoughStoreSkus;
}
结果为:
1 黑色 三段 40 3_16_19有库存
2 黑色 三段 36 3_6_15 有库存
3 黑色 二段 40 3_5_19有库存
4 白色 三段 36 2_6_15有库存
5 白色 二段 41 2_5_20有库存
第二步,使用递归找到所有有库存sku的幂集
/**
获取所有包含库存的幂集集合
@param skus 包含库存的sku
@return 去掉重复的子集的幂集集合
*/
+ (NSMutableArray *)powerSetWithSkus:(NSMutableArray *)skus {
NSMutableArray *enoughStoreSkus = [self enoughStoreSkusWithSkus:skus];
// 包含所有的
NSMutableArray *allPowerSet = [NSMutableArray array];
for (NSInteger i = 0; i < enoughStoreSkus.count; i ++) {
TRCMallShopCartSkuModel *skuModel = enoughStoreSkus[i];
NSArray *select_spec_ids = [skuModel.select_spec_id componentsSeparatedByString:@"_"];
[self powersetArray:[NSMutableArray arrayWithArray:select_spec_ids] index:0 set:@"" powerSet:allPowerSet ];
}
return allPowerSet;
}
// 递归求单个sku的幂集
+ (void)powersetArray:(NSMutableArray *)array index:(NSInteger)index set:(NSString *)set powerSet:(NSMutableArray *)powerSet {
NSString *tempString = set;
if (index >= array.count) {
NSLog(@"set = %@ ==%p",set,set);
if (set.length > 0 && ![powerSet containsObject:set]) {
[powerSet addObject:set];
}
} else {
[self powersetArray:array index:index + 1 set:tempString powerSet:powerSet singleSkuPowset:singleSkuPowset]; // 每次需要set完整的版本
// 每次将temp数组的部分元素加到temp中
if (tempString.length > 0) {
tempString = [tempString stringByAppendingString:[NSString stringWithFormat:@",%@",array[index]]];
} else {
tempString = [tempString stringByAppendingString:[NSString stringWithFormat:@"%@",array[index]]];
}
[self powersetArray:array index:index + 1 set:tempString powerSet:powerSet singleSkuPowset:singleSkuPowset]; // temp成为新的set
// 如果powerset的是时候一直i+1就等于把set数组一直置空
}
}
打印结果:
Printing description of allPowerSet:
<__NSArrayM 0x60000045d340>(
19,
6,
6,19,
3,
3,19,
3,6,
3,6,19,
15,
6,15,
3,15,
3,6,15,
5,
5,19,
3,5,
3,5,19,
2,
2,15,
2,6,
2,6,15,
20,
5,20,
2,20,
2,5,
2,5,20
)
第三步,循环每个元素,判断该元素是否可以点击
// 判断是否有库存
+ (void)haveStoreWithSpecValues:(NSMutableArray *)specValues powerSet:(NSMutableArray *)powerSet {
/*如何确定 红 可选? 只需要确定 红-B 可选
如何确定 中 可选? 需要确定 白-中-B 可选
如何确定 2G 可选? 需要确定 白-B-2G 可选*/
// 已经有选中元素的属性行
NSMutableArray *selectedSpecValues = [NSMutableArray array];
// 没有选择元素的属性行
NSMutableArray *withOutSelectedValus = [NSMutableArray array];
for (NSInteger i = 0; i < specValues.count; i ++) {
TRCMallShopCartAttributeModel *attributeModel = specValues[i];
if (attributeModel.selected) {
[selectedSpecValues addObject:attributeModel];
} else {
[withOutSelectedValus addObject:attributeModel];
}
}
for (NSInteger i = 0; i < specValues.count; i ++) {
TRCMallShopCartAttributeModel *attributeModel = specValues[i];
NSArray *selections = [self selectionDescendingCurrentSpecPropertyModel:attributeModel selectedSpecValues:selectedSpecValues];
for (NSInteger j = 0; j < attributeModel.values.count; j ++) {
TRCMallShopCartElementModel *elementModel = attributeModel.values[j];
// 如果是处于未选中状态,判断当前元素是否可选
if (elementModel.status != 1) {
NSString *spec_ids = [self getSpec_id_pathWithSpecValues:selections elementModel:elementModel];
if ([powerSet containsObject:spec_ids]) {
elementModel.status = 0;
} else {
elementModel.status = 2;
}
} else {
// 已经选中的情况下,肯定是可选的
elementModel.status = 1;
}
}
}
}
/**
* 获取用户已经选中的路径链接3,9
*/
+ (NSString *)getSelectedElementIdPathWithSpecValues:(NSArray *)specValues {
NSString *spec_ids = @"";
for (NSInteger i = 0; i < specValues.count; i ++) {
TRCMallShopCartAttributeModel *attributeModel = specValues[i];
if (attributeModel.selected) {
for (NSInteger j = 0; j < attributeModel.values.count; j ++) {
TRCMallShopCartElementModel *model = attributeModel.values[j];
// 已经选中了
if (model.status == 1) {
NSString *spec_value_id = [NSString stringWithFormat:@"%ld",model.spec_value_id];
if (spec_ids.length > 0) {
spec_ids = [spec_ids stringByAppendingString:[NSString stringWithFormat:@",%@",spec_value_id]];
} else {
spec_ids = [spec_ids stringByAppendingString:spec_value_id];
}
}
}
}
}
return spec_ids;
}
/**
* 获取用户已经选中的属性,颜色,白色,三段,段位:二段
*/
+ (NSString *)getSelectedElementPropertysWithSpecValues:(NSArray *)specValues {
NSString *propertys = @"";
for (NSInteger i = 0; i < specValues.count; i ++) {
TRCMallShopCartAttributeModel *attributeModel = specValues[i];
if (attributeModel.selected) {
if (propertys.length == 0) {
propertys = @"已选:";
}
propertys = [propertys stringByAppendingString:[NSString stringWithFormat:@"%@:",attributeModel.name]];
for (NSInteger j = 0; j < attributeModel.values.count; j ++) {
TRCMallShopCartElementModel *model = attributeModel.values[j];
// 已经选中了
if (model.status == 1) {
NSString *text = model.text;
propertys = [propertys stringByAppendingString:text];
propertys = [propertys stringByAppendingString:@","];
}
}
}
}
if ([propertys hasSuffix:@","]) {
propertys = [propertys substringToIndex:propertys.length - 1];
}
return propertys;
}
// 根据当前的属性元素,和已经选择其他属性元素,拼接成最短路径
+ (NSString *)getSpec_id_pathWithSpecValues:(NSArray *)specValues elementModel:(TRCMallShopCartElementModel *)elementModel {
NSString *spec_ids = @"";
for (NSInteger i = 0; i < specValues.count; i ++) {
TRCMallShopCartAttributeModel *attributeModel = specValues[i];
for (NSInteger j = 0; j < attributeModel.values.count; j ++) {
TRCMallShopCartElementModel *model = attributeModel.values[j];
// 没有选择,并且,跳出下一个循环
if (model.status !=1 && (elementModel.spec_value_id != model.spec_value_id)) {
continue;
}
if (elementModel.spec_value_id == model.spec_value_id) {
NSString *spec_value_id = [NSString stringWithFormat:@"%ld",model.spec_value_id];
if (spec_ids.length > 0) {
spec_ids = [spec_ids stringByAppendingString:[NSString stringWithFormat:@",%@",spec_value_id]];
} else {
spec_ids = [spec_ids stringByAppendingString:spec_value_id];
}
} else {
// 已经选中
if (model.status == 1 && ![model.spec_id isEqualToString:elementModel.spec_id]) {
NSString *spec_value_id = [NSString stringWithFormat:@"%ld",model.spec_value_id];
if (spec_ids.length > 0) {
spec_ids = [spec_ids stringByAppendingString:[NSString stringWithFormat:@",%@",spec_value_id]];
} else {
spec_ids = [spec_ids stringByAppendingString:spec_value_id];
}
}
}
}
}
return spec_ids;
}
// 把选择好的元素和当前需要判断的属性行生成一个排序数组
+ (NSArray *)selectionDescendingCurrentSpecPropertyModel:(TRCMallShopCartAttributeModel *)specPropertyModel selectedSpecValues:(NSMutableArray *)selectedSpecValues {
NSMutableArray *combianSpecValues = [NSMutableArray arrayWithArray:selectedSpecValues];
if (![combianSpecValues containsObject:specPropertyModel]) {
[combianSpecValues addObject:specPropertyModel];
}
return [combianSpecValues sortedArrayUsingComparator:^NSComparisonResult(TRCMallShopCartAttributeModel *obj1, TRCMallShopCartAttributeModel *obj2) {
//因为不满足sortedArrayUsingComparator方法的默认排序顺序,则需要交换
if (obj1.row > obj2.row)
return NSOrderedDescending;
//因为满足sortedArrayUsingComparator方法的默认排序顺序,则不需要交换
if (obj1.row < obj2.row)
return NSOrderedAscending;
return NSOrderedDescending;
}];
}