【问题标题】:Most efficient way of compressing sparse graph in ASCII?用ASCII压缩稀疏图的最有效方法?
【发布时间】:2016-11-08 17:44:13
【问题描述】:

我正在开发处理稀疏矩阵的软件。它们并不大(从 15x15 到 ~300x300)。我希望能够将矩阵的表示形式存储在一个短字符串中,以便我可以将它作为一个值存储在 CSV 文件中(以及许多其他内容)。

到目前为止,我尝试将矩阵视为二进制字符串,convert to base62:

import numpy as np
import networkx as nx

def graphToHash(a,numnodes):
  def baseN(num,b,numerals="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"):
      return ((num == 0) and numerals[0]) or (baseN(num // b, b, numerals).lstrip(numerals[0]) + numerals[num % b])
  return str(numnodes) + '!' + baseN(int(''.join([str(i) for i in flatten_list(a)]),2), 62)

def flatten_list(l):
    l1=[item for sublist in l if isinstance(sublist,list) or isinstance(sublist,np.ndarray) for item in sublist]
    l=l1+[item for item in l if not isinstance(item,list) and not isinstance(item,np.ndarray)]
    return l

# example
import sys
sys.setrecursionlimit(10000)
a=np.array(nx.to_numpy_matrix(nx.connected_watts_strogatz_graph(160,8,.3,1000))).astype(int)
hash=graphToHash(a,160)
len(hash) # ~4300 characters

这适用于小图(30 个节点约为 150 个字符)。但是较大的图有点笨拙(160 个节点约为 4300,需要我增加递归限制)。

因为图是二元和稀疏的,我知道我可以做得更好。理想情况下,我想继续使用 {0-9,a-z,A-Z} 字符串,因为我知道这些不会在我的 CSV 文件中造成任何问题。

压缩二元稀疏矩阵最有效的方法是什么?

【问题讨论】:

  • 它实际上工作正常,谢谢。不过仍在寻找更好的方法。
  • 我可以,但它不会使字符串更短。我读过提高递归限制是不好的,但我不知道为什么,所以这不是我主要关心的问题。
  • 你能不能把cpickle.dumps对象变成一个字符串,然后zlib.compress,然后编码为base62?
  • 如果您要限制自己使用字母数字,您不妨采用可读性和易于编码/解码的方式,并且只使用十进制 0-9。如果你真的想要压缩和可靠性,你应该放弃这些限制
  • 好的,我在另一条 4-5% 的评论中找到了我正在寻找的部分内容。其他问题是 a) 对角线全为零,即它是一个简单的图形吗? b) 矩阵是对称的,即如果是图,它是有向的还是无向的?您能否在问题中包含示例 160x160、4-5% 填充矩阵?

标签: python graph compression sparse-matrix


【解决方案1】:

使用sparse6 格式怎么样?它使用可打印的 ASCII 字符。 http://users.cecs.anu.edu.au/~bdm/data/formats.txt

import networkx as nx
G = nx.connected_watts_strogatz_graph(160,4,.3,1000)
s = nx.generate_sparse6(G)
print(len(s))
print(s)

505
>>sparse6<<:~?A__O??K@?SA?[B__D_kE?{F@CH`KI@[J`_L`gM`{NACOaGQ`?QA[R_oIAcTAsUA{VBCWBKXBSHBOZbW[ac\BsSBo^cO_CKacOb_?bCcccgedGfd?hdSiD[jDcldsmdwo_GTE?peGqe[rEcsEktb_^E{vcGwfGyfg{d_{FkAFg}_ObFs~gKgFH@GS\DhAG\BglDGtEG{AG|GhSmHPJhXKhkuHlNiO?CPOIKMILQI\RIdSIlTItUI|VJDMJ@XjHYbwjJPZcxZ_WPc`\Js`FX^_`__`CKL`k\bKc\IXcKldKsbDpfk|WLLhdPeLPjl[nHHllhmf`fLpnl{lMLCMLqM[?Eprm`tmtuM|vNDwNLxNTyN\rN[mKH{i@|n{~LP~OCeDI?oUA_YBOeBOcMOiEosjOyGpAHghZPUIh@sNYKeIKhqMpiNcwzPyO`QOQMS??~QQR`iRql{QsXQqVpqVjhmREXRSyR\NNqZRc?@a[Rk@Jq]

【讨论】:

  • networkx 已将 nx.generate_sparse6 移至 nx.readwrite.sparse6.to_sparse6_bytes。我认为这发生在 networkx 2.0 更新期间的某个时间
  • 提交删除 generate_sparse6 github.com/networkx/networkx/commit/…
【解决方案2】:

经过我们在 cmets 的长时间讨论,我记得这是一个二进制数组... derp run length encoding:

def brle(decoded): #binary run length encoding
    run = 0
    encoded = []
    for i in decoded:
        if i:
            encoded.append(run)
            run = 0
        else:
            run += 1
    return encoded

def brld(encoded): #binary run length decoding
    decoded = np.zeros(sum(encoded)+len(encoded)+1) #random trickery to get original length of flat list
    pos = 0
    for run in encoded:
        pos += run
        decoded[pos] = 1
        pos += 1
    return decoded

没有任何字母数字编码...

a=np.array(nx.to_numpy_matrix(nx.connected_watts_strogatz_graph(160,4,.3,1000))).astype(int)
b = flatten_list(a)
encoded = brle(b)

len(';'.join([str(x) for x in encoded])) # ==1706 chars

c = brld(encoded)
assert(all(b==c)) # passes

采用 utf-8 编码:

s = ''.join(unichr(x).encode('utf-8') for x in encoded) #711 bytes in memory
assert(encoded == [ord(x) for x in s.decode('utf-8')]) # passes

【讨论】:

  • 太棒了 :) 不确定 utf-8 编码在没有分隔符的情况下是否可逆,但没关系——仍然是一个很大的改进
  • 每个 utf-8 字符都是一个数字。您不需要分隔符...我将解码添加到我的示例中
  • 单个 utf-8 字符可以在 range(2**21) 中有一个序数(最多 4 个字节)存在 5 和 6 字节字符的规定,但我不确定 python 是否实现了这一点(最初保留为了将来的兼容)
  • 我使用了一些类似的技术来更好地压缩我之前写的代码高尔夫答案。codegolf.stackexchange.com/a/92814/42652
  • 啊,好点子。无论如何,我可能会坚持使用 base62... UTF-8 似乎不能很好地存储在 CSV 中(即读入 R 或 Excel 时出现问题)。还是有很大的进步。
【解决方案3】:

由于预计该图是稀疏的,因此我将对其基于邻接列表的表示进行编码。像这样的东西(请注意,我重用了您的 baseN() 版本,但我会用迭代版本替换它):

#!/usr/bin/env python3

def baseN(num,b,numerals="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"):
      return ((num == 0) and numerals[0]) or (baseN(num // b, b, numerals).lstrip(numerals[0]) + numerals[num % b])

def encode_graph(g):
    # the leading 'a' is needed to protect the leading zero (if any)
    s = 'a' + 'a'.join(['a'.join(map(str,x)) for x in g])
    n = int(s, 11)
    return baseN(n, 62)

print(encode_graph([(0,1), (1,5), (1,23), (5,23)])) # outputs 64wc3BssnTd

【讨论】:

  • 谢谢,但不幸的是,这比我现有的解决方案稍差。它可能适用于更稀疏的图形。我尝试的图表是 160x160 和 ~4-5% 填充
猜你喜欢
  • 2015-12-05
  • 2013-11-09
  • 1970-01-01
  • 2017-12-27
  • 1970-01-01
  • 1970-01-01
  • 2023-01-07
  • 2013-09-29
  • 1970-01-01
相关资源
最近更新 更多