Fun One Liners

Here’s my (non-expert) understanding:

This would arguably be unexpected behavior if the code was identity(a), but Kristoffer used identity.(a), which says apply the identity function to each element of a and recollect that into a new array. When it creates a new array, the broadcast machinery is supposed to choose the narrowest appropriate type, so this should be expected behavior.

However, julia gives not guarantees that inference will run exactly so future minor releases could make this less reliable, but that is unlikely since identity is quite easy to infer :wink:

There is nothing in that code that semantically depend on inference. That would be very bad (for the reason you mentioned).

1 Like

Right, so if I run a = [var1, var2, var3], I’m not actually very clear on what algorithm julia uses to decide on the appropriate eltype of a. In particular, it seems that there are cases where the compiler would have been free to choose a different eltype than the one it settled on. For instance,

julia> a = [1, 2.0, 3, big"4.56", true]
5-element Array{BigFloat,1}:
 1.0
 2.0
 3.0
 4.560000000000000000000000000000000000000000000000000000000000000000000000000033
 1.0

julia> a = [1, 2.0, 3, big"4.56", true, "hi"]
6-element Array{Any,1}:
    1
    2.0
    3
    4.560000000000000000000000000000000000000000000000000000000000000000000000000033
 true
     "hi"

In one of those, it cast all the elements to BigFloat and in the other it just set the eltype to Any. It seems to me that it could have chosen Union{Real, String} as the eltype in the second example. Not sure if that’s inference related or something else.

I don’t think we make guarantees around what the eltype will be in the second example, right?

It accumulates into an array of the narrowest element type and when it encounters an element with another type, it widens the type, creates a new array, copies over all elements and keep going so the resulting element type should be:

foldl((x,y)-> Base.promote_typejoin(x, y), typeof.(a))

I am not sure it is documented what exact type you get out so it might be true that there is no guarantee. My point is that it is not relying on inference.

2 Likes

If you enjoy this sort of thing and know a bit of Python, you might find this talk I saw at PyCon 2016 about the “onelinerizer” amusing: Chelsea Voss - Oneliner-izer: An Exercise in Constrained Coding - PyCon 2016 - YouTube .

3 Likes

Number of symbols in base with an underscore

count(x -> occursin("_", x), string.(names(Base)))

Check if parentheses in a string are balanced

check(s) = mapreduce(x->(x=='(')-(x==')'), (x,y)-> x<0 ? -1 : x+y, s)==0

Apply f to the elements of v and concatenate the result.

mapconcat(f, v) = [z for x in v for z in f(x)]

List of sublists of l

powerset(l) = foldl((y,x) -> append!(y,vcat.(y,x)), l, init=[[]])

Some binary tree

gentree(n) = n == 0 ? 0 : [[gentree(n-1)],[gentree(n-1)]]

Flatten a nested list

flatit(A) = mapreduce(x -> isa(x,Array) ? flatit(x) : x, vcat, A)

Moving julia. Not sure where it comes from.

for i=1:100 println(" "^floor(Int,30*(1+sin(.2*i)))*"julia"); sleep(.1);end

Search for the element in it with max f value, with the associated value

argmax(f, it) = maximum(zip(f.(it), it))

UTF-8 trigonometry

sin(π/60) ≈ √2*(√5-1)*(3*√3+3)/48 - (3*√3-3)*√(5+√5)/24

The exponential function

my_exp(x,n=10) = foldr((k,r) -> 1+x/k*r, 1:n, init = 1)

Math formula

println.(join(n^2:n^2+n, "+") *"=" * join(n^2+n+1:n^2+2n, "+") for n =1:5);

Pythagorean triples

[(a,b,c) for a = 1:100 for b = a:100 for c = b:100 if a^2+b^2 == c^2]

The Blancmange curve

using Winston 
fplot(x -> sum(1/2^k*abs(2^k*x-round(2^k*x)) for k = 0:20), [0, 1]) 

The Pascal triangle

using LinearAlgebra
pascal(n) = round.(Int, exp(diagm(-1 => 1:n)))

The Fibonacci sequence

fib(n) = n>1 ? fib(n-1)+fib(n-2) : 1

Absolute frequencies in the game of Yahtzee

using DataStructures, Base.Iterators
counter(sort(collect(values(counter(x)))) for x in product(fill(1:6,5)...))

Solution to first Euler problem

sum(filter(x -> x%3 == 0 || x%5 == 0, 1:999))

Three years of Sundays

using Primes 
@time factor(big(2)^67-1)

Edit: added some links. And the mandatory tests:

using Test

test_set() =
   @testset "oneliner" begin
     @test check("(1*(2+3))") == true
     @test check("(1)*(2+3))") == false
     @test mapconcat(sum, [[1,3],[0,1]])  == [4,1]
     @test powerset([1,2]) == [[], [1], [2], [1, 2]]
     @test gentree(2) == [[[[0], [0]]], [[[0], [0]]]]
     @test length(flatit(gentree(4))) == 16
     @test argmax(x -> x^2, -9:10) == (100, 10)
     @test pascal(5)[3+1,2+1] == 3
     @test fib(4) == 5
     @test sum(filter(x -> x%3 == 0 || x%5 == 0, 1:999)) == 233168
     @test my_exp(1) ≈ exp(1)
     @test prod(first, factor(big(2)^67-1).pe) == big(2)^67-1
   end
14 Likes

Some of these would be very useful in a tutorial about higher order functions that has been discussed. Would it be OK with you if they were used for that purpose?

1 Like

Here’s some alternatives for fun! But less fancy I suppose :frowning:

count(@. occursin("_", string($names(Base))))
Even sum would work.

mapconcat(f, v) = vcat([f(u) for u in v]...)

pascal(n) = [binomial(i,j) for i in 0:n-1, j in 0:n-1]

fib(n) = ([1 1; 1 0]^n)[1,1]

2 Likes

Sure. Note that a few of them are not very efficient (check, flatit).

2 Likes

Your use of the @. is nifty. And we can shorten mapconcat a bit

   mapconcat(f, v) = vcat(f.(v)...)
1 Like

What’s the difference compared to just f.(v)?

1 Like

It concatenates the result.

 julia> show(mapconcat(n -> 1:n, [2,3]) )
 [1, 2, 1, 2, 3]

 julia> show(mapconcat(collect, ["yes", "no"]))
 ['y', 'e', 's', 'n', 'o']

In contrast,

julia> show((n -> 1:n).([2,3]))
UnitRange{Int64}[1:2, 1:3]

julia> show(collect.(["yes", "no"]))
Array{Char,1}[['y', 'e', 's'], ['n', 'o'] ]
1 Like

I was going to say that then you should avoid splatting and use mapreduce instead, because splatting is slow, while reduce(vcat) is highly optimized.

But I was completely surprised:

mapconcat(f, v) = vcat(f.(v)...) # this should be sloooooow
mapcat(f, v) = mapreduce(f, vcat, v)  # this should be fast

s = [randstring(10) for _ in 1:1000];
@btime mapconcat(collect, $s);
  71.097 μs (1006 allocations: 180.05 KiB)

@btime mapcat(collect, $s);
  4.536 ms (2590 allocations: 19.33 MiB)

In fact:

R = rand(10_000);
@btime [($R)...];
  200.872 μs (10003 allocations: 312.70 KiB)

@btime reduce(vcat, $R);
  19.792 ms (200294 allocations: 32.42 MiB)

I thought splatting was super slow, and reduce(vcat) was supposed to be super fast.

1 Like

The function reduce uses a pairwise reduction algorithm that is not particularly suitable for the task at hand. You may use foldl instead.

s = [Random.randstring(10) for _ in 1:1000]
mapconcat1(f, v) = vcat(f.(v)...)
mapconcat2(f, v) = mapreduce(f, vcat, v)
mapconcat3(f, v) = mapfoldl(f, append!, v, init = [])
mapconcat4(f, v) = [z for x in v for z in f(x)]

@btime mapconcat1(collect, s);
81.295 μs (1006 allocations: 180.05 KiB)

@btime mapconcat2(collect, s);
2.071 ms (2590 allocations: 19.33 MiB)

@btime mapconcat3(collect, s);
145.627 μs (1014 allocations: 285.58 KiB)

@btime mapconcat4(collect, s);
160.690 μs (3018 allocations: 300.63 KiB)

You can see that mapfoldl and the array comprehension give similar timings. The dotted version allocates less as expected and thus is twice as fast.

One problem with the splatting approach is that it creates and compiles a new method of vcat for every length on input vector. This can pile of if you use it for many different lengths. I’m not sure how much of a problem it is in practice, though.

2 Likes

Sum elementwise array of vectors, e.g.: x = [[1, 2, 3], [4, 5, 6]] should produce [5, 7, 9]
Answer: (.+)(x...)

2 Likes

This is just sum(x)

4 Likes

You are correct! That was over engineering on my side :slight_smile:

2 Likes

no such thing as over engineering a fun one liner. Cool solution!

1 Like

“One liners”

to_linear_ind(x, lim) = (i=0; [i = i*lim[j]+x[j]-1 for j in reverse(1:length(lim))]; i+1)

to_cartesian_ind(I, lim) = [((I, r) = fldmod1(I, l); r) for l in lim]
2 Likes