CS50 Vigenere,代码快完成了,但我不知道还缺少什么?
CS50 Vigenere, code is almost done but I don't know what's missing?
#include <cs50.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
int main(int argc, string argv[])
{
// two arguments
if (argc != 2)
{
printf("Give two arguments\n");
return 1;
}
printf("plaintext: ");
string plaintext = get_string();
printf("ciphertext: ");
string key = argv[1];
for (int i = 0, t = 0, n = strlen(plaintext); i < n; i++, t++)
{
// if it's no letter, then:
if (!isalpha(plaintext[i]) && plaintext[i] != ' ')
{
printf("False");
return 1;
}
int number = 0;
if (isalpha(plaintext[i]))
{
number += 1;
}
if (strlen(key) > number)
{
number = 0;
}
if (isupper(plaintext[i]))
{
printf("%c", (((plaintext[i] - 65) + key[number]) % 26) + 65);
}
//if it is lowercase
else if (islower(plaintext[i]))
{
printf("%c", (((plaintext[i] - 97) + key[number]) % 26) + 97);
}
else
{
printf("%c", plaintext[i]);
}
}
printf("\n");
}
所以我的代码中缺少一些东西。当我执行 ./vigenere baz
然后键入明文时:Hello, world!
,我得到密文:ByffiFalse
。我应该得到 iekmo, vprke!
另外,当我输入 ./vigenere hello
,然后输入 bye
作为明文时,我也会得到密文 bye
,而它应该是 icp
.有人可以找出我的代码缺少什么或有什么问题吗?
您的代码最大的两个问题是计算正确的密钥差异值(您不是)和密钥推进。我会倒着讲。
按键推进应从第一个按键字符开始,然后随着每个正在处理的纯文本逐一推进。当键位置到达字符串末尾时,它会重新启动。最基本的伪代码是
char *keyp = argv[1];
for (loop through plainttext)
{
if (*keyp == 0) // reached the terminator ?
keyp = argv[1]; // then reset to beginning.
//... process the current plain text character, using *keyp
//... as the next key character to use.
// advance key to next position (possibly conditionally)
++keyp;
}
但是您的代码没有这样做。相反,它会立即推进密钥,这意味着您从 second 字符开始。
int number = 0;
if (isalpha(plaintext[i]))
{
number += 1; // HERE. first pass will use key[1]. it should be key[0]
}
if (strlen(key) > number) // this is backward
{
number = 0;
}
其次,也许更重要的是,如果 Vigenere 密码有效地使用正方形阴影,则整个要点 table。 See this link for a picture of that。您正在编码的算法的要点是 act 就像 table 使用数学存在一样。偏移量很重要part.When 你做这个计算:
(((plaintext[i] - 65) + key[number]) % 26) + 65
实际上应该是这样的:
(((plaintext[i] - 'A') + key[number]) % 26) + 'A'
考虑添加关键字符的作用。举个例子:
key: baz
plaintext: Hello, World!
您计算出的第一个密文字符为:
((('H' - 'A') + 'a') % 26) + 'A'
注意:'a'
在那里是因为你的第一关被一个打破了,记得吗?
压缩如下
(((7) + 97) % 26) + 'A'
((105) % 26) + 'A'
(1 % 26) + 'A'
1 + 'A'
'B'
这正是您得到的。但这是错误的。这是错误的,因为这是错误的:
(((plaintext[i] - 'A') + key[number]) % 26) + 'A'
^^^^^^^^^^^
这是输入字符的原始 ascii 值。它应该是 1..26 之间的计算值。简而言之,您没有正确调整按键输入。
假设解
以下假定密钥始终为小写。它还修复了您的 first-skip 逻辑,并使用 cs50.h 解耦(坦率地说,我认为弊大于利)。最后,它使用 `char* 来跟踪下一个要使用的关键字符。我把支持混合大小写输入键的任务留给你:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
int main(int argc, char *argv[])
{
// two arguments
if (argc != 2)
{
printf("Give two arguments\n");
return 1;
}
printf("plaintext: ");
char pt[256] = { 0 };
if (fgets(pt, sizeof pt, stdin))
{
// get the plaintext length
size_t ptlen = strlen(pt);
// remove trailing newline if present, and adjust ptlen
if (ptlen > 0 && pt[ptlen - 1] == '\n')
pt[--ptlen] = 0;
// the key we're using. intially at the start
char *key = argv[1];
for (size_t i = 0; i < ptlen; ++i)
{
// reset key if prior iteration landed on terminator
if (!*key)
key = argv[1];
if (isalpha((unsigned char)pt[i]))
{
if (isupper((unsigned char)pt[i]))
{
printf("%c", (((pt[i] - 'A') + (*key-'a')) % 26) + 'A');
++key;
}
//if it is lowercase
else if (islower((unsigned char)pt[i]))
{
printf("%c", (((pt[i] - 'a') + (*key-'a')) % 26) + 'a');
++key;
}
else
{
fputc(pt[i], stdout);
}
}
else
{
fputc(pt[i], stdout);
}
}
fputc('\n', stdout);
}
else
{
perror("Failed to read string");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
来自./progname baz
的输出
plaintext: Hello, World!
Iekmo, Vprke!
- 所有非字母字符(不仅仅是空格)都应该在不编码的情况下跳过。不要打印
"False"
和 return,例如 "Hello, world!"
字符串中的 ','
符号。此外,您可以就地编码字符串。因此,主循环可能看起来像
printf("plaintext: ");
string s = GetString();
if (s == NULL)
return 1;
for (int i = 0, len = strlen(s); i < len; ++i) {
if (isalpha(s[i])) {
/* encode s[i] in-place,
* all non-alpha characters left as is
*/
}
}
printf("ciphertext: %s\n", s);
- 关键字符也应该是"shifted"。例如,对于大写字母
s[i] = ((s[i] - 'A') + (key[n] - 'A') % 26) + 'A';
if (++n >= keylen)
n = 0;
我建议在主循环之前规范化键,这样您就可以对输入字符串中的小写和大写字符使用(key[n] - 'A')
:
string key = argv[1];
strupper(k);
int keylen = strlen(key);
int n = 0;
虽然我不想提供完整的代码,因为这是你的课程,但我认为你自己做会更好。但是……一些作品:
strupper
函数:
void strupper(string s)
{
for (int i = 0, n = strlen(s); i < n; ++i)
s[i] = toupper(s[i]);
}
紧凑主循环:
for (int i = 0, n = strlen(s); i < n; ++i) {
if (isalpha(s[i])) {
char ref = isupper(s[i]) ? 'A' : 'a';
int shift = k[j] - 'A';
s[i] = ref + (s[i] - ref + shift) % 26;
if (++j >= klen) j = 0;
}
}
p.s。由于 int number = 0;
在 for
循环中定义和归零,您对所有输入字符使用相同的键字符。
#include <cs50.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
int main(int argc, string argv[])
{
// two arguments
if (argc != 2)
{
printf("Give two arguments\n");
return 1;
}
printf("plaintext: ");
string plaintext = get_string();
printf("ciphertext: ");
string key = argv[1];
for (int i = 0, t = 0, n = strlen(plaintext); i < n; i++, t++)
{
// if it's no letter, then:
if (!isalpha(plaintext[i]) && plaintext[i] != ' ')
{
printf("False");
return 1;
}
int number = 0;
if (isalpha(plaintext[i]))
{
number += 1;
}
if (strlen(key) > number)
{
number = 0;
}
if (isupper(plaintext[i]))
{
printf("%c", (((plaintext[i] - 65) + key[number]) % 26) + 65);
}
//if it is lowercase
else if (islower(plaintext[i]))
{
printf("%c", (((plaintext[i] - 97) + key[number]) % 26) + 97);
}
else
{
printf("%c", plaintext[i]);
}
}
printf("\n");
}
所以我的代码中缺少一些东西。当我执行 ./vigenere baz
然后键入明文时:Hello, world!
,我得到密文:ByffiFalse
。我应该得到 iekmo, vprke!
另外,当我输入 ./vigenere hello
,然后输入 bye
作为明文时,我也会得到密文 bye
,而它应该是 icp
.有人可以找出我的代码缺少什么或有什么问题吗?
您的代码最大的两个问题是计算正确的密钥差异值(您不是)和密钥推进。我会倒着讲。
按键推进应从第一个按键字符开始,然后随着每个正在处理的纯文本逐一推进。当键位置到达字符串末尾时,它会重新启动。最基本的伪代码是
char *keyp = argv[1];
for (loop through plainttext)
{
if (*keyp == 0) // reached the terminator ?
keyp = argv[1]; // then reset to beginning.
//... process the current plain text character, using *keyp
//... as the next key character to use.
// advance key to next position (possibly conditionally)
++keyp;
}
但是您的代码没有这样做。相反,它会立即推进密钥,这意味着您从 second 字符开始。
int number = 0;
if (isalpha(plaintext[i]))
{
number += 1; // HERE. first pass will use key[1]. it should be key[0]
}
if (strlen(key) > number) // this is backward
{
number = 0;
}
其次,也许更重要的是,如果 Vigenere 密码有效地使用正方形阴影,则整个要点 table。 See this link for a picture of that。您正在编码的算法的要点是 act 就像 table 使用数学存在一样。偏移量很重要part.When 你做这个计算:
(((plaintext[i] - 65) + key[number]) % 26) + 65
实际上应该是这样的:
(((plaintext[i] - 'A') + key[number]) % 26) + 'A'
考虑添加关键字符的作用。举个例子:
key: baz
plaintext: Hello, World!
您计算出的第一个密文字符为:
((('H' - 'A') + 'a') % 26) + 'A'
注意:'a'
在那里是因为你的第一关被一个打破了,记得吗?
压缩如下
(((7) + 97) % 26) + 'A'
((105) % 26) + 'A'
(1 % 26) + 'A'
1 + 'A'
'B'
这正是您得到的。但这是错误的。这是错误的,因为这是错误的:
(((plaintext[i] - 'A') + key[number]) % 26) + 'A'
^^^^^^^^^^^
这是输入字符的原始 ascii 值。它应该是 1..26 之间的计算值。简而言之,您没有正确调整按键输入。
假设解
以下假定密钥始终为小写。它还修复了您的 first-skip 逻辑,并使用 cs50.h 解耦(坦率地说,我认为弊大于利)。最后,它使用 `char* 来跟踪下一个要使用的关键字符。我把支持混合大小写输入键的任务留给你:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
int main(int argc, char *argv[])
{
// two arguments
if (argc != 2)
{
printf("Give two arguments\n");
return 1;
}
printf("plaintext: ");
char pt[256] = { 0 };
if (fgets(pt, sizeof pt, stdin))
{
// get the plaintext length
size_t ptlen = strlen(pt);
// remove trailing newline if present, and adjust ptlen
if (ptlen > 0 && pt[ptlen - 1] == '\n')
pt[--ptlen] = 0;
// the key we're using. intially at the start
char *key = argv[1];
for (size_t i = 0; i < ptlen; ++i)
{
// reset key if prior iteration landed on terminator
if (!*key)
key = argv[1];
if (isalpha((unsigned char)pt[i]))
{
if (isupper((unsigned char)pt[i]))
{
printf("%c", (((pt[i] - 'A') + (*key-'a')) % 26) + 'A');
++key;
}
//if it is lowercase
else if (islower((unsigned char)pt[i]))
{
printf("%c", (((pt[i] - 'a') + (*key-'a')) % 26) + 'a');
++key;
}
else
{
fputc(pt[i], stdout);
}
}
else
{
fputc(pt[i], stdout);
}
}
fputc('\n', stdout);
}
else
{
perror("Failed to read string");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
来自./progname baz
plaintext: Hello, World!
Iekmo, Vprke!
- 所有非字母字符(不仅仅是空格)都应该在不编码的情况下跳过。不要打印
"False"
和 return,例如"Hello, world!"
字符串中的','
符号。此外,您可以就地编码字符串。因此,主循环可能看起来像
printf("plaintext: ");
string s = GetString();
if (s == NULL)
return 1;
for (int i = 0, len = strlen(s); i < len; ++i) {
if (isalpha(s[i])) {
/* encode s[i] in-place,
* all non-alpha characters left as is
*/
}
}
printf("ciphertext: %s\n", s);
- 关键字符也应该是"shifted"。例如,对于大写字母
s[i] = ((s[i] - 'A') + (key[n] - 'A') % 26) + 'A';
if (++n >= keylen)
n = 0;
我建议在主循环之前规范化键,这样您就可以对输入字符串中的小写和大写字符使用(key[n] - 'A')
:
string key = argv[1];
strupper(k);
int keylen = strlen(key);
int n = 0;
虽然我不想提供完整的代码,因为这是你的课程,但我认为你自己做会更好。但是……一些作品:
strupper
函数:
void strupper(string s)
{
for (int i = 0, n = strlen(s); i < n; ++i)
s[i] = toupper(s[i]);
}
紧凑主循环:
for (int i = 0, n = strlen(s); i < n; ++i) {
if (isalpha(s[i])) {
char ref = isupper(s[i]) ? 'A' : 'a';
int shift = k[j] - 'A';
s[i] = ref + (s[i] - ref + shift) % 26;
if (++j >= klen) j = 0;
}
}
p.s。由于 int number = 0;
在 for
循环中定义和归零,您对所有输入字符使用相同的键字符。