I'm trying to write some Erlang that would filter an array in the form:
[{dakota, "cold and snowy"}, {california, "perfect weather"}] % and so on
Here is what I've got - I get a syntax error when I try to make a .beam from werl.
-module(matcher).
-export([findkeywords/2]).
findkeywords(Word, Arr) ->
IsMatch = fun({Key, Desc}) ->
lists:any(fun(X) -> X==Word end, string:tokens(Desc, " ")),
lists:filter(IsMatch, [{K, V} || {K, V} <- Arr]).
Can anyone spot where my syntax is off?
I saw your call to arms on twitter and just had to come take a look. :D
If you want this to compile, you're just missing an end on your fun on line 6. Add it in and it compiles without complaint.
-module(matcher).
-export([findkeywords/2]).
findkeywords(Word, Arr) ->
IsMatch = fun({Key, Desc}) ->
lists:any(fun(X) -> X==Word end, string:tokens(Desc, " ")) end, % ADD THE END HERE
lists:filter(IsMatch, [{K, V} || {K, V} <- Arr]).
You can clean this up a bit too, unless this is an exercise in string matching for yourself. The string module has str(String, SubString) -> Index and rstr(String, SubString) -> Index that are described as such in the Erlang Manual:
Returns the position where the first/last occurrence of SubString begins in String. 0 is returned if SubString does not exist in String. For example:
> string:str(" Hello Hello World World ", "Hello World").
8
Using this tidies it up a bit, and you could even shorten the whole thing into a one liner. The list comprehension is unnecessary as the data is already in the format that you're trying to feed it in.
-module(matcher).
-export([findkeywords/2]).
findkeywords(Word, Arr) ->
lists:filter(fun({_Key, Desc}) -> string:str(Desc, Word) > 0 end, Arr).
You miss one "end" from the two functions. Also, it looks like the list comprehension in this example used is not needed.
findkeywords(Word, Arr) ->
IsMatch =
fun({_, Desc}) -> lists:any(fun(X) -> X == Word end, string:tokens(Desc, " ")) end,
lists:filter(IsMatch, [{K, V} || {K, V} <- Arr]).
You are missing the end key word for one of the funs. However, looks like you are searching within strings. This is normally what is use
-define(DATA,[{dakota, "cold and snowy"}, {california, "perfect weather"}]).
string_contains(Big,Small)-> string:rstr(Big,Small) > 0.
findkeywords(Word)-> [X || X <- ?DATA,string_contains(element(2,X),Word) == true].
Anyway, one of your funs was not ended well. that's all.
Related
-module(solarSystem).
-export([process_csv/1, is_numeric/1, parseALine/2, parse/1, expandT/1, expandT/2,
parseNames/1]).
parseALine(false, T) ->
T;
parseALine(true, T) ->
T.
parse([Name, Colour, Distance, Angle, AngleVelocity, Radius, "1" | T]) ->
T;%Where T is a list of names of other objects in the solar system
parse([Name, Colour, Distance, Angle, AngleVelocity, Radius | T]) ->
T.
parseNames([H | T]) ->
H.
expandT(T) ->
T.
expandT([], Sep) ->
[];
expandT([H | T], Sep) ->
T.
% https://rosettacode.org/wiki/Determine_if_a_string_is_numeric#Erlang
is_numeric(L) ->
S = trim(L, ""),
Float = (catch erlang:list_to_float(S)),
Int = (catch erlang:list_to_integer(S)),
is_number(Float) orelse is_number(Int).
trim(A) ->
A.
trim([], A) ->
A;
trim([32 | T], A) ->
trim(T, A);
trim([H | T], A) ->
trim(T, A ++ [H]).
process_csv(L) ->
X = parse(L),
expandT(X).
The problem is that it will calls process_csv/1 function in my module in a main, L will be a file like this:
[["name "," col"," dist"," a"," angv"," r "," ..."],["apollo11 ","white"," 0.1"," 0"," 77760"," 0.15"]]
Or like this:
["planets ","earth","venus "]
Or like this:
["a","b"]
I need to display it as follows:
apollo11 =["white", 0.1, 0, 77760, 0.15,[]];
Planets =[earth,venus]
a,b
[[59],[97],[44],[98]]
My problem is that no matter how I make changes, it can only show a part, and there are no symbols. The list cannot be divided, so I can't find a way.
In addition, because Erlang is a niche programming language, I can't even find examples online.
So, can anyone help me? Thank you, very much.
In addition, I am restricted from using recursion.
I think the first problem is that it is hard to link what you are trying to achieve with what your code says thus far. Therefore, this feedback maybe is not exactly what you are looking for, but might give some ideas. Let's structure the problem into the common elements: (1) input, (2) process, and (3) output.
Input
You mentioned that L will be a file, but I assume it is a line in a file, where each line can be one of the 3 (three) samples. In this regard, the samples also do not have consistent pattern.For this, we can build a function to convert each line of the file into Erlang term and pass the result to the next step.
Process
The question also do not mention the specific logic in parsing/processing the input. You also seem to care about the data type so we will convert and display the result accordingly. Erlang as a functional language will naturally be handling list, so on most cases we will need to use functions on lists module
Output
You didn't specifically mention where you want to display the result (an output file, screen/erlang shell, etc), so let's assume you just want to display it in the standard output/erlang shell.
Sample file content test1.txt (please note the dot at the end of each line)
[["name "," col"," dist"," a"," angv"," r "],["apollo11 ","white","0.1"," 0"," 77760"," 0.15"]].
["planets ","earth","venus "].
["a","b"].
Howto run: solarSystem:process_file("/Users/macbook/Documents/test1.txt").
Sample Result:
(dev01#Macbooks-MacBook-Pro-3)3> solarSystem:process_file("/Users/macbook/Documents/test1.txt").
apollo11 = ["white",0.1,0,77760,0.15]
planets = ["earth","venus"]
a = ["b"]
Done processing 3 line(s)
ok
Module code:
-module(solarSystem).
-export([process_file/1]).
-export([process_line/2]).
-export([format_item/1]).
%%This is the main function, input is file full path
%%Howto call: solarSystem:process_file("file_full_path").
process_file(Filename) ->
%%Use file:consult to convert the file content into erlang terms
%%File content is a dot (".") separated line
{StatusOpen, Result} = file:consult(Filename),
case StatusOpen of
ok ->
%%Result is a list and therefore each element must be handled using lists function
Ctr = lists:foldl(fun process_line/2, 0, Result),
io:format("Done processing ~p line(s) ~n", [Ctr]);
_ -> %%This is for the case where file not available
io:format("Error converting file ~p due to '~p' ~n", [Filename, Result])
end.
process_line(Term, CtrIn) ->
%%Assume there are few possibilities of element. There are so many ways to process the data as long as the input pattern is clear.
%%We basically need to identify all possibilities and handle them accordingly.
%%Of course there are smarter (dynamic) ways to handle them, but below may give you some ideas.
case Term of
%%1. This is to handle this pattern -> [["name "," col"," dist"," a"," angv"," r "],["apollo11 ","white"," 0.1"," 0"," 77760"," 0.15"]]
[[_, _, _, _, _, _], [Name | OtherParams]] ->
%%At this point, Name = "apollo11", OtherParamsList = ["white"," 0.1"," 0"," 77760"," 0.15"]
OtherParamsFmt = lists:map(fun format_item/1, OtherParams),
%%Display the result to standard output
io:format("~s = ~p ~n", [string:trim(Name), OtherParamsFmt]);
%%2. This is to handle this pattern -> ["planets ","earth","venus "]
[Name | OtherParams] ->
%%At this point, Name = "planets ", OtherParamsList = ["earth","venus "]
OtherParamsFmt = lists:map(fun format_item/1, OtherParams),
%%Display the result to standard output
io:format("~s = ~p ~n", [string:trim(Name), OtherParamsFmt]);
%%3. Other cases
_ ->
%%Display the warning to standard output
io:format("Unknown pattern ~p ~n", [Term])
end,
CtrIn + 1.
%%This is to format the string accordingly
format_item(Str) ->
StrTrim = string:trim(Str), %%first, trim it
format_as_needed(StrTrim).
format_as_needed(Str) ->
Float = (catch erlang:list_to_float(Str)),
case Float of
{'EXIT', _} -> %%It is not a float -> check if it is an integer
Int = (catch erlang:list_to_integer(Str)),
case Int of
{'EXIT', _} -> %%It is not an integer -> return as is (string)
Str;
_ -> %%It is an int
Int
end;
_ -> %%It is a float
Float
end.
I'm iterating through a list of strings, and I want to return the contents of a string if the beginning of it matches the provided string.
e.g.
strings = [ "GITHUB:github.com", "STACKOVERFLOW:stackoverflow.com" ]
IO.puts fn(strings, "GITHUB") // => "github.com"
This is what I thinking so far:
def get_tag_value([ << tag_name, ": ", tag_value::binary >> | rest ], tag_name), do: tag_value
def get_tag_value([ _ | rest], tag_name), do: get_tag_value(rest, tag_name)
def get_tag_value([], tag_name), do: ""
But I get this:
** (CompileError) lib/file.ex:31: a binary field without size is only allowed at the end of a binary pattern and never allowed in binary generators
Which makes sense, but then I'm not quite sure how to go about doing this. How would I match a substring to a different variable provided as an argument?
Here's how I'd do this making most use of pattern matching and no call to String.starts_with? or String.split:
defmodule A do
def find(strings, string) do
size = byte_size(string)
Enum.find_value strings, fn
<<^string::binary-size(size), ":", rest::binary>> -> rest
_ -> nil
end
end
end
strings = ["GITHUB:github.com", "STACKOVERFLOW:stackoverflow.com"]
IO.inspect A.find(strings, "GITHUB")
IO.inspect A.find(strings, "STACKOVERFLOW")
IO.inspect A.find(strings, "GIT")
IO.inspect A.find(strings, "FOO")
Output:
"github.com"
"stackoverflow.com"
nil
nil
There are many ways to skin this cat.
For example:
def get_tag_value(tag, strings) do
strings
|> Enum.find("", &String.starts_with?(&1, tag <> ":"))
|> String.split(":", parts: 2)
|> Enum.at(1, "")
end
or if you still wanted to explicitly use recursion:
def get_tag_value(_tag, []), do: ""
def get_tag_value(tag, [str | rest]) do
if String.starts_with?(str, tag <> ":") do
String.split(str, ":", parts: 2) |> Enum.at(1, "")
else
get_tag_value(tag, rest)
end
end
Are just two of many possible ways.
However, you won't be able to pattern match the string in the function head without knowing it (or at least the length) beforehand.
iex(1)> strings = [ "GITHUB:github.com", "STACKOVERFLOW:stackoverflow.com" ]
iex(2)> Enum.filter(strings, fn(s) -> String.starts_with?(s, "GITHUB") end)
iex(3)> |> Enum.map(fn(s) -> [_, part_2] = String.split(s, ":"); part_2 end)
# => ["github.com"]
In Enum.filter/2 I select all strings they start with "GITHUB" and I get a new List. Enum.map/2 iterates through the new List and splits each string at the colon to return the second part only. Result is a List with all parts after the colon, where the original string starts with "GITHUB".
Be aware, that If there's an item like "GITHUBgithub.com" without colon, you get a MatchError. To avoid this either use String.starts_with?(s, "GITHUB:") to filter the right strings or avoid the pattern matching like I did in Enum.map/2 or use pattern matching for an empty list like #ryanwinchester did it.
You can use a combination of Enum.map and Enum.filter to get the matching pairs you're looking for:
def get_tag_value(tag_name, tags) do
tags
|> Enum.map(&String.split(&1, ":")) # Creates a list of [tag_name, tag_value] elements
|> Enum.filter(fn([tn, tv]) -> tn == tag_name end) # Filters for the tag name you're after
|> List.last # Potentially gets you the pair [tag_name, tag_value] OR empty list
end
And in the end you can either call List.last/1 again to either get an empty list (no match found) or the tag value.
Alternatively you can use a case statement to return a different kind of result, like a :nomatch atom:
def get_tag_value(tag_name, tags) do
matches = tags
|> Enum.map(&String.split(&1, ":")) # Creates a list of [tag_name, tag_value] elements
|> Enum.filter(fn([tn, tv]) -> tn == tag_name end) # Filters for the tag name you're after
|> List.last # Potentially gets you the pair [tag_name, tag_value] OR empty list
case matches do
[] -> :nomatch
[_, tag_value] -> tag_value
end
end
This would be my take in Erlang:
get_tag_value(Tag, Strings) ->
L = size(Tag),
[First | _] = [Val || <<Tag:L/binary, $:, Val/binary>> <- Strings]
First.
The same in Elixir (there are probably more idiomatic ways of writing it, tho):
def gtv(tag, strings) do
l = :erlang.size(tag)
[first | _ ] =
for << t :: binary - size(l), ":", value :: binary >> <- strings,
t == tag,
do: value
first
end
I have the following functions:
search(DirName, Word) ->
NumberedFiles = list_numbered_files(DirName),
Words = make_filter_mapper(Word),
Index = mapreduce(NumberedFiles, Words, fun remove_duplicates/3),
dict:find(Word, Index).
list_numbered_files(DirName) ->
{ok, Files} = file:list_dir(DirName),
FullFiles = [ filename:join(DirName, File) || File <- Files ],
Indices = lists:seq(1, length(Files)),
lists:zip(Indices, FullFiles). % {Index, FileName} tuples
make_filter_mapper(MatchWord) ->
fun (_Index, FileName, Emit) ->
{ok, [Words]} = file:consult(FileName), %% <---- Line 20
lists:foreach(fun (Word) ->
case MatchWord == Word of
true -> Emit(Word, FileName);
false -> false
end
end, Words)
end.
remove_duplicates(Word, FileNames, Emit) ->
UniqueFiles = sets:to_list(sets:from_list(FileNames)),
lists:foreach(fun (FileName) -> Emit(Word, FileName) end, UniqueFiles).
However, when i call search(Path_to_Dir, Word) I get:
Error in process <0.185.0> with exit value:
{{badmatch,{error,{1,erl_parse,["syntax error before: ","wordinfile"]}}},
[{test,'-make_filter_mapper/1-fun-1-',4,[{file,"test.erl"},{line,20}]}]}
And I do not understand why. Any ideas?
The Words variable will match to content of the list, which might not be only one tuple, but many of them. Try to match {ok, Words} instead of {ok, [Words]}.
Beside the fact that the function file:consult/1 may return a list of several elements so you should replace {ok,[Words]} (expecting a list of one element = Words) by {ok,Words}, it actually returns a syntax error meaning that in the file you are reading, there is a syntax error.
Remember that the file should contain only valid erlang terms, each of them terminated by a dot. The most common error is to forget a dot or replace it by a comma.
I am having Data like the below:
Data = [{<<"status">>,<<"success">>},
{<<"META">>,
{struct,[{<<"createdat">>,1406895903.0},
{<<"user_email">>,<<"gopikrishnajonnada#gmail.com">>},
{<<"campaign">>,<<"5IVUPHE42HP1NEYvKb7qSvpX2Cm">>}]}},
{<<"mode">>,1}]
And Now i am having a
FieldList = ['<<"5IVUPHE42HP1NEYvKb7qSvpX2Cm">>']
Now:
I am trying like the below but i am getting empty instead of the value
90> [L || L <- FieldList,proplists:get_value(<<"campaign">>,element(2,proplists:get_value(<<"META">>,Data,{[],[]}))) == L].
[]
so how to get the both values are equal and get the final value.
You can parse the atom as if it were an Erlang term:
atom_to_binary(Atom) ->
L = atom_to_list(Atom),
{ok, Tokens, _} = erl_scan:string(L ++ "."),
{ok, Result} = erl_parse:parse_term(Tokens),
Result.
You can then do
[L ||
L <- FieldList,
proplists:get_value(<<"campaign">>,
element(2,
proplists:get_value(<<"META">>,Data,{[],[]})))
== atom_to_binary(L)
].
You can also do it the other way round, (trying to) convert the binary to an atom using this function:
binary_literal_to_atom(Binary) ->
Literal = lists:flatten(io_lib:format("~p", [Binary])),
try
list_to_existing_atom(Literal)
catch
error:badarg -> undefined
end.
This function will return undefined if the atom is not known yet (s. Erlang: binary_to_atom filling up atom table space security issue for more information on this). This is fine here, since the match can only work if the atom was known before, in this case by being defined in the FieldList variable.
How did you get those values in the first place?
Data = [{<<"status">>,<<"success">>},
{<<"META">>,
{struct,[{<<"createdat">>,1406895903.0},
{<<"user_email">>,<<"gopikrishnajonnada#gmail.com">>},
{<<"campaign">>,<<"5IVUPHE42HP1NEYvKb7qSvpX2Cm">>}]
}
},
{<<"mode">>,1}].
[_,{_,{struct,InData}}|_] = Data.
[X || {<<"campaign">>,X} <- InData].
it gives you the result in the form : [<<"5IVUPHE42HP1NEYvKb7qSvpX2Cm">>]
of course you can use the same kind of code if the tuple {struct,InData} may be in a different place in the Data variable.
-module(wy).
-compile(export_all).
main() ->
Data = [{<<"status">>,<<"success">>},
{<<"META">>,
{struct,[{<<"createdat">>,1406895903.0},
{<<"user_email">>,<<"gopikrishnajonnada#gmail.com">>},
{<<"campaign">>,<<"5IVUPHE42HP1NEYvKb7qSvpX2Cm">>}]
}
},
{<<"mode">>,1}],
Fun = fun({<<"META">>, {struct, InData}}, Acc) ->
Value = proplists:get_value(<<"campaign">>, InData, []),
[Value | Acc];
(_Other, Acc)->
Acc
end,
lists:foldl(Fun, [], Data).
I think you can use this code.
Working with Erlang's case, I'm facing a problem. The problem is the following:
other languages:
switch(A)
{
case "A" : case "B" :
//do something
break;
}
So, how to achieve the same thing using Erlang? Because sometimes it is very important to put conditions like these, to avoid overhead.
May be guards are what you want.
the_answer_is(N) when A == "A"; A == "B";
; - is OR
, - is AND
You can use case expressions in Erlang. The syntax is:
case Expression of
Pattern1 [when Guard1] -> Expr_seq1;
Pattern2 [when Guard2] -> Expr_seq2;
...
end
To quote Pragmatic Erlang:
case is evaluated as follows. First,
Expression is evaluated; assume this
evaluates to Value. Thereafter, Value
is matched in turn against Pattern1
(with the optional guard Guard1),
Pattern2, and so on, until a match is
found. As soon as a match is found,
then the corresponding expression
sequence is evaluated—the result of
evaluating the expression sequence is
the value of the case expression. If
none of the patterns match, then an
exception is raised.
An example:
filter(P, [H|T]) ->
case P(H) of
true -> [H|filter(P, T)];
false -> filter(P, T)
end;
filter(P, []) ->
[].
filter(P , L); returns a list of all those elements X in L for which P(X) is true. This can be written using pattern matching, but the case construct makes the code cleaner. Note that choosing between pattern matching and case expressions is a matter of taste, style and experience.
Not my favorite style, but you can do something like:
case A of
_ when A == "A";
A == "B" -> do_ab();
_ when A == "C";
_ when A == "D" -> do_cd();
_ -> do_default()
end.