John Smith
John Smith

Reputation: 897

How would you create a modification of an existing array of a composity type with map() in Julia?

My code:

details = """'Swift', '2014', 'compiled'; 'Objective-C', '1984', 'compiled'; 'Scala', '2004', 'compiled'"""

println("Reding the following data will make it easier for you to create an array: $details ")

println("How many programming languages would you like to include in your array?")

max_languages = parse(Int16, readline())

struct Language
    name::String
    appeared::Int16
    implementation::String
    end


languages = Vector{Language}(undef, max_languages)


for i in 1:max_languages 
 println("Name of the language?")
 name = readline()
 println("In what year did the language first appear?")
 appeared = parse(Int16, readline())
 println("What is the implementation, is it compiled, JIT, or interpreted?")
 implementation = readline()
 languages[i] = Language(name, appeared, implementation)
 end
 
println(languages)

function calc_age(array)
 today::Int16 = 2020
 [ array.age : today - array.appeared ]
 end
 
languagesWithAge = map(calc_age, languages)

println(languagesWithAge)

The goal of my code is to create an array of elements of a composite type(Language). In the array called languages one of the fields is appeared(this field is meant to represent the year that a programming language first appeared). In the second array I want this field to be replaced by the field named age (which is supposed to be computed by 2020 - appeared). This job I thought should be done with map(). Once it is done I want to store the second array in a variable called languagesWithAge.

Just to get the age I could do this:

println("How many programming languages would you like to include in your array?")

max_languages = parse(Int16, readline())

struct Language
    name::String
    appeared::Int16
    implementation::String
    age::Int16
    end

Language(name, appeared, implementation) = Language(name, appeared, implementation, 2020 - appeared)

languages = Vector{Language}(undef, max_languages)

languages[1] = Language("Julia", 2012, "jit")  
languages[2] = Language("Python", 1990, "interpreted")  

println(languages)

But my objective is to keep the original array and create a new one using map(). I would like to introduce the data, that is the names of the languages, the years of their first appearance, and their implementation only once. Let us imagine that we have the details of a thousand languages to enter manually - can be quite tedious. That's why I want this to be done only once. That is another reason why I thoughtI about using map() - I want to avoid this step:

languages[2] = Language("Python", 1990, "interpreted")  

or this:

for i in 1:max_languages 
 println("Name of the language?")
 name = readline()
 println("In what year did the language first appear?")
 appeared = parse(Int16, readline())
 println("What is the implementation, is it compiled, JIT, or interpreted?")
 implementation = readline()
 languages[i] = Language(name, appeared, implementation)
 end

Originally, I wanted languages to be a tuple, but being a newbie, I yet don't know how to create such a tuple.

When I ran the code, after successfully creating languages array, I get the following from Julia:

ERROR: LoadError: type Language has no field age
Stacktrace:
 [1] getproperty(::Any, ::Symbol) at ./sysimg.jl:18
 [2] calc_age(::Language) at /home/jerzy/ComputerScience/SoftwareDevelopment/MySoftware/MyJulia/age.jl:37
 [3] iterate at ./generator.jl:47 [inlined]
 [4] _collect at ./array.jl:632 [inlined]
 [5] collect_similar(::Array{Language,1}, ::Base.Generator{Array{Language,1},typeof(calc_age)}) at ./array.jl:561
 [6] map(::Function, ::Array{Language,1}) at ./abstractarray.jl:1987
 [7] top-level scope at none:0
 [8] include at ./boot.jl:317 [inlined]
 [9] include_relative(::Module, ::String) at ./loading.jl:1044
 [10] include(::Module, ::String) at ./sysimg.jl:29
 [11] exec_options(::Base.JLOptions) at ./client.jl:266
 [12] _start() at ./client.jl:425
in expression starting at /home/jerzy/ComputerScience/SoftwareDevelopment/MySoftware/MyJulia/age.jl:40

Line 40 that Julia's message refers to is:

languagesWithAge = map(calc_age, languages)

It seems clear to me that I cannot achieve my objective without at leats defining another struct. Would creating multidimensional arrays instead of one-dimensional arrays be an alternative? How would you do this?

Upvotes: 1

Views: 105

Answers (1)

Nils Gudat
Nils Gudat

Reputation: 13800

First of all here's why you're seeing the error:

function calc_age(array)
 today::Int16 = 2020
 [ array.age : today - array.appeared ]
 end

Your function takes an argument array and then tries to access the field age of that argument. The problem is that when you do map(calc_age, languages), the function is called for each element of the array, not for the array itself (despite you calling the argument array in the function definition). So what is happening is that the first element of your language array gets passed into calc_age, the function then tries to access languages[1].age, i.e. the field age of the first element in languages, but since this is a Language struct as defined in your code it doesn't actually have that field. That is, you're trying to set the value for a field that is not part of your struct definition.

Here's a somewhat reduced example of what you're trying to achieve I think:

julia> struct Language
           name::String
           appeared::Int
           implementation::String
       end

julia> languages = [Language("Julia", 2012, "Julia and C"),
                    Language("Python", 1983, "C")]
2-element Array{Language,1}:
 Language("Julia", 2012, "Julia and C")
 Language("Python", 1983, "C")

# Note that we define the function as operating on a single language
julia> calc_age(language; base_year = 2020) = base_year - language.appeared
calc_age (generic function with 1 method)

# Now we can use broadcasting (dot syntax) to apply it to an array
julia> calc_age.(languages)
2-element Array{Int64,1}:
  8
 37

One thing you didn't specify is where you want the calculated age to go? Age here is sufficiently simple that one possibility would be to add it as information to the Language struct itself by calculating it automatically upon construction:

julia> struct Language2
    name::String
    appeared::Int
    implementation::String
    age::Int
end

julia> Language2(name, appeared, implementation) = Language2(name, appeared, implementation, 
                                                      2020 - appeared)
Language2

julia> Language2("Julia", 2012, "Julia and C")  
Language2("Julia", 2012, "Julia and C", 8)   

Upvotes: 3

Related Questions