英文:
how to manipulate very long string to avoid out of memory with golang
问题
我正在尝试提高个人技能,解决HackerRank挑战:
有一个由小写英文字母组成的字符串s,该字符串被无限重复。给定一个整数n,找到并打印出无限字符串的前n个字母中字母'a'的数量。
1<=s<=100 && 1<=n<=10^12
我天真地认为以下代码是可以的:
fs := strings.Repeat(s, int(n)) // 完整字符串
ss := fs[:n] // 子字符串
fmt.Println(strings.Count(ss, "a"))
显然,我耗尽了内存并得到了一个"out of memory"的错误。
我从未遇到过这种问题,对如何处理它一无所知。
如何操作非常长的字符串以避免"out of memory"错误?
英文:
I trying for personal skills improvement to solve the hacker rank challenge:
There is a string, s, of lowercase English letters that is repeated infinitely many times. Given an integer, n, find and print the number of letter a's in the first n letters of the infinite string.
1<=s<=100 && 1<=n<=10^12
Very naively I though this code will be fine:
fs := strings.Repeat(s, int(n)) // full string
ss := fs[:n] // sub string
fmt.Println(strings.Count(ss, "a"))
Obviously I explode the memory and got an: "out of memory".
I never faced this kind of issue, and I'm clueless on how to handle it.
How can I manipulate very long string to avoid out of memory ?
答案1
得分: 2
我希望这可以帮到你,你不需要通过遍历字符串来进行实际计数。
那是一种朴素的方法。你需要使用一些基本的算术来得到答案,而不会耗尽内存,希望注释有所帮助。
var answer int64
// 首先确定字符串 s 中有多少个字符"a"。
aCount := int64(strings.Count(s, "a"))
// 如果 s 必须达到长度 n,它将完整重复多少次
repeats := n / int64(len(s))
remainder := n % int64(len(s))
// 如果 n/len(s) 不能完全整除,意味着必须有一个余数,检查是否是这种情况。
// 如果 s 的长度为 5,n 的值为 22,那么 s 的前两个字符将多重复一次。
if remainder > 0{
aCountInRemainder := strings.Count(s[:remainder], "a")
answer = int64((aCount * repeats) + int64(aCountInRemainder))
} else{
answer = int64((aCount * repeats))
}
return answer
可能还有其他方法,但这是我想到的方法。
英文:
I hope this helps, you don't have to actually count by running through the string.
That is the naive approach. You need to use some basic arithmetic to get the answer without running out of memory, I hope the comments help.
var answer int64
// 1st figure out how many a's are present in s.
aCount := int64(strings.Count(s, "a"))
// How many times will s repeat in its entirety if it had to be of length n
repeats := n / int64(len(s))
remainder := n % int64(len(s))
// If n/len(s) is not perfectly divisible, it means there has to be a remainder, check if that's the case.
// If s is of length 5 and the value of n = 22, then the first 2 characters of s would repeat an extra time.
if remainder > 0{
aCountInRemainder := strings.Count(s[:remainder], "a")
answer = int64((aCount * repeats) + int64(aCountInRemainder))
} else{
answer = int64((aCount * repeats))
}
return answer
There might be other methods but this is what came to my mind.
答案2
得分: 1
正如你所发现的,如果实际生成字符串,你最终会在内存中拥有一个巨大的内存块。
表示“一系列传入字节”的一种常见方法是将其实现为io.Reader
(可以将其视为字节流),然后让代码运行r.Read(buff)
循环。
根据你提到的练习的具体情况(一个重复出现的固定字符串n
次),特定字母的出现次数也可以直接从s
中该字母的出现次数计算出来,再加上一些其他操作(我会让你自己弄清楚需要进行哪些乘法和计数)。
如何实现一个重复字符串的Reader,而不需要分配10^12倍的字符串内存?
请注意,在实现.Read()
方法时,调用者已经分配了自己的缓冲区。你不需要在内存中重复字符串,只需要用正确的值填充缓冲区,例如通过将数据逐字节复制到缓冲区中。
以下是一种实现方式:
type RepeatReader struct {
str string
count int
}
func (r *RepeatReader) Read(p []byte) (int, error) {
if r.count == 0 {
return 0, io.EOF
}
// 在每次迭代中,pos将保存到目前为止已复制的字节数
var pos = 0
for r.count > 0 && pos < len(p) {
// 要复制切片,可以使用内置的'copy'方法
// 在每次迭代中,你需要在已经复制的字节之后写入字节,
// 因此使用'p[pos:]'
n := copy(p[pos:], r.str)
// 更新已复制字节的数量
pos += n
// 对于这个第一个示例,计算方式不正确:
// 即使只有部分复制了str,我也会减少一个完整的count
r.count--
}
return pos, nil
}
https://go.dev/play/p/QyFQ-3NzUDV
要完整、正确地实现,你还需要跟踪下次调用.Read()
时需要从哪个偏移量开始:
type RepeatReader struct {
str string
count int
offset int
}
func (r *RepeatReader) Read(p []byte) (int, error) {
if r.count == 0 {
return 0, io.EOF
}
var pos = 0
for r.count > 0 && pos < len(p) {
// 在复制到p时,你应该从r.offset开始:
n := copy(p[pos:], r.str[r.offset:])
pos += n
// 更新r.offset:
r.offset += n
// 如果已经发出了一个完整的str的副本,减少'count'并将'offset'重置为0
if r.offset == len(r.str) {
r.count--
r.offset = 0
}
}
return pos, nil
}
https://go.dev/play/p/YapRuioQcOz
现在你可以在遍历这个Reader时计算出a
的数量。
英文:
As you found out, if you actually generate the string you will end up having that huge memory block in RAM.
One common way to represent a "big sequence of incoming bytes" is to implement it as an io.Reader
(which you can view as a stream of bytes), and have your code run a r.Read(buff)
loop.
Given the specifics of the exercise you mention (a fixed string repeated n
times), the number of occurrence of a specific letter can also be computed straight from the number of occurences of that letter in s
, plus something more (I'll let you figure out what multiplications and counting should be done).
How to implement a Reader that repeats the string without allocating 10^12 times the string ?
Note that, when implementing the .Read()
method, the caller has already allocated his buffer. You don't need to repeat your string in memory, you just need to fill the buffer with the correct values -- for example by copying byte by byte your data into the buffer.
Here is one way to do it :
type RepeatReader struct {
str string
count int
}
func (r *RepeatReader) Read(p []byte) (int, error) {
if r.count == 0 {
return 0, io.EOF
}
// at each iteration, pos will hold the number of bytes copied so far
var pos = 0
for r.count > 0 && pos < len(p) {
// to copy slices over, you can use the built-in 'copy' method
// at each iteration, you need to write bytes *after* the ones you have already copied,
// hence the "p[pos:]"
n := copy(p[pos:], r.str)
// update the amount of copied bytes
pos += n
// bad computation for this first example :
// I decrement one complete count, even if str was only partially copied
r.count--
}
return pos, nil
}
https://go.dev/play/p/QyFQ-3NzUDV
To have a complete, correct implementation, you also need to keep track of the offset you need to start from next time .Read()
is called :
type RepeatReader struct {
str string
count int
offset int
}
func (r *RepeatReader) Read(p []byte) (int, error) {
if r.count == 0 {
return 0, io.EOF
}
var pos = 0
for r.count > 0 && pos < len(p) {
// when copying over to p, you should start at r.offset :
n := copy(p[pos:], r.str[r.offset:])
pos += n
// update r.offset :
r.offset += n
// if one full copy of str has been issued, decrement 'count' and reset 'offset' to 0
if r.offset == len(r.str) {
r.count--
r.offset = 0
}
}
return pos, nil
}
https://go.dev/play/p/YapRuioQcOz
You can now count the a
s while iterating through this Reader.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论