C language programming-points exchange

Preface

Hi everyone, it's me again.

Surely we have to redeem points in time, such as shopping malls shopping points , where the operator calls integration , or is it the school's scorecard , points course of study , etc., then, assuming that the goods / gifts is not limited, how can we do Is it possible to spend as many points as possible?

In fact, this is also one of the final homework assigned by the teacher. I searched for related keywords on the Internet, but I didn't seem to see a similar article, so I thought about it.

Now put up the teacher’s homework requirements:

  1. Users have consumption points m on the shopping website, and recently the website launched a point exchange activity. The number of types of redemption products is uncertain . For example, "backpacks" are off the shelves, but "soap" and "toothbrushes" have been added.
  2. There is no limit to the number of redemption of each product , such as: redeem 10, 12, or 100 "new towels".
  3. After the user points are exchanged, there will almost always be surplus. Find out the top 5 combinations with the least points loss after the exchange.

Specific implementation process

Ideas

I'm not very smart. I vaguely feel that this is a bit similar to "change exchange", but I'm not sure.

Well, some people say that dynamic programming can be used for this ...Sorry, I don't know this (I don't understand it yet); only enumeration.

Well, then the enumeration method.

Of course, I also know that enumeration is very time-consuming, and may not even wait until the end , but fortunately, we only require a few feasible solutions. Practice shows that generally the required results can be calculated quickly.

Speaking of enumeration, it must use loops, and if there are multiple commodities, then multiple loops must be used; and there is a problem here is that the number of commodities is uncertain, so how can we achieve the multiplicity of indefinite quantities? What about the loop? I decided to use a recursive method to achieve it.

At the same time, in order to avoid timeout, I set the program to find 5 " optimal solutions " —that is , the result with a remaining integral of 0 —and then terminate the calculation and output the result. However, in the process of communicating with my classmates, I found that in some special cases, it is easy to time out under simple enumeration;

a. When there are remaining points anyway , and the total amount of points is very large (that is, the value of the goods is all a multiple of 10, and the points are not), the calculation cannot be terminated, causing unnecessary time complexity;
b . When the commodity values ​​are all the same, and the points cannot be divisible by them , it will also cause all cases to be enumerated all the time
. c. When the commodity values ​​are all even numbers and the points are odd numbers ;

There is also a problem that is not known as a bug, that is, the value of the commodity is not rich enough, and the integral is too large, which may cause timeout (it is estimated that this is an inevitable defect of the enumeration method).

Commodity data reading and storage

Let’s write the program below. Let’s start by simply reading the data into the storage.

First of all, the teacher’s requirement is to use a structure to store data, and I have defined a spInfodata type here;

struct spInfo
{
    
    
    char name[256];
    int  value;
};

Then define a structure array spList. The initial data of the commodity can be written into the program in the form of initialization, such as this:

#define MAX_SP_NUM 256
spInfo spList[MAX_SP_NUM] = {
    
    
	{
    
    "Towels",380},
	{
    
    "Tooth Paste",260},
	{
    
    "Pencil",80}
};

However, with previous experience in student information management homework , why not read it from a file ? This makes it easy to modify, copy and paste information from the website, and is more user-friendly ; of course, keyboard input can also be selected . Next, I will introduce the method of file reading, if you don’t need it, please skip the next part.

First of all, our file needs to be written in this style, and the file name is set here splist.txt:

双肩包 = 8288
移动电源 = 7909
不锈钢保温杯 = 8699
新款毛巾 = 678
滋润型护肤霜 = 721

trim(), str2num()Is a function written by myself, used to trim off the spaces and line breaks at the beginning and end of the string. str2num()You can use the stdlib.hinside atoi()instead. For details, please refer to the student information management I wrote...

void trim(char* strIn, char* strOut) // support in-place opreation
{
    
    
	char *a=strIn, *b;
	while (*a == ' '||*a == '\n' || *a == '\t') a++; // ignore spaces at the beginning
	b = strIn + strlen(strIn) - 1; // get pointer pointing at the end of the line
	while (*b == ' '||*b == '\n' || *a == '\t') b--; // ignore spaces at the end
	while (a<=b) *strOut++ = *a++; // transplace
	*strOut='\0';
}

load_spList()Used to perform file reading.

int load_spList(const char* filePath = "splist.txt")
{
    
    
    FILE *fp = fopen(filePath,"r");
    char buffer[512]; //缓冲区,用来读入文件的一行
    char tmp[512]; //临时字符数组
    int k=0;
    while(!feof(fp))
    {
    
    
        fgets(buffer, sizeof(buffer), fp);
        char *src = buffer;
        char *dst = tmp;
        while (*src == ' ') src++; //ignore spaces
        if (*src =='\n') continue; //ignore empty line;

        trim(buffer,buffer); 
        while (*src != '=') *dst++ = *src++;
        *dst = '\0';
        trim(tmp,tmp);
        strcpy(spList[k].name,tmp);

        src = str2num(src, &spList[k].value);
        k++;
    }
    return k;
}

Recursive implementation of enumeration

Some global variables and arrays are declared below to store data and results;

#define RESULT_NUM 5
int results[RESULT_NUM][MAX_SP_NUM + 1];
int tmp_result[MAX_SP_NUM];
int results_index = 0;
int best_results = 0;
int EXPECT_REMAIN = 0;

RESULT_NUMIt is used to limit the number of required output solutions. The resultsarray is used to store the results (the last position of each line is used to store the remaining points), is tmp_resultused to store the number of commodity exchanges in the current discussion process, and is results_indexused to represent the array that stores the results now In the first few lines, it is best_resultsused to record the optimal solution that has been found, and is EXPECT_REMAINused to set the number of points remaining for the optimal solution (in response to the above-mentioned problem that will cause timeout).

The core functions are shown below a()to perform recursive operations. The operation of the function can be understood in this way. For the first item, first assume that you buy n pieces, and the points are subtracted. Then call recursively to discuss the next item, buy m pieces...until the export condition is reached: the points are used up, or the optimal is reached Circumstances, or the product enumeration is complete.

According to this, our function should be up to several things:

  • The serial number of the commodity under discussion
  • Total number of products
  • Remaining points
  • Result record table- has been set as a global variable

Of course, I think of these types here, and you can also set the variables you need according to your ideas.

Note: The update_plan()function is used to complete the transcription to the result storage array.

void update_plan(int tot_sp, int now_sp_index, int now_credit, int results_index)
{
    
    
    //写入结果,同时避免写入tmp_result里面来自上一次讨论的无意义的结果
    for (int p = 0; p<= now_sp_index - 1; p++ ) results[results_index][p] = tmp_result[p];
    //把其他的部分置零,因为结果存储数组里可能会有之前没那么优秀的结果的数据
    for (int p = now_sp_index; p< tot_sp; p++) results[results_index][p]=0;
    //最后一个位置存放剩余的积分数
    results[results_index][MAX_SP_NUM] = now_credit;
}
void a(int tot_sp,int now_sp_index, int now_credit)
{
    
    
    if (best_results == RESULT_NUM) return; //已经找够了,就退出
    // 如果剩余积分达“最佳状态”,或者讨论完了所有商品:
    if (now_credit <= EXPECT_REMAIN || now_sp_index == tot_sp ) 
    {
    
    
    	//如果这是个最佳结果,则“最佳结果”数加一
        if (now_credit == EXPECT_REMAIN) best_results++; 
        //如果结果存储数组还没有满
        if (results_index < RESULT_NUM) 
        {
    
    
            update_plan(tot_sp,now_sp_index,now_credit,results_index);
            results_index++;
        }
        else //否则得看有没有可以替换的数据
        {
    
    
            int max_remain_i = 0;
            for (int w=0; w < RESULT_NUM; w++) //遍历结果数组,看谁的剩余积分最多
            {
    
    
                if (results[w][MAX_SP_NUM] > results[max_remain_i][MAX_SP_NUM]) max_remain_i = w;
            } 
            if(now_credit<results[max_remain_i][MAX_SP_NUM]) //如果当前找到的方案比它优秀,那么就更新
                update_plan(tot_sp,now_sp_index,now_credit,max_remain_i);
        }
        return;
    }
	//从当前商品能兑换的最大量开始讨论
    for (int i = now_credit/spList[now_sp_index].value; i >=0 ; i--) 
    {
    
    
        tmp_result[now_sp_index]=i; //假设购买i件当前商品
        //往下一层递归
        a(tot_sp, now_sp_index+1, now_credit - spList[now_sp_index].value*i);
        //递归函数终止后,会回到上一级,然后进入下一个循环,即商品数-1,然后接着讨论
    }
}

main function

After some tentative calculations, it can be known that if there are a lot of points and the types of goods have a certain abundance, it is easy to spend all the points; if there are not many points, then even if there are a lot of goods, the entire enumeration process can be very fast. End (think about it, because you can't exchange a few, so the recursion will end soon).

int main()
{
    
    
	//首先读入的商品的信息
    int tot_sp = load_spList();
	//设置三个标签,对应前文所提到的特殊情况
    int flag_1 = 1; //假设所有商品都是10的倍数
    int flag_2 = 1; //假设所有商品价值均相等
    int flag_3 = 1; //假设所有商品价值全为偶数
    //输出读取到的商品信息
    printf("# Items: \n");
    for (int i=0;i<tot_sp;i++)
    {
    
    
        printf("%5d | %s\n",spList[i].value,spList[i].name);
        
        if (spList[i].value%10 != 0) flag_1 = 0;
        if (i>0) if (spList[i].value != spList[i-1].value) flag_2 = 0;
        if (spList[i].value % 2 == 1) flag_3 = 0;
    }
    printf("# Total %d items;\n",tot_sp);
    
    //获取用户积分数
    int m;
    printf("# Please input the credits you've got: ");
    scanf("%d",&m);
    
	//设定“最佳剩余积分”,即积分不可能少于这个
    if (flag_1 == 1) EXPECT_REMAIN = m % 10; 
    if (flag_3 == 1 && flag_1 != 1) EXPECT_REMAIN = m % 2; 
    if (flag_2 == 1) EXPECT_REMAIN = m % spList[0].value;
    // 比如,积分不是10的倍数,但恰好商品全是10的倍数,
    // 则剩余积分不可能为0,最好情况也只可能是m%10。
    //benchmark(m,tot_sp,flag_1,flag_2,flag_3); 
    //跑分函数用来检测有没有会被卡住的情况,该函数的definition见下个部分
    
	//开始运算
    a(tot_sp,0,m); 
    //运算结束,输出结果
    
    printf("\n");
    int cnt = 1; //记录输出方案的个数
    for(int i=0;i<results_index;i++)
    {
    
    
    	if(results[i][MAX_SP_NUM]==m) continue; //跳过积分没有被使用的情况
        
        printf("# Plan %d:\n",cnt++); //输出方案序号
        int remain = m;
        for (int j=0;j<tot_sp;j++) 
        {
    
    
            if (results[i][j] == 0) continue; //不输出没有兑换的商品
            printf("- %5d * %9d | %s\n",results[i][j],spList[j].value,spList[j].name);
            //remain -= results[i][j] * spList[j].value; 
            //设置一个remain,用来校验是否计算正确,可以去掉它,直接使用数组中存储的那个结果
        }
        printf ("# Remain credits: %d\n", results[i][MAX_SP_NUM]);
        //printf ("# Remain credits: %d\n",remain);
        printf("\n");
    }
    printf("# Finished!\n");
    if(cnt == 0) printf("# No solution can be found!\n");
    return 0;
}

Running points: detection algorithm performance

A running score function has been added, which can be used to detect whether the program will be stuck with a number between 1 and m. It seems useless.

int benchmark(int m, int tot_sp, int flag_1, int flag_2, int flag_3)
{
    
    
    if (flag_1 == 1) EXPECT_REMAIN = m % 10; 
    if (flag_3 == 1 && flag_1 != 1) EXPECT_REMAIN = m % 2; 
    if (flag_2 == 1) EXPECT_REMAIN = m % spList[0].value;
    for (int i=1;i<m;i++)
    {
    
       
        a(tot_sp,0,i);
        printf ("credits: %8d, Bingo! \n",i); 
        
        results_index = 0;
        best_results = 0;
        memset(results,0,sizeof(results));
        memset(tmp_result,0x00,sizeof(tmp_result));
    }
}

At last

Some people may say that your algorithm does not work, and the result is just a random solution, so what should I do if I want to redeem what I want?

In this case, I think I can first redeem what I want, subtract the points, and then run this program again; furthermore, I am only responsible for helping you spend the points clean, not saying that it can help you Make the best choice.

If you like it, you can try it and see if you can use it in your life (I guess no one uses it-"What's wrong with the remaining points? I have to write a program for that little thing, it's too much trouble!" ). You can also do some optimizations, such as sorting the results first and then outputting them, for example, you can "exchange" the desired products in advance, and so on. In general, this program can handle most of the problems, although there will still be stuck situations, if you are unfortunate / lucky enough to encounter it, you can leave a message below.

Guess you like

Origin blog.csdn.net/henry_23/article/details/104971250