sku多规格属性组合库存判断 - iOS

  对于做移动端开发而言,算法,数据结构似乎只用于面试装B用,数据的查找,排序,增加,删除,如果一个for循环不能解决,那么再嵌套一层循环,如果再不行,那就再嵌套...。前段时间看到一篇文章说,面试招的是高级工程师,做的事情是一个初级程序员的事,似乎有点道理,但也不完全对,有时候还真的需要一些思路解决特殊的问题。
在做商城sku库存判断开发的过程中,就发现了一个使用很多层循环不能解决的问题,需要有特殊的思路。

1.想要实现的效果

选中某个元素的时候,如果其他元素和当前选择的元素组合成的sku没有库存,则把其他元素置灰,不可点击。


图1.png
2.关键字定义
  1. 商品SKU,SKU=Stock Keeping Unit(库存量单位),就是用于描述库存量的最小单元,比如一个商品有红色,蓝色,那么红色为一种SKU,蓝色为另外一种SKU。
  2. 规格 商品的属性,比如鞋子有颜色属性,尺寸属性
  3. 元素 商品的属性描述,比如鞋子的颜色有白色,蓝色,尺寸有40码,41码

具体情况是这样的, 在购物袋中(商品详情页中),用户在点击购买或者加入购物车时需要选择所有的规则,但是不是所有规则下的属性都是可选的,比如上面的图片如果选择了黑色,二段,那么鞋码的36,41是不可选的,因为没有库存,如果按照上面的图中的所有可以产生的sku数总共有多少种呢

黑色        白色
三段        二段
40       36        41

黑色 三段 50
黑色 三段 36
黑色 三段 41
黑色 二段 40
黑色 二段 36
黑色 二段 41
白色 三段 50
白色 三段 36
白色 三段 41
白色 二段 40
白色 二段 36
白色 二段 41
总共有2x2x3 = 12 种

3.服务器返回json格式
1531624561062.jpg
1531624561062.jpg

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;
    }];    
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容