Project Euler Problem 79 (C++和Python代码实现和解析)

100 篇文章 3 订阅
87 篇文章 1 订阅

Problem 79 : Passcode derivation

A common security method used for online banking is to ask the user for three random characters from a passcode. For example, if the passcode was 531278, they may ask for the 2nd, 3rd, and 5th characters; the expected reply would be: 317.

The text file, keylog.txt, contains fifty successful login attempts.

Given that the three characters are always asked for in order, analyse the file so as to determine the shortest possible secret passcode of unknown length.

1. 欧拉项目第79道题 : 密码推导

用于网上银行的一种常见的安全方法是向用户询问密码中的三个随机字符。例如,如果密码为531278,他们可能要求第2、第3和第5个字符;预期的答复是:317。

文本文件(keylog.txt ) 包含50次成功的登录尝试。

鉴于这三个字符总是按序被询问,分析文件,以确定最短可能的未知长度的保密的密码。

2. 求解分析

如何推导密码呢? 分为三步,
第一步: 因为询问是按顺序的, 我们先统计每个数字的前面的数字集合和它后面的数字集合, 考虑到第一步都是直接的, 我们还需要进行第二步, 把间接的数字也增加进来,
第二步: 就是遍历每个数字的后面的数字集合, 把有些间接的数字放进集合里。
第三步: 就是排序,如果一个数字后面的数字集合里的项越多,它就排在前面,一个数字后面的数字集合的项越少,它就排在后面,当然如果一个数字前面的数字集合和后面的数字集合的项都为空,那这个数字就没有被使用。

Step1: get info after reading keylog :
The digits behind digit 0 :
The digits behind digit 1 : 0, 2, 6, 8, 9,
The digits behind digit 2 : 0, 8, 9,
The digits behind digit 3 : 1, 6, 8,
The digits behind digit 4 :
The digits behind digit 5 :
The digits behind digit 6 : 0, 2, 8, 9,
The digits behind digit 7 : 1, 2, 3, 6, 9,
The digits behind digit 8 : 0, 9,
The digits behind digit 9 : 0,

Step 2: after derivation and before descending sort :
The digits behind digit 0 :
The digits behind digit 1 : 0, 2, 6, 8, 9,
The digits behind digit 2 : 0, 8, 9,
The digits behind digit 3 : 0, 1, 2, 6, 8, 9,
The digits behind or before digit 4 : none
The digits behind or before digit 5 : none
The digits behind digit 6 : 0, 2, 8, 9,
The digits behind digit 7 : 0, 1, 2, 3, 6, 8, 9,
The digits behind digit 8 : 0, 9,
The digits behind digit 9 : 0,

Step 3: after derivation and after descending sort:
The digits behind digit 7 : 0, 1, 2, 3, 6, 8, 9,
The digits behind digit 3 : 0, 1, 2, 6, 8, 9,
The digits behind digit 1 : 0, 2, 6, 8, 9,
The digits behind digit 6 : 0, 2, 8, 9,
The digits behind digit 2 : 0, 8, 9,
The digits behind digit 8 : 0, 9,
The digits behind digit 9 : 0,
The digits behind or before digit 5 : none
The digits behind or before digit 4 : none
The digits behind digit 0 :

最后,我们就可以推断出密码的所有的数字了。所以,第一步是基础,第二步最重要,不少人就是就是用纸和笔做的第二步,第三步排序也很重要,排序后,密码就水落石出了。

3. C++代码实现

我们根据求解分析的主要三步,定义了类PE0079, 类图如下:
在这里插入图片描述
首先,我们定义了一个struct _Digit:

typedef struct _Digit
{
    int  d;                   // digit
    bool is_used;             // this flag indicates whether digit is used   
    set<int> digits_s;        // digits set behind digit
    set<int> before_digits_s; // digits set before digit
} Digit;

类PE0079有一个数据成员:vector m_digits_vec,保存从0到9每个数字的信息;
类PE0079还有几个成员函数,其中第一步在函数readKeylog()里实现,第二步和第三步在函数findShortestSecretPasscode()里实现。

C++代码

#include <iostream>
#include <vector>
#include <set>
#include <iterator>
#include <fstream>
#include <sstream>
#include <algorithm>

using namespace std;

#define UNIT_TEST

typedef struct _Digit
{
    int  d;                   // digit
    bool is_used;             // this flag indicates whether digit is used   
    set<int> digits_s;        // digits set behind digit
    set<int> before_digits_s; // digits set before digit
} Digit;

class PE0079
{
private:
    vector<Digit> m_digits_vec;

    int readKeylog(char *filename);

    void printDigitsDebugInfo();
    void printDigitsInfo();

public:
    PE0079();
    int findShortestSecretPasscode();
};

bool myless(const Digit& digit1, const Digit& digit2);

PE0079::PE0079()
{
    Digit digit;
    for (int i = 0; i < 10; i++)
    {
        digit.d       = i;
        digit.is_used = true;
        m_digits_vec.push_back(digit);
    }
}
        
bool myless(const Digit& digit1, const Digit& digit2)
{
    int size1 = digit1.digits_s.size();
    int size2 = digit2.digits_s.size();

    if (size1 > size2)
    {
        return true;
    }
    else if (size1 == size2)
    {
        return digit1.d > digit2.d;
    }

    return false;
}
    
void PE0079::printDigitsDebugInfo()
{
    for (int i = 0; i < 10; i++)
    {
        int k = m_digits_vec[i].d;
        if (true == m_digits_vec[i].is_used)
        {
            cout << "The digits behind digit " << k << " : ";
            set<int> tmp_digits_s = m_digits_vec[i].digits_s;
            copy(tmp_digits_s.begin(), tmp_digits_s.end(), \
                ostream_iterator<int>(cout, ", "));
            cout << endl;
        }
        else
        {
            cout << "The digits behind or before digit "<<k<<" : none"<<endl;
        }
    }
}
        
void PE0079::printDigitsInfo()
{
    for (int i = 0; i < 10; i++)
    {
        if (true == m_digits_vec[i].is_used)
        {
            cout << m_digits_vec[i].d;
        }
    }
    cout << endl;
}

int PE0079::readKeylog(char *filename)
{
    char szBuf[8];

    ifstream in(filename);
    if (!in) return -1;

    int number;
    int digit, last_digit;

    while (in.getline(szBuf, 8))
    {
        string strText = szBuf;
        istringstream s(strText);
        s >> number;
        last_digit = -1;
        while (number > 0)
        {
            digit = number % 10;
            number /= 10;
            if (-1 != last_digit)
            {
                m_digits_vec[digit].digits_s.insert(last_digit);
                m_digits_vec[last_digit].before_digits_s.insert(digit);
            }
            last_digit = digit;
        }
    }
    return 0;
}

int PE0079::findShortestSecretPasscode()
{
    char filename[] = "p079_keylog.txt";
    
    if (0 != readKeylog(filename))
    {
        return -1;
    }

#ifdef UNIT_TEST
    cout << "Step 1: get info after reading keylog : " << endl;
    printDigitsDebugInfo();
#endif

    for (int i = 0; i < 10; i++)
    {
        if (0 != m_digits_vec[i].digits_s.size() || 
            0 != m_digits_vec[i].before_digits_s.size())
        {
            for (auto digit : m_digits_vec[i].digits_s)
            {
                for (auto digit_new : m_digits_vec[digit].digits_s)
                {
                    m_digits_vec[i].digits_s.insert(digit_new);
                }
            }
        }
        else
        {
            m_digits_vec[i].is_used = false;
        }
    }

#ifdef UNIT_TEST
    cout << "Step 2: after derivation and before descending sort : "<<endl;
    printDigitsDebugInfo();
#endif

    sort(m_digits_vec.begin(), m_digits_vec.end(), myless);

#ifdef UNIT_TEST
    cout << "Step 3: after derivation and after descending sort: " << endl;
    printDigitsDebugInfo();
#endif

    cout << "The shortest possible secret passcode of unknown length is ";
    printDigitsInfo();

    return 0;
}
    
int main()
{
    PE0079 pe0079;

    pe0079.findShortestSecretPasscode();

    return 0;
}

4. Python代码实现

Python也采用了求解分析的方法,但是由于数据结构的原因,Python和C++稍微有点不同。

为了控制打印Debug 信息,一个全局的变量 PE0079_Eanble_Debug 被定义:
PE0079_Eanble_Debug = True, 打印出所有的Debug信息,否则,PE0079_Eanble_Debug = False, 不会打印任何Debug信息。

Python 代码

class PE0079(object):
    def __init__(self):
        self.m_digits_list = []
        for i in range(10):
            # digit tuple = (digit, digits set behind digit, digits set before digit)  
            digit_tuple = i, set(), set() 
            self.m_digits_list += [ digit_tuple ]
        
    def printStep1DebugInfo(self):
        for digit_tuple in self.m_digits_list:
            print('The digits behind digit %d :' % digit_tuple[0], digit_tuple[1])
            print('The digits before digit %d :' % digit_tuple[0], digit_tuple[2])
    
    def printStep2DebugInfo(self, tmp_passcode_list):
        print("Step 2: after derivation and before descending sort : ")
        for index, digits_list in enumerate(tmp_passcode_list):
            if len(digits_list) != 0 or len(self.m_digits_list[index][2]) != 0:
                print('The digits behind digit %d :'% index, digits_list)
            else:
                print('The digits behind or before digit %d : none'% index)

    def printStep3DebugInfo(self, final_passcode_list):
        print("Step 3: after derivation and after descending sort: ")
        for digit_tuple in final_passcode_list:
            print('The digits behind digit %d :'%digit_tuple[0], digit_tuple[1])

    def printDigitsInfo(self, passcode_list):
        for digit_tuple in passcode_list:
            print(digit_tuple[0], end='')
        print()

    def readKeylog(self, filename):
        for line in open(filename).readlines():
            number, last_digit = int(line), -1    # one number per line
   
            while number > 0:
                digit, number = number % 10, number // 10
                if -1 != last_digit:
                    digit_tuple = self.m_digits_list[digit]
                    self.m_digits_list[digit][1].add(last_digit)
                    last_digit_tuple = self.m_digits_list[last_digit]
                    last_digit_tuple[2].add(digit)
                last_digit = digit
        
    def findShortestSecretPasscode(self):   
        self.readKeylog('p079_keylog.txt')
    
        if True == PE0079_Eanble_Debug:
            print("Step 1: get info after reading keylog :")
            self.printStep1DebugInfo();
        
        tmp_passcode_list = []
        for digit_tuple in self.m_digits_list:
            tmp_list = list(digit_tuple[1])    # digits list behind digit
            if 0 != len(digit_tuple[1]) or 0 != len(digit_tuple[2]):
                if len(digit_tuple[1]) > 0:
                    for digit in digit_tuple[1]:
                        digit_new_tuple = self.m_digits_list[digit] 
                        for digit_new in digit_new_tuple[1]:
                            if digit_new not in tmp_list:
                                tmp_list += [ digit_new ]
            tmp_passcode_list += [ tmp_list]
               
        if True == PE0079_Eanble_Debug:
            self.printStep2DebugInfo(tmp_passcode_list)
            
        final_passcode_list = []
        for index, list_behind_digit in enumerate(tmp_passcode_list):
            if len(list_behind_digit) != 0 or len(self.m_digits_list[index][2]) != 0:
                final_passcode_list += [(index, list_behind_digit)]
                        
        final_passcode_list.sort(key=lambda passcode: len(passcode[1]), reverse=True)
    
        if True == PE0079_Eanble_Debug:
            self.printStep3DebugInfo(final_passcode_list)
    
        print("The shortest possible secret passcode of unknown length is ", end='');
        self.printDigitsInfo(final_passcode_list)

        return 0
    
def main():
    global PE0079_Eanble_Debug 
    PE0079_Eanble_Debug = True
    
    pe0079 = PE0079()

    pe0079.findShortestSecretPasscode()
    
if  __name__ == '__main__':
    main()

Python代码 II (通过使用列表的deepcopy,最终解决了RuntimeError: Set changed size during iteration)

import copy 

class PE0079(object):
    def __init__(self):
        self.m_digits_list = []
        for i in range(10):
            # d_tuple = (digit, digits set behind digit, digits set before digit)  
            self.m_digits_list += [ (i, set(), set()) ]
        
    def printDebugInfo(self):
        for d_tuple in self.m_digits_list:
            if len(d_tuple[1]) != 0 or len(d_tuple[2]) != 0:
                print('The digits behind digit %d :' %d_tuple[0],d_tuple[1])
                #print('The digits before digit %d :' %d_tuple[0],d_tuple[2])
            else:
                print('The digits behind or before digit %d : none'%d_tuple[0])
        print('')
    
    def printDigitsInfo(self):
        for digit_tuple in self.m_digits_list:
            if len(digit_tuple[1]) != 0 or len(digit_tuple[2]) != 0:
                print(digit_tuple[0], end='')
        print('')
    
    def readKeylog(self, filename):
        for line in open(filename).readlines():
            number, last_digit = int(line), -1    # one number per line
            while number > 0:
                digit, number = number % 10, number // 10
                if -1 != last_digit:
                    self.m_digits_list[digit][1].add(last_digit)
                    self.m_digits_list[last_digit][2].add(digit)
                last_digit = digit
        
    def findShortestSecretPasscode(self):   
        self.readKeylog('p079_keylog.txt')
    
        if True == PE0079_Eanble_Debug:
            print("Step 1: get info after reading keylog :")
            self.printDebugInfo();
            
        # for digit in digit_tuple[1]:
        # RuntimeError: Set changed size during iteration
        tmp_passcode_digits_list = copy.deepcopy(self.m_digits_list) # required!!
        for d, digit_tuple in enumerate(tmp_passcode_digits_list):
            if len(digit_tuple[1]) > 0:
                for digit in digit_tuple[1]:
                    behind_digit_tuple = tmp_passcode_digits_list[digit] 
                    for behind_digit in behind_digit_tuple[1]:
                        if behind_digit not in digit_tuple[1]:
                            self.m_digits_list[d][1].add(behind_digit)

            #if len(digit_tuple[2]) > 0:
            #    for digit in digit_tuple[2]:
            #        before_digit_tuple = tmp_passcode_digits_list[digit] 
            #        for before_digit in before_digit_tuple[2]:
            #            if before_digit not in digit_tuple[2]:
            #                self.m_digits_list[d][2].add(before_digit)

        if True == PE0079_Eanble_Debug:
            print("Step 2: after derivation and before descending sort : ")
            self.printDebugInfo()
                        
        # descending sort, by the number of digits behind digit
        self.m_digits_list.sort(key=lambda d_tuple: len(d_tuple[1]), reverse=True)
    
        if True == PE0079_Eanble_Debug:
            print("Step 3: after derivation and after descending sort: ")
            self.printDebugInfo()
        
        print("The shortest possible secret passcode of unknown length is ",end='')
        self.printDigitsInfo()

        return 0
    
def main():
    global PE0079_Eanble_Debug 
    PE0079_Eanble_Debug = True
    
    pe0079 = PE0079()

    pe0079.findShortestSecretPasscode()
    
if  __name__ == '__main__':
    main()
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值