12

在回答这个询问十六进制到(原始)二进制转换的问题时,有一条评论建议它可以用“5-10 行 C 或任何其他语言”来解决。

我确信对于(某些)脚本语言可以实现,并且想看看如何。我们能否证明该评论对于 C 也是正确的?

注意:这并不意味着十六进制到ASCII二进制 - 特别是输出应该是对应于输入 ASCII 十六进制的原始八位字节流。此外,输入解析器应该跳过/忽略空格。

编辑(由 Brian Campbell 撰写)为了保持一致性,我可以提出以下规则吗?如果您认为这些内容没有帮助,请随意编辑或删除这些内容,但我认为由于已经讨论了某些情况应该如何工作,因此进行一些澄清会有所帮助。

  1. 程序必须从标准输入读取并写入标准输出(我们也可以允许读取和写入通过命令行传入的文件,但我无法想象在任何语言中这会比标准输入和标准输出更短)
  2. 该程序必须仅使用包含在您的基本标准语言发行版中的软件包。对于 C/C++,这意味着它们各自的标准库,而不是 POSIX。
  3. 程序必须在没有传递给编译器或解释器的任何特殊选项的情况下编译或运行(因此,'gcc myprog.c' 或 'python myprog.py' 或 'ruby myprog.rb' 都可以,而 'ruby -rscanf myprog.rb ' 是不允许的;需要/导入模块会影响您的字符数)。
  4. 程序应读取由成对的相邻十六进制数字(大写、小写或混合大小写)表示的整数字节,可选用空格分隔,并将相应的字节写入输出。每对十六进制数字首先写入最高有效半字节。
  5. 程序对无效输入的行为(除 之外[a-fA-F \t\r\n]的字符,将两个字符分隔在单个字节中的空格,输入中的奇数个十六进制数字)是未定义的;错误输入的任何行为(除了主动损坏用户的计算机或其他东西)都是可以接受的(抛出错误,停止输出,忽略错误字符,将单个字符视为一个字节的值,都可以)
  6. 程序可能不会写入额外的字节来输出。
  7. 代码按源文件中最少的总字节数计分。(或者,如果我们想更忠实于最初的挑战,分数将基于最少的代码行数;在这种情况下,我会限制每行 80 个字符,否则你会得到一堆1 行的关系)。
4

16 回答 16

8

编辑Checkers 已将我的 C 解决方案减少到46 字节,然后由于 BillyONEal 的提示加上我的错误修复(不再有错误输入的无限循环,现在它只是终止循环)而减少到 44 字节。请感谢 Checkers 将其从 77 字节减少到 46 字节:

main(i){while(scanf("%2x",&i)>0)putchar(i);}

而且我有一个比上一个更好的 Ruby 解决方案,在42 38字节(感谢 Joshua Swank 的正则表达式建议):

STDIN.read.scan(/\S\S/){|x|putc x.hex}

原始解决方案

C,77 个字节,或两行代码(如果您可以将 1 放在#include同一行,则为 1)。请注意,这对错误输入有一个无限循环;在 Checkers 和 BillyONEal 的帮助下,44 字节的解决方案修复了错误,并在输入错误时停止。

#include <stdio.h>
int main(){char c;while(scanf("%2x",&c)!=EOF)putchar(c);}

如果你正常格式化它甚至只有 6 行:

#include <stdio.h>
int main() {
  char c;
  while (scanf("%2x",&c) != EOF)
    putchar(c);
}

Ruby,79 字节(我相信这可以改进):

STDOUT.write STDIN.read.scan(/[^\s]\s*[^\s]\s*/).map{|x|x.to_i(16)}.pack("c*")

它们都从 STDIN 获取输入并写入 STDOUT

于 2009-04-27T21:57:28.577 回答
7

39 char perl oneliner

y/A-Fa-f0-9//dc,print pack"H*",$_ for<>

编辑:并没有真正接受大写,已修复。

于 2009-04-28T13:11:54.853 回答
7

45 字节可执行文件(base64 编码):

6BQAitjoDwDA4AQI2LQCitDNIevrWMOy/7QGzSF09jLkBMAa5YDkByrEJA/D

(粘贴到扩展名为 .com 的文件中)

编辑:好的,这是代码。打开一个窗口的控制台,创建一个名为“hex.com”的 45 字节文件,输入“debug hex.com”,然后输入“a”并输入。复制并粘贴这些行:

db e8,14,00,8a,d8,e8,0f,00,c0,e0,04,08,d8,b4,02,8a,d0,cd,21,eb,eb,cd,20
db b2,ff,b4,06,cd,21,74,f6,32,e4,04,c0,1a,e5,80,e4,07,2a,c4,24,0f,c3

按回车键,'w',然后再次输入,'q' 并回车。您现在可以运行“hex.com”

EDIT2:使它小了两个字节!

db e8, 11, 00, 8a, d8, e8, 0c, 00, b4, 02, 02, c0, 67, 8d, 14, c3
db cd, 21, eb, ec, ba, ff, 00, b4, 06, cd, 21, 74, 0c, 04, c0, 18
db ee, 80, e6, 07, 28, f0, 24, 0f, c3, cd, 20

那很棘手。我不敢相信我花时间做那件事。

于 2009-04-28T15:10:56.313 回答
6

Brian 的 77 字节 C 解决方案可以改进到 44 字节,这要归功于 C 对函数原型的宽容。

main(i){while(scanf("%2x",&i)>0)putchar(i);}
于 2009-04-28T01:47:21.613 回答
4

在 Python 中:

binary = binascii.unhexlify(hex_str)

一条线!(是的,这是作弊。)

于 2009-04-27T19:58:55.553 回答
3

编辑:这段代码是在充实要求的问题编辑之前很长时间编写的。

鉴于单行 C 可以包含大量语句,几乎可以肯定它是正确的,但没有用处。

在 C# 中,我几乎肯定会用 10 多行来编写它,即使它在 10 行中也是可行的。我会将“解析 nybble”部分与“将字符串转换为字节数组”部分分开。

当然,如果您不关心发现错误的长度等,它会变得更容易一些。您的原始文本还包含空格 - 是否应该跳过、验证等?它们是所需输入格式的一部分吗?

我宁愿怀疑这个评论是在没有考虑一个令人愉快的、可读的解决方案是什么样子的情况下发表的。

话虽如此,这是 C# 中的一个可怕的版本。对于加分,它完全不恰当地使用 LINQ 来节省一两行代码。当然,线路可以更长...

using System;
using System.Linq;

public class Test
{
    static void Main(string[] args)
    {
        byte[] data = ParseHex(args[0]);
        Console.WriteLine(BitConverter.ToString(data));

    }

    static byte[] ParseHex(string text)
    {
        Func<char, int> parseNybble = c => (c >= '0' && c <= '9') ? c-'0' : char.ToLower(c)-'a'+10;
        return Enumerable.Range(0, text.Length/2)
            .Select(x => (byte) ((parseNybble(text[x*2]) << 4) | parseNybble(text[x*2+1])))
            .ToArray();
    }
}

(这是通过使用任何内置的十六进制解析代码来避免“作弊”,例如Convert.ToByte(string, 16)。除此之外,这将意味着失去对单词 nybble 的使用,这始终是一个奖励。)

于 2009-04-27T19:56:24.793 回答
2

嘎。

你不能在我的即兴估计上打电话给我!;-P

这是一个没有奇怪格式的 9 行 C 版本(好吧,我承认 tohex 数组最好分成 16 行,这样你就可以看到哪些字符代码映射到哪些值......),并且只有 2 个快捷方式除了一次性脚本之外,我不会部署任何东西:

#include <stdio.h>
char hextonum[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0,10,11,12,13,14,15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,10,11,12,13,14,15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
char input[81]="8b1f0008023149f60300f1f375f40c72f77508507676720c560d75f002e5ce000861130200000000";
void main(void){
   int i = 0;
   FILE *fd = fopen("outfile.bin", "wb");
   while((input[i] != 0) && (input[i+1] != 0))
      fputc(hextonum[input[i++]] * 16 + hextonum[input[i++]], fd);
}

没有合并的行(每个语句都有自己的行),它是完全可读的,等等。一个混淆的版本无疑可能更短,一个人可以作弊并将右大括号放在与前面的语句相同的行上,等等等等。

我不喜欢它的两件事是我没有 close(fd) ,并且 main 不应该是 void 并且应该返回一个 int。可以说它们是不需要的——操作系统将释放程序使用的所有资源,文件将毫无问题地关闭,编译器将处理程序退出值。鉴于它是一次性使用脚本,它是可以接受的,但不要部署它。

两者都变成了 11 行,所以无论如何这并不是一个巨大的增加,而 10 行版本将包括一个或另一个,这取决于一个人可能会觉得是两害相权。

它不进行任何错误检查,并且不允许空格 - 再次假设它是一次性程序,那么在运行脚本之前执行搜索/替换并删除空格和其他空格会更快,但是它不应该只需要另外几行来吃空白。

当然,有一些方法可以缩短它,但它们可能会显着降低可读性......

哼。 只需阅读有关 line length 的评论,因此这里有一个更新的版本,它有一个更丑的 hextonum 宏,而不是数组:

#include <stdio.h>
#define hextonum(x) (((x)<'A')?((x)-'0'):(((x)<'a')?((x)+10-'A'):((x)+10-'a')))
char input[81]="8b1f0008023149f60300f1f375f40c72f77508507676720c560d75f002e5ce000861130200000000";
void main(void){
   int i = 0;
   FILE *fd = fopen("outfile.bin", "wb");
   for(i=0;(input[i] != 0) && (input[i+1] != 0);i+=2)
      fputc(hextonum(input[i]) * 16 + hextonum(input[i+1]), fd);
}

它不是非常不可读,但我知道很多人对三元运算符有疑问,但是宏的适当命名和一些分析应该很容易让普通 C 程序员知道它是如何工作的。由于宏中的副作用,我不得不移至 for 循环,因此我不必为 i+=2 设置另一行(hextonum(i++)每次调用时都会将 i 增加 5,宏副作用不适合胆小的人!)。

此外,输入解析器应该跳过/忽略空格。

抱怨,抱怨,抱怨。

我不得不添加几行来满足这个要求,现在最多 14 行用于格式合理的版本。它将忽略所有不是十六进制字符的内容:

#include <stdio.h>
int hextonum[] = {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,-1,-1,-1,-1,-1,-1,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1};
char input[]="8b1f 0008 0231 49f6 0300 f1f3 75f4 0c72 f775 0850 7676 720c 560d 75f0 02e5 ce00 0861 1302 0000 0000";
void main(void){
   unsigned char i = 0, nibble = 1, byte = 0;
   FILE *fd = fopen("outfile.bin", "wb");
   for(i=0;input[i] != 0;i++){
      if(hextonum[input[i]] == -1)
         continue;
      byte = (byte << 4) + hextonum[input[i]];
      if((nibble ^= 0x01) == 0x01)
         fputc(byte, fd);
   }
}

我不关心 80 个字符的行长,因为输入甚至不小于 80 个字符,但是一个 3 级三元宏可以替换第一个 256 个条目数组。如果不介意一点“替代格式”,那么以下 10 行版本并非完全不可读:

#include <stdio.h>
int hextonum[] = {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,-1,-1,-1,-1,-1,-1,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1};
char input[]="8b1f 0008 0231 49f6 0300 f1f3 75f4 0c72 f775 0850 7676 720c 560d 75f0 02e5 ce00 0861 1302 0000 0000";
void main(void){
   unsigned char i = 0, nibble = 1, byte = 0;
   FILE *fd = fopen("outfile.bin", "wb");
   for(i=0;input[i] != 0;i++){
      if(hextonum[input[i]] == -1) continue;
      byte = (byte << 4) + hextonum[input[i]];
      if((nibble ^= 0x01) == 0x01) fputc(byte, fd);}}

而且,再一次,进一步的混淆和比特旋转可能会导致一个更短的例子。

于 2009-04-27T21:09:54.697 回答
2

Perl

当然,在一条(相当短的)行中:

my $bin = map { chr hex } ($hex =~ /\G([0-9a-fA-F]{2})/g);
于 2009-04-27T22:52:34.263 回答
2

哈斯克尔:

import Data.Char
import Numeric
import System.IO
import Foreign

main = hGetContents stdin >>= 
       return.fromHexStr.filter (not.isSpace) >>=  
       mapM_ (writeOneByte stdout)

fromHexStr (a:b:tl) = fromHexDgt [a,b]:fromHexStr tl
fromHexStr [] = []
fromHexDgt str =  case readHex str of 
  [(i,"")] -> fromIntegral (i)
  s -> error$show s

writeOneByte h i = allocaBytes 1 (wob' h i)
wob' :: Handle -> Int8 -> (Ptr Int8) -> IO ()
wob' h i ptr = poke ptr i >> hPutBuf h ptr 1
于 2009-04-28T00:44:54.617 回答
2
.

它是一种叫做“Hex!”的语言。它的唯一用途是从标准输入读取十六进制数据并将其输出到标准输出。十六进制!由一个简单的python脚本解析。导入系统

try:
  data = open(sys.argv[1], 'r').read()
except IndexError:
  data = raw_input("hex!> ")
except Exception as e:
  print "Error occurred:",e

if data == ".":
  hex = raw_input()
  print int(hex, 16)
else:
  print "parsing error"
于 2010-05-18T13:16:46.147 回答
1

相当可读的 C 解决方案(9 个“真实”行):

#include <stdio.h>
int getNextHexDigit() {
    int v;
    while((v = fgetc(stdin)) < '0' && v != -1) {    /* Until non-whitespace or EOF */
    }
    return v > '9' ? 9 + (v & 0x0F) : v - '0';      /* Extract number from hex digit (ASCII) */
}
int main() {
    int v;
    fputc(v = (getNextHexDigit() << 4) | getNextHexDigit(), stdout);
    return v > 0 ? main(0) : 0;
}

要支持 16 位 little endian,请替换main为:

int main() {
    int v, q;
    v = (getNextHexDigit() << 4) | getNextHexDigit();
    fputc(q = (getNextHexDigit() << 4) | getNextHexDigit(), stdout);
    fputc(v, stdout);
    return (v | q) > 0 ? main(0) : 0;
}
于 2009-04-27T20:24:40.513 回答
1

一个 31 个字符的 Perl 解决方案:

s/\W//g,print(pack'H*',$_)for<>

于 2009-05-20T23:49:49.853 回答
0

我无法在脑海中对此进行编码,但是对于每两个字符,输出 (byte)((AsciiValueChar1-(AsciiValueChar1>64?48:55)*16)+(AsciiValueChar1-(AsciiValueChar1>64?48: 55))) 将十六进制字符串更改为原始二进制文件。如果您的输入字符串不是 0 到 9 或 A 到 B 以外的任何值,这将非常糟糕,所以我不能说它对您有多大用处。

于 2009-04-27T20:02:53.140 回答
0

我知道 Jon 已经发布了一个(更干净的)LINQ 解决方案。但是这一次,我能够使用 LINQ 语句在执行期间修改字符串并滥用 LINQ 的延迟评估,而不会被我的同事大喊大叫。:p

string hex = "FFA042";
byte[] bytes =
    hex.ToCharArray()
       .Select(c => ('0' <= c && c <= '9') ? 
                         c - '0' :
                         10 + (('a' <= c) ? c - 'a' : c - 'A'))
       .Select(c => (hex = hex.Remove(0, 1)).Length > 0 ? (new int[] {
           c,
           hex.ToCharArray()
                 .Select(c2 => ('0' <= c2 && c2 <= '9') ?
                                    c2 - '0' :
                                    10 + (('a' <= c2) ? c2 - 'a' : c2 - 'A'))
                 .FirstOrDefault() }) : ( new int[] { c } ) )
       .Where(c => (hex.Length % 2) == 1)
       .Select(ca => ((byte)((ca[0] << 4) + ca[1]))).ToArray();

1 条语句格式化为便于阅读。

更新

支持空格和不均匀小数位数(89A 等于 08 9A)

byte[] bytes =
    hex.ToCharArray()
       .Where(c => c != ' ')
       .Reverse()
       .Select(c => (char)(c2 | 32) % 39 - 9)
       .Select(c => 
           (hex =
                new string('0', 
                           (2 + (hex.Replace(" ", "").Length % 2)) *
                                hex.Replace(" ", "")[0].CompareTo('0')
                                                       .CompareTo(0)) +
                hex.Replace(" ", "").Remove(hex.Replace(" ", "").Length - 1))
              .Length > 0 ? (new int[] {
                        hex.ToCharArray()
                           .Reverse()
                           .Select(c2 => (char)(c2 | 32) % 39 - 9)
                           .FirstOrDefault(), c }) : new int[] { 0, c } )
                     .Where(c => (hex.Length % 2) == 1)
                     .Select(ca => ((byte)((ca[0] << 4) + ca[1])))
                     .Reverse().ToArray();

还是一种说法。可以通过在开始时在十六进制字符串上运行 replace(" ", "") 来缩短时间,但这将是第二个语句。

这个有两个有趣的点。如何在没有源字符串本身以外的外部变量帮助的情况下跟踪字符数。在解决这个问题时,我遇到了 char y.CompareTo(x) 只返回“y - x”而 int y.CompareTo(x) 返回 -1、0 或 1 的事实。所以 char y.CompareTo(x).CompareTo(0 ) 等于返回 -1、0 或 1 的 char 比较。

于 2009-04-27T21:26:06.123 回答
0

PHP , 28 个符号:

<?=pack(I,hexdec($argv[1]));
于 2009-07-16T11:32:03.217 回答
0

游戏迟到了,但这里有一些 Python{2,3} 单行代码(100 个字符,需要import sys, re):

sys.stdout.write(''.join([chr(int(x,16)) for x in re.findall(r'[A-Fa-f0-9]{2}', sys.stdin.read())]))
于 2011-09-25T12:41:21.597 回答