benlauwens / thinkjulia.jl Goto Github PK
View Code? Open in Web Editor NEWPort of the book Think Python to the Julia programming language
License: Other
Port of the book Think Python to the Julia programming language
License: Other
I tried adding the ThinkJulia package into a completely clean environment on a Mac mini M1 running Ventura 13.0.1;
Complete error message:
ERROR: Error building GR
:
tar: Error opening archive: Failed to open 'downloads/gr-0.48.0-Darwin-aarch64.tar.gz'
[ Info: Downloading pre-compiled GR 0.48.0 Darwin binary
[ Info: Using insecure connection
[ Info: Cannot download GR run-time
ERROR: LoadError: failed process: Process(tar xzf downloads/gr-0.48.0-Darwin-aarch64.tar.gz
, ProcessExited(1)) [1]
In the Section titled "Why Julia" in the Preface, I noticed a small grammatical mistake.
In the last paragraph, it is written that-
It is the responsibility of the programmer to optimize the code that forms a bottleneck, but this can done in Julia itself.
Focus is on- [...] this can done in Julia itself..
I think a be is missing here. And the corrected sentence fragment would be-
**[...] this can be done in Julia itself. **
Making the whole sentence-
It is the responsibility of the programmer to optimize the code that forms a bottleneck, but this can be done in Julia itself.
The section on statements in chapter two really seems designed around Python (specifically Python 2). In Julia, every statement is an expression and has a value (including all kinds of blocks), so there is no meaningful distinction between the two. It's true that some expressions are used primarily for their side effects, like assignment, but the value is also sometimes used, e.g.
result = if (foo = expensive_function()) > 1
calculate_something(foo)
else
calculate_something_else(foo)
end
We use the value of an assignment expression in a test, and we assign the value of the conditional expression to a variable.
It's true that some Julia expressions are used more for their side effects than others. The value of a loop block is always nothing
, as far as I've observed, but they still have expression semantics. The meaningful distinction in Julia is whether an expression is being used for side effects.
Anyway, I think the current form of the section on statements doesn't map very well to Julia's semantics, which are more like Lisp than Python.
Also, the book constantly refers to "print statements". Julia has no print statement (and neither does Python 3...), it has only a print function and a println function.
Should I start on rewrites?
From chapter 1
The equation 3+ =3 is illegal because even though + and = are legal tokens, you canβt have one right after the other.
Actually, the equation is wrong in Julia because of the whitespace, not because of the order of the tokens (besides it is an invalid assignment anyway).
The following commands are both valid
a =+ 3
a += 3
Maybe have another example for syntax like
a = ) 1
All tokens are valid, but the ")" is unexpected
In the section "String Operations", first
cannot be assigned.
julia> first = "throat"
ERROR: cannot assign variable Base.first from module Main
Stacktrace:
[1] top-level scope at none:0
In the section "Nested Conditionals", there is some text that doesn't render correctly, see (((indentation)
.
This snippet does not work, I had to add .git suffix to make it succeed:
(v1.6) pkg> add https://github.com/BenLauwens/ThinkJulia.jl
(v1.6) pkg> add https://github.com/BenLauwens/ThinkJulia.jl.git
I just started looking at the ThinkJulia book. It looks really nice--thanks for putting this together!
Just a couple things I noticed:
more@ = 100000
but instead type = "Advanced Theoretical Zymurgy"
is written twice.julia version 1.8
(@v1.8) pkg> add https://github.com/BenLauwens/ThinkJulia.jl
Updating git-repo `https://github.com/BenLauwens/ThinkJulia.jl`
Resolving package versions...
ERROR: Unsatisfiable requirements detected for package ImageMagick [6218d12a]:
I am currently using the online version of Think Julia -- thanks for providing this great resource.
I am brand new to Julia and have been working my way through the book, and I noticed that at the beginning of Chapter 4, it explains "Packages can be installed in the REPL by entering the Pkg REPL-mode using the key ]
." However, I was unable to find anything about how to exit Pkg REPL-mode. (Perhaps it is in the book, but I couldn't find it.)
I was stuck in Pkg REPL-mode for an embarrassing amount of time before I found here that it is as simple as pressing Backspace or CTRL+ C on an empty line.
Given that you explain how to enter Pkg REPL-mode, I thought it would be useful to briefly explain how to exit Pkg REPL-mode as well.
Hello. It looks like the following link on line 357 of chapter 12 should be a "programmer-defined" instead of "Base"? Found by @chunggnuhc.
ThinkJulia.jl/book/src/chap12.asciidoc
Line 357 in 33d130e
We haven't tested, because we haven't built the PDF yet.
global
is necessary for count
in for
loop.julia> word = "banana"
"banana"
julia> count = 0
0
julia> for letter in word
if letter == 'a'
count = count + 1
end
end
ERROR: UndefVarError: count not defined
Stacktrace:
[1] top-level scope at ./REPL[20]:3 [inlined]
[2] top-level scope at ./none:0
julia> println(count)
0
julia> word = "banana"
"banana"
julia> count = 0
0
julia> for letter in word
if letter == 'a'
global count = count + 1
end
end
julia> println(count)
3
islower
is deprecated. It should be islowercase
.Section 4, just in the beginning.
Hello,
I am currently learning Julia with your book (Thank you! π ), and came across this example In the Data Encapsulation Subsection, and am a little bit confused with the usage of an empty pushfirst!
in the processword
method.
struct Markov
order :: Int64
suffixes :: Dict{String, Array{String, 1}} #β
prefix :: Array{String, 1}
function Markov(order::Int64=2)
new(order, Dict{String, Array{String, 1}}(), Array{String, 1}())
end
end
function processword(markov::Markov, word::String)
if length(markov.prefix) < markov.order
push!(markov.prefix, word)
return
end
get!(markov.suffixes, (markov.prefix...,), Array{String, 1}()) #β
push!(markov.suffixes[(markov.prefix...,)], word) #β
pushfirst!(markov.prefix) #β
push!(markov.prefix, word)
end
I suppose the pushfirst!
should instead be popfirst!
as otherwise the prefix
Vector{}
would keep growing beyond the length determined by the order
variable ?
In the get!
and push!
to markov.suffixes
the keys being pushed/getted are of type Tuple{String,...}
, but the type of key as declared in the struct markov
is of type String
, which is only a valid Type
for cases where the order == 1
, for all other cases it should instead be of Type
:Tuple{String,Vararg{String}}
as we already see in the example method.
This is what happens when i try to run the example as is, the prefix
bloats up and I get an appropriate MethodError
for Type
:Tuple{String, Vararg{String}}
m = Markov()
[Out]:
Markov(2, Dict{String,Array{String,1}}(), String[])
for x in ["the", "man", "was", "as", "the", "man", "saw", ","]
processword(m, x)
end
[Out]:
MethodError: Cannot `convert` an object of type Tuple{String,String} to an object of type String
Closest candidates are:
convert(::Type{T<:AbstractString}, !Matched::T<:AbstractString) where T<:AbstractString at strings/basic.jl:207
convert(::Type{T<:AbstractString}, !Matched::AbstractString) where T<:AbstractString at strings/basic.jl:208
convert(::Type{T}, !Matched::T) where T at essentials.jl:154
Stacktrace:
[1] get!(::Function, ::Dict{String,Array{String,1}}, ::Tuple{String,String}) at ./dict.jl:440
[2] get!(::Dict{String,Array{String,1}}, ::Tuple{String,String}, ::Array{String,1}) at ./dict.jl:421
[3] processword(::Markov, ::String) at ./In[1]:15
[4] top-level scope at ./In[3]:2
struct Markov
order :: Int64
suffixes :: Dict{Tuple{String,Vararg{String}}, Array{String, 1}} #β
prefix :: Array{String, 1}
function Markov(order::Int64=2)
new(order, Dict{Tuple{String,Vararg{String}}, Array{String, 1}}(), Array{String, 1}()) #β
end
end
function processword(markov::Markov, word::String)
if length(markov.prefix) < markov.order
push!(markov.prefix, word)
return
end
get!(markov.suffixes, (markov.prefix...,), Array{String, 1}()) #β
push!(markov.suffixes[(markov.prefix...,)], word) #β
popfirst!(markov.prefix) #β
push!(markov.prefix, word)
end
m = Markov()
for x in ["the", "man", "was", "as", "the", "man", "saw", ","]
processword(m, x)
end
m.suffixes
[Out]:
Dict{Tuple{String,Vararg{String,N} where N},Array{String,1}} with 5 entries:
("man", "was") => ["as"]
("as", "the") => ["man"]
("was", "as") => ["the"]
("man", "saw") => [","]
("the", "man") => ["was", "saw"]
m.prefixes
[Out]
2-element Array{String,1}:
"saw"
","
In Chapter 16, the Modifiers section, you have the following code:
function increment!(time, seconds)
time.second += seconds
if time.second >= 60
time.second -= 60
time.minute += 1
end
if time.minute >= 60
time.minute -= 60
time.hour += 1
end
end
This won't work because MyTime
is previously defined as an immutable struct.
It happened when running the following example code:
function polygon(t, n, len)
angle = 360 / n
for i in 1:n
forward(t, len)
turn(t, -angle)
end
end
function circle(t, r)
circumference = 2 * Ο * r
n = 50
len = circumference/n
polygon(t, n, len)
end
circle(5, 5)
Since v1.4, 'add https://github.com/BenLauwens/ThinkJulia.jl' does not work.
According to this issue, REQUIRE
should be replace to Project.toml
.
Having just wrestled with the global
vs local
issue in these for
loops, my opinion is that the Julia Docs and tutorials
In my opinion your great book (really!) would give another service to the Julia community if you did this....
Thank you!
I am using Julia 1.1 in Ubuntu 18.04 and was doing the Exercise 14-1 of the Modules Section in chapter 14.
Executing the code:
include("wc.jl")
using LineCount
linecount("wc.jl")
gives the error:
ArgumentError: Package LineCount not found in current path:
- Run `import Pkg; Pkg.add("LineCount")` to install the LineCount package.
Stacktrace:
[1] require(::Module, ::Symbol) at ./loading.jl:823
[2] top-level scope at In[1]:2
In https://stackoverflow.com/questions/37200025/how-to-import-custom-module-in-julia says that in >0.7 you should use:
include("wc.jl")
using .LineCount
linecount("wc.jl")
that runs with no problem.
Thanks for your work in this great book,
Daniel Palma
Docstrings are often triple-quoted strings, also known as multiline strings because the triple quotes allow the string to span more than one line.
However,
"aaaa
bbbb"
is a completely valid string with a line break.
This is the definition of MyTime
as an immutable struct in your online version
"""
Represents the time of day.
fields: hour, minute, second
"""
struct MyTime
hour
minute
second
end
However, you modified the fields of tsum
in the addtime
function.
function addtime(t1, t2)
tsum = MyTime(t1.hour + t2.hour, t1.minute + t2.minute, t1.second + t2.second)
if tsum.second >= 60
tsum.second -= 60
tsum.minute += 1
end
if tsum.minute >= 60
tsum.minute -= 60
tsum.hour += 1
end
tsum
end
I the section called Debugging in the first chapter, it is mentioned that mistakes in a program are called bugs- for whimsical reasons.
In this sentence specifically-
Programmers make mistakes. For whimsical reasons, programming errors are called bugs and the process of tracking them down is called debugging.
But this is not very accurate.
Before transistors, ICs, and finally, fabricated circuit boards took over in computers, vacuum tubes used to be used in making computers.
The nomenclature of bugs was publicized by Admiral Grace Hopper. This is mentioned in the Wikipedia page-
The term "bug" was used in an account by computer pioneer Grace Hopper, who publicized the cause of a malfunction in an early electromechanical computer. A typical version of the story is:
In 1946, when Hopper was released from active duty, she joined the Harvard Faculty at the Computation Laboratory where she continued her work on the Mark II and Mark III. Operators traced an error in the Mark II to a moth trapped in a relay, coining the term bug. This bug was carefully removed and taped to the log book. Stemming from the first bug, today we call errors or glitches in a program a bug.
Sources:
Now granted that the term already existed before this incident, it was still publicized beginning from this incident.
Although it would be inaccurate to say that this incident coined the term bug, it might be interesting for people to know.
References and more can be found here: https://en.wikipedia.org/wiki/Software_bug#History
In the section "Assignment Statements", Ο
is assigned but when following along in the REPL, it gives the following error message:
julia> Ο = 3.14
ERROR: cannot assign variable MathConstants.Ο from module Main
Stacktrace:
[1] top-level scope at none:0
I assume this used to be valid before Julia 1.0?
Thank you for this wonderful book ππ
When adding ThinkJulia (with add https://github.com/BenLauwens/ThinkJulia.jl
) the following warning is shown:
β Warning: Package ThinkJulia does not have Libdl in its dependencies:
β - If you have ThinkJulia checked out for development and have
β added Libdl as a dependency but haven't updated your primary
β environment's manifest file, try `Pkg.resolve()`.
β - Otherwise you may need to report an issue with ThinkJulia
β Loading Libdl into ThinkJulia from project dependency, future warnings for ThinkJulia are suppressed.
I used julia version 1.3.1 on Linux.
Hello,
great idea making this book for Julia.
I thinks the legibility of the code blocks would increase a lot with syntax highlighting
:
For the HTML version one could use e.g. Prism
Although their Julia is not up to date...
Or whatever Github uses for syntax highlighting (apparently it's Linuist)
More highlighters: https://github.com/JuliaEditorSupport/3rd-Party-Highlighters
E.g.
function fib(n)
if n == 0
return 0
elseif n == 1
return 1
else
return fib(n-1) + fib(n-2)
end
end
versus:
function fib(n)
if n == 0
return 0
elseif n == 1
return 1
else
return fib(n-1) + fib(n-2)
end
end
Anyway, what do you think about adding syntax highlighting for the code blocks?
in ch 20, under the heading "Mathematics" (below) Exercise 20-1, the following appears wronged type set (LaTeX):
(\mathrm{i}), representing the principal square root of (-1).
Hello, are there any plans to release this book in a PDF or ePub format? Such formats would make it easier for me, at least, to read it, especially when I happen to be offline.
This isn't a problem per se, but I was scanning the first couple chapters and noticed many references to Monty Python, presumably included from Think Python. Python has a culture of using Monty Python references in example code, a gimmick which is kind of cute in that context, but it's a little conspicuous in a book which ostensibly has nothing to do with Python.
I'm not overly bothered by it, and maybe it's not worth bringing up at all, but I'm reading the book now and I could keep some notes about where Monty Python references appear and maybe find some replacement material. Maybe Babbage quotes or other quips from CS luminaries, get some PR's together.
I just thought I'd bring it up. Feel free to close this issue and ignore if this is stupid.
The @time macro is handy--any thought about including a package like BenchmarkTools.jl in the section on Measuring Performance? I assume the focus is on Base but this might be a worthy exception.
In Chapter 10 it is written:
In this example, Julia only created one string object, and both a and b refer to it. But when you create two arrays, you get two objects:
Actually, this is a misunderstanding of the ===
operator. For fully immutable values (for example isbitstype
types), the objects don't really have any identity to worry about and ===
will just compare the literal binary bits of the value. For example if you pass a Bool
or Int64
from one function to another, these will generally be copied but different copies are ===
.
Functionally, the ===
actually does something similar for objects with identity, or those objects containing pointers to other mutable objects - it compares the bit value of the pointers. If they literally are the same bits of data, then there is no distinction that could be observed by the user, and the objects are ===
.
Where the explanation goes wrong is it fails to note that String
is a built-in type that is enforced to be immutable, and ===
compares the contents of two strings not the pointers. (You can observe the difference if you grab the pointers and use unsafe operations to manipulate the underlying RAM). At this level of discussion it might be helpful to focus on really obviously mutable values like Array
. Something like this:
To check whether two variables refer to the same object, you can use the
β‘
(\equiv TAB
) or===
operator.julia> a = [1, 2, 3]; julia> b = b; julia> a β‘ b true
In this example, Julia only created one array object, and both
a
andb
refer to it. But when you create two arrays, you get two objects:julia> a = [1, 2, 3]; julia> b = [1, 2, 3]; julia> a β‘ b false
Exercise 3-3 renders incorrectly as shown in the image below.
This happens in Google Chrome Version 69.0.3497.100 (Official Build) (64-bit).
Running Julia
In the browser, you can run Julia on JuliaBox. No installation is required β just point your browser there, log in, and start computing (see AppendixΒ B).
Lauwens, Ben; Downey, Allen B.. Think Julia . O'Reilly Media. Kindle Edition.
The link in the Kindle version is dead, and I saw a discourse reference dated 2020 that said that it was going away:
"Since JuliaBox sunset will take place on May 31st π’ what are the best options to run Julia in the cloud for academic users? "
Hi everyone, we were translating the book to Portuguese (here) and noticed that JuliaBox is shutting down and that it affects the English book as well. Do you guys have any plans related to this change?
Going through this text has been great so far! Thanks for all of the effort!
After doing ] add https://github.com/BenLauwens/ThinkJulia.jl
then using ThinkJulia
, the call to the Turtle()
constructor produces ERROR: UndefVarError: Turtle not defined
on Julia version 1.1.1.
While attempting to import and compile Luxor in isolation, I get a precompile error. However, I don't see any errors when performing ] add https://github.com/BenLauwens/ThinkJulia.jl
I have installed JuliaPro on Windows 10, but when I add ThinkJulia, an error message relating to LibCairo appears.
The important line from chapter 4 is:
pkg> add https://github.com/BenLauwens/ThinkJulia.jl
I get this long error:
(v1.1) pkg> add https://github.com/BenLauwens/ThinkJulia.jl
Updating git-repo `https://github.com/BenLauwens/ThinkJulia.jl`
ERROR: GitError(Code:ERROR, Class:Config, failed to map '--help')
Stacktrace:
[1] macro expansion at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.1/LibGit2/src/error.jl:101 [inlined]
[2] #checkout_tree#46(::LibGit2.CheckoutOptions, ::Function, ::LibGit2.GitRepo, ::LibGit2.GitTree) at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.1/LibGit2/src/repository.jl:358
[3] #checkout_tree at ./none:0 [inlined]
[4] macro expansion at ./gcutils.jl:87 [inlined]
[5] (::getfield(Pkg.Types, Symbol("##62#63")))(::LibGit2.GitTree) at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.1/Pkg/src/Types.jl:780
[6] with(::getfield(Pkg.Types, Symbol("##62#63")), ::LibGit2.GitTree) at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.1/LibGit2/src/types.jl:1125
[7] #handle_repos_add!#61(::Bool, ::Nothing, ::Function, ::Pkg.Types.Context, ::Array{Pkg.Types.PackageSpec,1}) at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.1/Pkg/src/Types.jl:760
[8] #handle_repos_add! at ./none:0 [inlined]
[9] #add_or_develop#17(::Symbol, ::Bool, ::Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}, ::Function, ::Pkg.Types.Context, ::Array{Pkg.Types.PackageSpec,1}) at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.1/Pkg/src/API.jl:47
[10] (::getfield(Pkg.API, Symbol("#kw##add_or_develop")))(::NamedTuple{(:mode,),Tuple{Symbol}}, ::typeof(Pkg.API.add_or_develop), ::Pkg.Types.Context, ::Array{Pkg.Types.PackageSpec,1}) at ./none:0
[11] do_add!(::Dict{Symbol,Any}, ::Array{Pkg.Types.PackageSpec,1}, ::Dict{Symbol,Any}) at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.1/Pkg/src/REPLMode.jl:672
[12] #invokelatest#1(::Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}, ::Function, ::Any, ::Any, ::Vararg{Any,N} where N) at ./essentials.jl:742
[13] invokelatest(::Any, ::Any, ::Vararg{Any,N} where N) at ./essentials.jl:741
[14] do_cmd!(::Pkg.REPLMode.PkgCommand, ::REPL.LineEditREPL) at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.1/Pkg/src/REPLMode.jl:563
[15] #do_cmd#31(::Bool, ::Function, ::REPL.LineEditREPL, ::String) at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.1/Pkg/src/REPLMode.jl:538
[16] do_cmd at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.1/Pkg/src/REPLMode.jl:534 [inlined]
[17] (::getfield(Pkg.REPLMode, Symbol("##53#56")){REPL.LineEditREPL,REPL.LineEdit.Prompt})(::REPL.LineEdit.MIState, ::Base.GenericIOBuffer{Array{UInt8,1}}, ::Bool) at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.1/Pkg/src/REPLMode.jl:988
[18] #invokelatest#1 at ./essentials.jl:742 [inlined]
[19] invokelatest at ./essentials.jl:741 [inlined]
[20] run_interface(::REPL.Terminals.TextTerminal, ::REPL.LineEdit.ModalInterface, ::REPL.LineEdit.MIState) at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.1/REPL/src/LineEdit.jl:2273
[21] run_frontend(::REPL.LineEditREPL, ::REPL.REPLBackendRef) at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.1/REPL/src/REPL.jl:1035
[22] run_repl(::REPL.AbstractREPL, ::Any) at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.1/REPL/src/REPL.jl:192
[23] (::getfield(Base, Symbol("##734#736")){Bool,Bool,Bool,Bool})(::Module) at ./client.jl:362
[24] #invokelatest#1 at ./essentials.jl:742 [inlined]
[25] invokelatest at ./essentials.jl:741 [inlined]
[26] run_main_repl(::Bool, ::Bool, ::Bool, ::Bool, ::Bool) at ./client.jl:346
[27] exec_options(::Base.JLOptions) at ./client.jl:284
[28] _start() at ./client.jl:436
I am getting this error with Julia 1.1 and Julia 1.0. Yes, this is weird, adn unfortunately I don't understand the error, like "where do I begin?". By the way, I don't expect that you can solve this problem.
So I'm to learn coding for the first time, and I'm starting with Think Julia. I've run into an incredible number of walls, where something very simple is impossible to figure out and makes proceeding any further in the lesson impossible. For example, the chapter with the turtle drawings. I can't figure out how to type in a turtle, which seems to be a requirement, so I'm completely unable to do anything in that chapter.
Anyway, all those problems pale in comparison to what seems to me a colossal flaw in the book: there are no answers to any of the questions. For someone trying to learn this on their own, this is fatal. A bunch of challenging questions are asked, almost none of which I can answer. The typical way I would learn, say, using a math or physics textbook, would be to see worked solutions or at least the final answers, which make it possible to figure out how things work. But there are no answers to any questions in this book, meaning the questions themselves are borderline useless.
Perhaps there are answers available somewhere, but I have been unable to find them.
if the fields are width, height and corner (x,y)
then
function findcenter(rect)
Point(rect.corner.x, rect.corner.y)
end
returns the corner, not the center.
function findcenter(rect)
Point((width - rect.corner.x)/2, (height - rect.corner.y)/2)
end
would return the center, would it not?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
π Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. πππ
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google β€οΈ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.