String 朱莉娅:如何得到给定字符串的随机排列?

String 朱莉娅:如何得到给定字符串的随机排列?,string,julia,permutation,String,Julia,Permutation,我想了两种不同的方法,但都很难看 通过拆分将字符串s转换为数组a,然后使用sample(a,length(s),replace=false)和将数组再次合并为字符串 在r中为i获取长度s的随机排列r,并将s[i]的单个s[i]连接起来 正确的方法是什么?不幸的是,没有方法匹配示例(::String,::Int64;replace=false) 也许为String定义shuffle方法会构成类型盗版,但无论如何,这里有一个建议的实现: Base.shuffle(s::String) = isasc

我想了两种不同的方法,但都很难看

  • 通过拆分将字符串
    s
    转换为数组
    a
    ,然后使用
    sample(a,length(s),replace=false)
    将数组再次合并为字符串

  • r
    中为
    i
    获取长度
    s
    随机排列
    r
    ,并将
    s[i]
    的单个
    s[i]
    连接起来


  • 正确的方法是什么?不幸的是,没有方法匹配
    示例(::String,::Int64;replace=false)

    也许为
    String
    定义
    shuffle
    方法会构成类型盗版,但无论如何,这里有一个建议的实现:

    Base.shuffle(s::String) = isascii(s) ? s[randperm(end)] : join(shuffle!(collect(s)))
    

    如果您想从
    shuffle
    中挤出性能,那么您可以考虑:

    function shufflefast(s::String)
        ss = sizeof(s)
        l = length(s)
    
        ss == l && return String(shuffle!(copy(Vector{UInt8}(s))))
    
        v = Vector{Int}(l)
        i = start(s)
        for j in 1:l
            v[j] = i
            i = nextind(s, i)
        end
    
        p = pointer(s)
        u = Vector{UInt8}(ss)
        k = 1
        for i in randperm(l)
            for j in v[i]:(i == l ? ss : v[i+1]-1)
                u[k] = unsafe_load(p, j)
                k += 1
            end
        end
        String(u)
    end
    
    对于大字符串,ASCII比UTF-8快4倍多,UTF-8快3倍多


    不幸的是,它很凌乱,所以我宁愿把它当作一种锻炼。但是,它只使用导出的函数,因此不是黑客。

    受Bogumil Kaminski答案中优化技巧的启发,以下版本的性能几乎相同,但更清晰一些(在我看来),并且使用了第二个实用函数,其本身可能有价值:

    function strranges(s)      # returns the ranges of bytes spanned by chars
        u = Vector{UnitRange{Int64}}()
        sizehint!(u,sizeof(s))
        i = 1
        while i<=sizeof(s)
            ii = nextind(s,i)
            push!(u,i:ii-1)
            i = ii
        end
        return u
    end
    
    function shufflefast(s)
        ss = convert(Vector{UInt8},s)
        uu = Vector{UInt8}(length(ss))
        i = 1
        @inbounds for r in shuffle!(strranges(s))
            for j in r
                uu[i] = ss[j]
                i += 1
            end
        end
        return String(uu)
    end
    

    编辑:性能稍微好一点-参见代码注释

    这是Bogumil Kaminski的回答,我试图避免计算长度(*),如果没有必要:

    function shufflefast2(s::String)
        ss = sizeof(s)
        local l
    
        for l in 1:ss
            #if ((codeunit(s,l) & 0xc0) == 0x80)
            if codeunit(s,l)>= 0x80  # edit (see comments bellow why)
                break
            end
        end
    
        ss == l && return String(shuffle!(copy(Vector{UInt8}(s))))
    
        v = Vector{Int}(ss)
        i = 1
        l = 0
        while i<ss
            l += 1
            v[l] = i
            i = nextind(s, i)
        end
        v[l+1] = ss+1  # edit - we could do this because ss>l
    
        p = pointer(s)
        u = Vector{UInt8}(ss)
        k = 1
        for i in randperm(l)
            # for j in v[i]:(i == l ? ss : v[i+1]-1)
            for j in v[i]:v[i+1]-1  # edit we could do this because v[l+1] is defined (see above)
                u[k] = unsafe_load(p, j)
                k += 1
            end
        end
        String(u)
    end
    
    差异太小,有时bkshufflefast更快。表现必须平等。整个长度必须计算,并且有相同的分配

    unicode字符串的计时示例:

    julia> srand(1234);@btime for i in 1:100 danshufflefast(s) end
      24.964 μs (500 allocations: 42.19 KiB)
    
    julia> srand(1234);@btime for i in 1:100 bkshufflefast(s) end
      20.882 μs (400 allocations: 37.50 KiB)
    
    julia> srand(1234);@btime for i in 1:100 shufflefast2(s) end
      19.038 μs (400 allocations: 40.63 KiB)
    
    shufflefast2在这里稍微快一点,但显然更快。比Bogumil函数的分配多一点,比Dan的解的分配少一点


    (*)-我有点希望Julia中的字符串实现将来会更快,长度也会比现在快得多

    join(collect(s)[randperm(length(s))])
    至少第二种方式与unicode有问题<代码>函数示例(s::String)连接(getindex([i代表s中的i],randperm(长度)))结束;示例(“ďaľšý”)
    似乎也适用于unicode。(但丹的解决方案更好!:)如果你知道这是一个ASCII字符串:
    s[randperm(end)]
    。否则:
    加入(洗牌!(收集))
    。也许这是一个很好的方法:
    s>collect>shuffle!|>加入
    。比什么更快?如果练习,那么您可能不需要运行整个长度:
    all((1:sizeof(s)中i的codeunit(s,i)和0xc0)==0x80))
    而不是
    sizeof(s)==length(s)
    。虽然我不确定这里的生成器-它们在Julia中似乎很慢。我使用
    sizeof(s)=length(s)
    ,因为它们后来在UTF-8部件中使用。如果我使用
    ascii(s)
    ,我将不得不在以后再次计算它们,这样我就避免了运行
    ascii(s)
    。但我同意它会降低ASCII部分的性能一点(与ASCII字符串的性能没有多大差别),而且速度比较是与第一个答案中提出的实现
    Base.shuffle(s::string)
    进行的。我们可能可以分配
    v=Vector{Int}(ss)
    并检查
    nextind是否返回比ss更大的索引以获得一些速度…从这个问题(和答案)中删除可能是更好的
    isascii
    定义为:
    fastisascii(s)=(对于1中的l:sizeof(s)codefunit(s,l)&0xc0==0x80&&return false;end;true)
    。你认为它应该成为公关吗?也许应该改进Julia codegen,使当前的
    isascii
    生成类似的代码。@DanGetz它可能很好!:)
    isascii(s::String)=(对于1中的l:sizeof(s)codefunit(s,l)>=0x80&&return false;end;true)
    也可以工作(更快)。它可能必须专门化,因为可能有不同的字符串实现。字符串具体类型正在使用UTF-8编码,所以这个方法必须很好(我们可能还需要一些测试?)。关于pull请求-您的贡献比我的更大,我对github还很陌生。所以我很高兴你做了这个公关!:)我在fastshuffle2中的“isascii”是受
    @edit Base的启发。它是有效的延续(codeunit(“-”,1))
    ,但它可以根据
    @edit isascii(“”
    )简化(你启发我看:)。。。
    julia> srand(1234);@btime for i in 1:100 danshufflefast("test") end
      19.783 μs (500 allocations: 34.38 KiB)
    
    julia> srand(1234);@btime for i in 1:100 bkshufflefast("test") end
      10.408 μs (300 allocations: 18.75 KiB)
    
    julia> srand(1234);@btime for i in 1:100 shufflefast2("test") end
      10.280 μs (300 allocations: 18.75 KiB)
    
    julia> srand(1234);@btime for i in 1:100 danshufflefast(s) end
      24.964 μs (500 allocations: 42.19 KiB)
    
    julia> srand(1234);@btime for i in 1:100 bkshufflefast(s) end
      20.882 μs (400 allocations: 37.50 KiB)
    
    julia> srand(1234);@btime for i in 1:100 shufflefast2(s) end
      19.038 μs (400 allocations: 40.63 KiB)