small medium large xlarge

Dave_gnome_head_isolated_pragsmall
16 Jul 2013, 02:25
Dave Thomas (367 posts)
  • Implement the following Enum functions using no library functions or list comprehensions: all?, each, filter, split, and take

A Possible Solution</summary>

defmodule MyList do

  def all?(list),     do: all?(list, fn x -> !!x end) # !! converts truthy to `true`
  def all?([], _fun), do: true
  def all?([ head | tail ], fun) do
    if fun.(head) do
      all?(tail, fun)
    else
      false
    end
  end

  def each([], _fun), do: []
  def each([ head | tail ], fun) do
    [ fun.(head) | each(tail, fun) ]
  end

  def filter([], _fun), do: []
  def filter([ head | tail ], fun) do
    if fun.(head) do
      [ head, filter(tail, fun) ]
    else
      [ filter(tail, fun) ]
    end
  end

  def split(list, count),      do: _split(list, [], count)
  defp _split([], front, _),   do: [ Enum.reverse(front), [] ]
  defp _split(tail, front, 0), do: [ Enum.reverse(front), tail ]
  defp _split([ head | tail ], front, count)  do
    _split(tail, [head|front], count-1)
  end

  def take(list, n), do: hd(split(list, n))

end


IO.inspect MyList.all?([])                 #=> true
IO.inspect MyList.all?([true, true])       #=> true
IO.inspect MyList.all?([true, false])      #=> false
IO.inspect MyList.all?([4, 5, 6], &1 > 3)  #=> true

MyList.each([1,2,3], IO.puts(&1))          #=> 1/2/3

IO.inspect MyList.split([1,2,3,4,5,6], 3)  #=> [[1, 2, 3], [4, 5, 6]]

IO.inspect MyList.take('pragmatic', 6)     #=> 'pragma'

</details>

Generic-user-small
24 Jul 2013, 19:01
Eugene Dorfman (4 posts)

Hi Dave

Maybe this is done intentionally to check who is doing exercises and checks with the answers :), but still looks like there are couple issues in this solution

Enum.each executes the function for each element in the list and returns :ok. MyList.each instead collects all function results in a list (sort of like Enum.map does) and returns it, thus we get:

iex(102)> Enum.each([1,2,3,4],IO.puts(&1))   
1
2
3
4
:ok
iex(103)> MyList1.each([1,2,3,4],IO.puts(&1))
1
2
3
4
[:ok, :ok, :ok, :ok]

So no need to add the results into the list, this implementation seems to work right:

def each([],fun), do: :ok
def each([h|t],fun) do 
	fun.(h)
	each(t,fun)
end

In filter implementation you are creating nested lists, because there is a coma used where a join operator is needed, and also in the else: case you do not need to nest the call to filter(tail, fun) into []. Here is the test output:

iex(104)> Enum.filter([1,2,3,4],fn x -> rem(x,2)==0 end)
[2, 4]
iex(105)> MyList1.filter([1,2,3,4],fn x -> rem(x,2)==0 end)
[[2, [[4, []]]]]

My implementation looks like this (I was trying to omit using the if else, thus the code is a bit ugly, I guess if else is better to use instead of pattern matching in this case, and also it would allow to not use accumulator):

def filter(list,fun), do: do_filter(list,fun,[])

defp do_filter([],fun,acc), do: Enum.reverse acc

defp do_filter([h|t],fun,acc) do
	acc = add_if?(fun.(h),h,acc)
	do_filter(t,fun,acc)
end

defp add_if?(true,h,acc), do: [h|acc]
defp add_if?(_,_,acc), do: acc

Enum.split produces a tuple as a result, while MyList.split creates a list. Also Enum.split works with negative numbers as well, by cutting it from the right. Here is the solution that handles the negatives:

#Enum.split
def split(list,n), do:  do_split([],list,n)
defp do_split(head,tail=[],n), do: pack(head,tail)
defp do_split(head,tail,0), do: pack(head,tail)
defp do_split(head,tail,n) when n<0 do
	n = n+length(tail)
	if n<0 do 
		n = 0
	end
	do_split(head,tail,n)
end
defp do_split(head,[h|t],n) do 
	head = [h | head]
	do_split(head,t,n-1)
end

defp pack(head,tail), do: {Enum.reverse(head), tail}
Generic-user-small
28 Jul 2013, 23:38
Dave Kennedy (1 post)

Yeah not so sure about the filter implementation provided as mentioned by Eugene it yields nested lists. Mine looks like:

def filter([], _func), do: []
def filter([head | tail], func) do
  if func.(head) do
    [head] ++ filter(tail, func)
  else
    [] ++ filter(tail, func)
  end
end
Generic-user-small
29 Jul 2013, 18:54
Eugene Dorfman (4 posts)

Dave K., while your implementation looks like it will work, it seems to be suboptimal, since your filter function will not be a subject to the tail call optimization, because it is not the last thing executed in the filter function body (instead the concatenation operator Kernel.++ is).

In Elixir, Erlang and I think other functional languages it is super-important to pay attention that your recursive call is the last thing you do in the function body, otherwise - you are risking to get a stack overflow. That’s why the accumulators are needed. I don’t remember if the book mentions this, however there is an excellent screencast with Jose Valim here: https://peepcode.com/products/elixir - where he mentions this tail call optimization and other things.

Also suboptimality comes from using list concatenation on every step. when you do join h | t , you always reuse the tail. Then you do Enum.reverse only once. And this is much cheaper, than creating a new list on every step and copy all items from both concatenated lists into it (this is how Kernel.++/2 seems to work, although I might be wrong, need to check that).

Generic-user-small
30 Aug 2013, 03:44
Arvind Dhiman (2 posts)

My implementation for split


    def split(list, count), do: _split(list,[], count)

    defp _split(_list, acc, 0), do: [Enum.reverse(acc), _list]

    defp _split([], acc, count) when count < 0  do
            [acc, []]
    end
    defp _split([], acc, count), do: [Enum.reverse(acc), []]


    defp _split([head|tail], acc, count) when count > 0 do
            _split(tail, [head|acc], count - 1)
    end

    defp _split(list, acc, count) when count < 0 do
            [head|tail] = Enum.reverse(list)
            result = _split(Enum.reverse(tail), [head|acc], count + 1)
            Enum.reverse(result)
    end

I used following test cases: http://elixir-lang.org/docs/stable/Enum.html#split/2

Generic-user-small
06 Sep 2013, 19:16
Fred Hsu (3 posts)

My version of all? without using if/else:

def all?(list), do: all?(list, fn x -> !!x end)
def all?([], _), do: true
def all?([head|tail], f), do: f.(head) and all?(tail, f)
Nathan-6kites_pragsmall
11 Mar 2014, 22:07
Nathan Feaver (9 posts)

I found some unit tests were really helpful for building these methods. My test for #each doesn’t test that the function actually gets called. Just run this file as a script:

$ elixir my-list_test.exs

# my-list_test.exs
Code.load_file("my-list.exs")
require Integer

ExUnit.start

defmodule MyListTest do
  use ExUnit.Case

  test "all? is true" do
    expected = MyList.all?([1,2,3], &(&1))
    assert expected
  end

  test "all? is false" do
    expected = MyList.all?([1,2,3], &(&1 == 1))
    assert expected == false
  end

  test "each function is invoked" do
    expected = MyList.each([1,2,3], &(&1 == 1))
    assert expected == :ok
  end

  test "filter returns matching elements" do
    expected = MyList.filter([1,2,3], &(Integer.odd?(&1)))
    assert expected == [1, 3]
  end

  test "split cuts the collection in two" do
    expected = MyList.split([1,2,3,4],1)
    assert expected == {[1], [2, 3, 4]}
  end

  test "take the first few items" do
    expected = MyList.take([1,2,3,4],1)
    assert expected == [1]
  end
end
Patrick_pragsmall
04 Aug 2014, 07:57
Patrick Oscity (13 posts)

My approach for split and take:

defmodule MyList
  def split([head | tail], count) when count > 0 do
    {left, right} = split(tail, count-1)
    {[head | left], right}
  end
  def split(list, _count), do: {[], list}

  def take([head | tail], count) when count > 0 do
    [head | take(tail, count-1)]
  end
  def take(_list, _count), do: []
end

If you want take to work with negative indices like Enum.take:

defmodule MyList
  def take(list, count) when count < 0 do
    Enum.reverse take(Enum.reverse(list), -count)
  end
  def take([head | tail], count) do
    [head | take(tail, count-1)]
  end
  def take(_list, _count), do: []
end
Generic-user-small
15 Aug 2014, 01:53
Michael Bishop (2 posts)

Here are my split and take which handle the negative indices in a way I haven’t yet seen.

defmodule MyList do
  def split(list, 0),             do: {[], list}
  def split([], _count),          do: {[], []}
  def split([head | tail], count) when count > 0 do
    {first, rest} = split(tail, count - 1)
    {[head|first], rest}
  end
  def split(list, count) do
    {first, rest, _rest_size}  = _rsplit(list, -count)
    {first, rest}
  end

  defp _rsplit([], _count), do: {[],[], 0}
  defp _rsplit([head | tail], count) do
    {first, rest, rest_size}  = _rsplit(tail, count)
    if (rest_size < count) do
      {first, [head|rest], rest_size + 1}
    else
      {[head|first], rest, rest_size}
    end
  end
  
  def take(_list, 0),    do: []
  def take([], _count), do: []
  def take([head|tail], count) when count > 0 do
    [head|take(tail, count-1)]
  end
  def take(list, count) do
    {first, _rest_size} = _rtake(list, -count)
    first
  end

  defp _rtake([], _count), do: {[], 0}
  defp _rtake([head|tail], count) do
    {rest, rest_size}  = _rtake(tail, count)
    if (rest_size < count) do
      {[head|rest], rest_size + 1}
    else
      {rest, rest_size}
    end
  end
end
9863_pragsmall
05 Dec 2014, 21:15
Suraj Kurapati (12 posts)

I’m posting my solution here, for the sake of discussion, because it’s different from what’s been posted so far.

My split() function projects the negative-count case onto the positive-count case in one fell swoop using max():

  def split(collection, count) when count < 0 do
    split(collection, max(0, length(collection) + count))
  end
  def split(collection, 0), do: { [], collection }
  def split([head|tail], count) do
    { left, right } = split(tail, count - 1)
    { [head|left], right }
  end
  def split([], _count), do: { [], [] }

My take() function simply re-uses my split() function from above:

  def take(collection, count) do
    { left, right } = split(collection, count)
    if count < 0 do
      right
    else
      left
    end
  end
Generic-user-small
13 Dec 2014, 17:52
Kent Worstein (1 post)

I don’t get why this approach to split fails to assign new values to the lists left and right. Am I using the for i <- list syntax correctly?

  def split(list, 0), do: {[], list}
  def split(list, count) when count > 0 do
    { left, right } = { [], [] }
    if count > length(list) - 1 do
      { list, [] }
    else
      for l <- (0 .. count - 1), do: left = left ++ [Enum.at(list, l)]
      for r <- (count .. length(list) - 1), do: right = right ++ [Enum.at(list, r)]
      { left, right }
    end
  end
  def split(list, count) when count < 0 do
    { left, right } = { [], [] }
    if abs(count) > length(list) - 1 do
      { [], list }
    else
      for l <- (0 .. length(list) - 1 + count), do: left = left ++ [Enum.at(list, l)]
      for r <- (length(list) + count .. length(list)), do: right = right ++ [Enum.at(list, r)]
      { left, right }
    end
  end
Generic-user-small
26 Dec 2014, 22:26
Pierre Sugar (56 posts)
defmodule MyEnum do

  # all?
  def all?([], _func), do: true
  def all?([head|tail], func) do
    func.(head) && all?(tail, func)
  end

  # each
  def each([], _func), do: :ok
  def each([head|tail], func) do
    func.(head)
    each(tail, func)
  end

  # filter
  def filter([], _func), do: []
  def filter([head|tail], func) do
    if func.(head) do
      [head|filter(tail, func)]
    else
      filter(tail, func)
    end
  end

  # split
  def split([], _count), do: {[],[]}
  def split(collection, count) 
    when count > length(collection), do: {collection, []}
  def split(collection, count) 
    when abs(count) > length(collection) or count == 0, do: {[], collection}
  def split(collection, count) do
    if count > 0 do
      first = _split(collection, count)
    else
      first = _split(collection, length(collection) + count)
    end
    {first, collection -- first}
  end
  defp _split(_, count) when count == 0, do: []
  defp _split([head|tail], count), do: [head|_split(tail, count-1)]

  # take
  def take(collection, count) 
    when abs(count) >= length(collection), do: collection
  def take(collection, count) when count < 0 do 
    collection -- _take(collection, length(collection)+count)
  end
  def take(collection, count), do: _take(collection, count)
  def _take(collection, count) when count == 0 or collection == [], do: []
  def _take([head|tail], count), do: [head|_take(tail, count-1)]
end
Dj-mangatar_pragsmall
21 May 2015, 21:03
Wirianto Djunaidi (5 posts)

My solution for split and take are in the same line as Suraj’s. I like his use of max(), which can reduce an extra pattern matching conditional method in mine.

defmodule MyList do

  defp type_check(x) when is_nil(x), do: false 
  defp type_check(x), do: x

  def all?(list, func \\ &type_check/1)

  def all?([], _), do: true

  def all?([head | tail], func) do
    func.(head) && MyList.all?(tail, func)
  end

  def each([], _), do: :ok

  def each([head|tail], func) do 
    func.(head)
    MyList.each(tail, func)
  end

  def filter([], _), do: []

  def filter([head | tail], func) do
    if func.(head) do
      [head | MyList.filter(tail, func)]
    else
      MyList.filter(tail, func)
    end
  end

  defp move_item(target, source, count) when count == 0 or length(source) == 0 do
    {target, source}
  end

  defp move_item(target, [head | tail], count) do
    move_item(target ++ [head], tail, count - 1)
  end

  def split([], _), do: {[], []}

  def split(list, count) when count < 0 and length(list) <= abs(count) do
    {[], list}
  end

  def split(list, count) when count < 0 do
    split(list, length(list) + count)
  end

  def split(list, count) do
    move_item([], list, count)
  end
  
  def take([], _) do
    []
  end

  def take(list, count) when count < 0 and length(list) < abs(count) do
    list
  end

  def take(list, count) when count < 0 do
    {_, rhs} = move_item([], list, length(list) + count)
    rhs
  end

  def take(list, count) do
    {lhs, _} = move_item([], list, count)
    lhs
  end

end

Me_pragsmall
10 Apr 2015, 22:50
Vignesh Rajagopalan (2 posts)
defmodule MyEnum do

  # all?
  def all?([], _), do: true
  def all?(list, fun), do: _all?(list, true, fun)
  defp _all?(_, false, _), do: false
  defp _all?([], true, _), do: true
  defp _all?([h|t], _, fun), do: _all?(t, fun.(h), fun)

  # each
  def each([], _), do: :ok
  def each([h|t], fun) do
    fun.(h)
    each(t, fun)
  end

  # filter
  def filter([], _), do: []
  def filter(list, fun), do: _filter(list, [], fun)
  defp _filter([], res, _), do: :lists.reverse res
  defp _filter([h|t], res, fun) do
    if fun.(h) do
      _filter(t, [h | res], fun)
    else
      _filter(t, res, fun)
    end
  end

  # split
  def split([], _), do: {[], []}
  def split(list, 0), do: {[], list}
  def split([h|t], n), do: _split({[h], t}, 1, n)
  defp _split({l1, l2}, cnt, n) when cnt == n or l2 == [], do: {:lists.reverse(l1), l2}
  defp _split({l1, [h|t]}, cnt, n), do: _split({[h|l1], t}, cnt+1, n)

  # take
  def take([], _), do: []
  def take(_, 0), do: []
  def take([h|t], n), do: _take([h], t, 1, n)
  defp _take(res, rem, cnt, n) when cnt == n or rem == [], do: :lists.reverse res
  defp _take(res, [h|t], cnt, n), do: _take([h | res], t, cnt+1, n)
end
Generic-user-small
27 Apr 2015, 02:22
Matt Schreck (2 posts)

all? Is this confusing or what? ~~~elixir def all?(list), do: all?(list, fn x -> !!x end) # !! converts truthy to true ~~~

AND he uses an if statement, even though he only mentions it might be necessary for filter! Anyway, here’s my solution, pretty readable and seems to work without truthy conversions

def all?([], _), do: true
def all?([head| [] ], func), do: func.(head) && true
def all?([head|tail], func), do: func.(head) && all?(tail, func)

each Eugene already mentioned what I was going to say for this, my answer is the same as most:

def each([],_), do: :ok
def each([head|tail], func) do
  func.(head)
  each(tail, func)
end

filter There is some conversation on this in the thread, and some invalid answers. Remember, the instructions said “using no library functions or list comprehensions” so you aren’t supposed to use Enum.reverse/1. I think Dave T. just made a typo in his answer, accidentally putting comma where he meant a pipe. My answer is the same as others:

def filter([], _func), do: []
def filter([head|tail], func) do
  if func.(head) do
    [head|filter(tail, func)]
  else
    filter(tail, func)
  end
end

split This time even Dave used Enum.reverse/1! Am I misunderstanding what “library function” means here? Figuring out the answer for non-negative counts is pretty easy, but getting negative counts without using library functions is very hard. You can see the above all use some function or another.

C2f77d9bcc71d1f9ddabd89a8ddf9615_pragsmall
17 May 2015, 13:24
Rebecca Skinner (6 posts)

I’m glad I wasn’t the only one baffled by all the responses using library functions, when the task specifically says “don’t use any”.

I’m not sure about the use of

def all?(list), do: all?(list, fn x -> !!x end) # !! converts truthy to true 

either, tbh.

I like the optimization of returning false in all? if fun.(head) is false. Didn’t think of that one.

42636_pragsmall
31 Aug 2015, 19:03
Waseem Ahmad (2 posts)

Here is my implementation of split. I have tried to be away from any library functions. It does not handle negative count yet. And it’s not very efficient as well.

def my_split([], _),       do: {[], []}
def my_split(list, 0),     do: {[], list}
def my_split(list, count), do: {left(list, count), right(list, count)}

def left(_list, 0), do: []
def left([], _count), do: []
def left([head | tail], count) do
  [head] ++ left(tail, count - 1)
end
  
def right(list, 0), do: list
def right([], _count), do: []
def right([_head | tail], count) do
  [] ++ right(tail, count - 1)
end
Generic-user-small
19 Nov 2015, 22:32
asymmetric . (4 posts)

My implementation of split uses string concatenation, like so: defp _split([ head | tail ], count, acc), do: _split(tail, count - 1, acc ++ [ head]).

Generic-user-small
15 Dec 2015, 22:59
Michael Johnston (9 posts)

To do split with no library functions, I just implemented count:

  def split([], _), do: {[], []}
  def split(collection, 0), do: {[], collection}
  def split([ head | tail ], count) when count > 0 do
    {left, right} =  split(tail, count-1)
    {[head | left], right}
  end
  def split(collection, count) when count < 0 do
    len = _len(collection)
    pos = len + count
    if pos < 0 do
      split(collection, 0)
    else
      split(collection, pos)
    end
  end
  def _len([]), do: 0
  def _len([ _ | [] ]), do: 1
  def _len([ _ | tail ]), do: 1 + _len(tail)

But, I think the approaches that use an accumulator to build the tail of the list are better because I think they are tail-first.

169065098_pragsmall
24 Dec 2015, 15:09
Bartosz Magryś (4 posts)

Here are my implementations of all?, each and filter. Some of them may seem to be a little complicated, but I was using only function head matching and basic operations.

defmodule Awesome do

  # all?

  def all?(collection, fun \\ fn x -> x end) when is_list(collection) do
    _all?(collection, fun, true)
  end

  defp _all?([], _, match) when match === true, do: true
  defp _all?(_, _, match) when match === false, do: false
  defp _all?([ head | tail ], fun, _match) do
    match_tmp = fun.(head)
    _all?(tail, fun, match_tmp)
  end

  # each

  def each(collection, fun) when is_list(collection) do
    _each(collection, fun)
  end

  defp _each([], _), do: :ok
  defp _each([ head | tail ], fun) do
     fun.(head)
     _each(tail, fun)
  end

  # filter

  def filter(collection, fun) when is_list(collection) do
    _filter(collection, fun, [])
  end

  defp _filter([], _, _), do: []
  defp _filter(collection = [ head | _tail ], fun, list) do
    boolean = fun.(head)
    _filter(collection, fun, list, boolean)
  end
  defp _filter([ head | tail ], fun, list, boolean)
  when boolean === true do
    [ head | _filter(tail, fun, list) ]
  end
  defp _filter([ _head | tail ], fun, list, _boolean) do
    _filter(tail, fun, list)
  end

end
Generic-user-small
30 Dec 2015, 17:56
Cory ODaniel (1 post)

Took a swing, tried to do all functions without conditionals and tail call optimized where applicable.

Tested functions against the examples for each in the elixir docs 1.2-rc1.

I ended up using Enum.reverse for splitting on a negative number… Womp.

defmodule PragProg.Ch10.Exercises.MyList do
  def take(list, 0), do: []
  def take(list, count) when count > 0 do
    {keep, _discard} = split(list, count)
    keep
  end

  def take(list,count) when count < 0 do
    {_discard,keep} = split(list,count)
    keep
  end

  def split(list, count), do: _split(list,count,[])
  defp _split(list, 0, acc), do: {acc, list}
  defp _split([], _count, acc), do: {acc,[]}

  defp _split([head|tail], count, acc) when count > 0 do
    _split(tail, count-1, acc++[head])
  end

  defp _split(list, count, acc) when count < 0 do
    _rsplit(list, count, acc)
  end

  defp _rsplit([], _count, acc), do: {[], acc}
  defp _rsplit(list, 0, acc), do: {list, acc}
  defp _rsplit(list, count, acc) do
    # This sucks?
    [last|rest] = Enum.reverse(list)
    rest = Enum.reverse(rest)
    _rsplit(rest, count+1, [last|acc])
  end

  def filter(list, f, acc \\ [])
  def filter([], _f, acc), do: acc
  def filter([head | tail], f, acc), do: _filter(f.(head), head, tail, f, acc)

  # Wanted to do it without and if statement
  defp _filter(true, prev, list, f, acc), do: filter(list, f, acc++[prev])
  defp _filter(false, _prev, list, f, acc), do: filter(list, f, acc)

  def each([], _), do: :ok
  def each([head|tail], fun) do
    fun.(head)
    each(tail,fun)
  end

  def all?([], _), do: true
  def all?([head|tail], fun) do
    _all?(tail, fun, fun.(head))
  end
  # Wanted to do it without a conditional
  defp _all?(_, _, false), do: false
  defp _all?(list, fun, true) do
    all?(list, fun)
  end
end

Generic-user-small
29 Feb 2016, 02:29
Nathan Hessler (8 posts)

I implemented all functions to the docs so split/2 and take/2 allow for negative numbers as second parameter. Also, I implemented without any if statements and all are tail optimized. And, because I understood the instructions to say no using Enum I implemented my own reverse/1 I’m curious to know if this would be considered idiomatic Elixir.

defmodule MyEnum do
  def all?(list, func \\ &(!!&1))
  def all?(list, func), do: _all?(list, func, true)

  defp _all?(_, _, false),           do: false
  defp _all?([], _, _),              do: true
  defp _all?([head | tail], func, _) do
    _all?(tail, func, !!func.(head))
  end

  def each([], _),              do: :ok
  def each([head | tail], func) do
    func.(head)
    each(tail, func)
  end

  def filter(list, func), do: _filter(MyEnum.reverse(list), func, [], false, nil)

  defp _filter([], _, selected, false, _),               do: selected
  defp _filter([], _, selected, true, val),              do: [val | selected]
  defp _filter([head | tail], func, selected, false, _)  do
    _filter(tail, func, selected, func.(head), head)
  end
  defp _filter([head | tail], func, selected, true, val) do
    _filter(tail, func, [val | selected], func.(head), head)
    end

  def split(list, count) when count >= 0,                do: _split(list, [], count)
  def split(list, count) when length(list) > abs(count), do: _split(list, [], length(list) + count)
  def split(list, _),                                    do: _split(list, [], 0)

  defp _split([], front, _),               do: [MyEnum.reverse(front), []]
  defp _split(list, front, 0),             do: [MyEnum.reverse(front), list]
  defp _split([head | tail], front, count) do
    _split(tail, [head | front], count - 1)
  end

  def take(list, count) when count >= 0, do: split(list, count) |> hd
  def take(list, count),                 do: split(list, count) |> tl |> hd

  def reverse(list), do: _reverse(list, [])

  defp _reverse([], reversed),            do: reversed
  defp _reverse([head | tail], reversed), do: _reverse(tail, [head | reversed])
end
Elixir1_pragsmall
09 Apr 2016, 13:35
Beibut Yerzhanov (7 posts)

my full version of Enum.split function. Works with negative numbers too.


defmodule Split do
  def split(list, count), do: _split(list, count, 0, [])

  def _split(list, count, acc, res) when acc == count do
    {res, list}
  end

  def _split([head | tail], count, acc, res) when acc < count do
       _split(tail, count, acc + 1, res ++ [head])
  end

  def _split([], _count, _acc, res) do
      {res, []}
  end

  def _split(list, count, _acc, _res) when (length(list) + count) < 0 do
    {[], list}
  end

  def _split(list, count, acc, res) when count < 0 do
      _split(list, length(list) + count, acc, res)
  end


end

Elixir1_pragsmall
10 Apr 2016, 09:38
Beibut Yerzhanov (7 posts)

identical to split function, unfortunately not pretty concise, but works anyway, including negative numbers.


defmodule Take do
  def take(list, n), do: _take(list, n, 0, [])

  def _take(_list, n, acc, res) when acc == n, do: res

  def _take([head | tail], n, acc, res) when acc < n do
      _take(tail, n, acc + 1, res ++ [head])
  end

  def _take([], _n, _acc, res), do: res

  def _take(list, n, _acc, _res) when (length(list) + n) < 0, do: list

  def _take(list, n, acc, res) when n < 0 do
      list -- _take(list, length(list) + n, acc, res)
  end
end

Generic-user-small
15 May 2016, 22:49
Michal Zalecki (1 post)

With split inspired by Patrick’s solution

defmodule MyEnum do
  def all?([], _), do: true
  def all?([head|tail], func), do: func.(head) && all?(tail, func)

  def each([], _), do: :ok
  def each([head|tail], func) do
    func.(head)
    each(tail, func)
  end

  def filter([], _), do: []
  def filter([head|tail], func) do
    if func.(head) do
      [head | filter(tail, func)]
    else
      filter(tail, func)
    end
  end

  def split(list, 0), do: {[], list}
  def split([head|tail], count) do
    {left, right} = split(tail, count-1)
    {[head|left], right}
  end

  def take(_, 0), do: []
  def take([head|tail], count), do: [head|take(tail, count-1)]

  def flatten([head|tail]), do: flatten(head) ++ flatten(tail)
  def flatten([]), do: []
  def flatten(single), do: [single]
end
Generic-user-small
18 May 2016, 09:57
Arthur Granowski (7 posts)

For ‘split’ extended Patrick’s solution to also take negative indices.

defmodule MyList do
  ###all?
  def all?(list, func), do: _all?(list, func, true)

  defp _all?([], _, predicate_ans), do: predicate_ans
  defp _all?([head | tail], func, _predicate_ans) do
     _all?(tail, func, func.(head))
  end


  ###each
  def each(list, func), do: _each(list, func, :ok)

  defp _each([], _, ans), do: ans
  defp _each([head | tail], func, _ans) do
     _each(tail, func, func.(head))
  end


  ###filter
  def filter([], _func), do: []

  def filter([head | tail], func) do
    if func.(head) do
      [ head | filter(tail, func) ]
    else
      filter(tail, func)
    end
  end


  ###split (negative indices are allowed)
  def split(list, count) when count < 0 do
    {right, left} = split(Enum.reverse(list), -count)
    {Enum.reverse(left), Enum.reverse(right)}
  end

  def split([head | tail], count) when count > 0 do
    {left, right} = split(tail, count-1)
    {[head | left], right}
  end

  #When count is 0
  def split(list, _count), do: {[], list}


  ###take (negative indices are allowed)
  def take(list, count) when count < 0 do
    Enum.reverse take(Enum.reverse(list), -count)
  end

  def take([head | tail], count) when count > 0 do
    [head | take(tail, count-1)]
  end

  #When count is 0
  def take(_list, _count), do: []
end

###---------------------------------------
###Calling the function(s): all?
IO.puts ""
IO.puts "Calling the function(s): all?"
MyList.all?([], &(&1 < 4))  |> IO.puts
#=> true
MyList.all?([1, 2, 3], &(&1 < 4))  |> IO.puts
#=> true
MyList.all?([1, 2, 3, 4, 5], &(&1 < 4))  |> IO.puts
#=> false

IO.puts "And now using the Enum package..."
Enum.all?([], &(&1 < 4)) |> IO.puts
#=> true
Enum.all?([1, 2, 3], &(&1 < 4)) |> IO.puts
#=> true
Enum.all?([1, 2, 3, 4, 5], &(&1 < 4)) |> IO.puts
#=> false


###Calling the function(s): each
IO.puts ""
IO.puts "Calling the function(s): each"
MyList.each([], fn(x) -> IO.puts x end)
#=> ok
MyList.each(["one", "two", "three"], fn(x) -> IO.puts x end)
#=> one
#=> two
#=> three
###Using a func to double each element in the list:
MyList.each([1, 2, 3], &(IO.puts &1 * 2))
#=> 2
#=> 4
#=> 6

IO.puts "And now using the Enum package..."
Enum.each([], fn(x) -> IO.puts x end)
#=> ok
Enum.each(["one", "two", "three"], fn(x) -> IO.puts x end)
#=> one
#=> two
#=> three
###Using a func to double each element in the list:
Enum.each([1, 2, 3], &(IO.puts &1 * 2))
#=> 2
#=> 4
#=> 6


###Calling the function(s): filter
IO.puts ""
IO.puts "Calling the function(s): filter"
MyList.filter([], fn(x) -> rem(x, 2) == 0 end) |> IO.inspect
#=> []
MyList.filter([1, 2, 3], fn(x) -> rem(x, 2) == 0 end) |> IO.inspect
#=> [2]

IO.puts "And now using the Enum package..."
Enum.filter([], fn(x) -> rem(x, 2) == 0 end) |> IO.inspect
#=> []
Enum.filter([1, 2, 3], fn(x) -> rem(x, 2) == 0 end) |> IO.inspect
#=> [2]


###Calling the function(s): split
IO.puts ""
IO.puts "Calling the function(s): split"
MyList.split([], 3)  |> IO.inspect
#=> {[], []}
MyList.split([1, 2, 3, 4, 5], 2) |> IO.inspect
#=> {[1, 2], [3, 4, 5]}
MyList.split([1, 2, 3, 4, 5], 3) |> IO.inspect
#=> {[1, 2, 3], [4, 5]}

#If 'count' is negative: starts counting from end
#(ie 5 shown here) to beginning.
MyList.split([1, 2, 3, 4, 5], -2) |> IO.inspect
#=> {[1, 2, 3], [4, 5]}
MyList.split([1, 2, 3, 4, 5], -3) |> IO.inspect
#=> {[1, 2], [3, 4, 5]}
MyList.split([1, 2, 3, 4, 5], -4) |> IO.inspect
#=> {[1], [2, 3, 4, 5]}
MyList.split([1, 2, 3, 4, 5], -5) |> IO.inspect
#=> {[], [1, 2, 3, 4, 5]}
MyList.split([1, 2, 3, 4, 5], -10) |> IO.inspect
#=> {[], [1, 2, 3, 4, 5]}
MyList.split([1, 2, 3, 4, 5], 0) |> IO.inspect
#=>{[], [1, 2, 3, 4, 5]}

IO.puts "And now using the Enum package..."
Enum.split([], 3) |> IO.inspect
#=> {[], []}
Enum.split([1, 2, 3, 4, 5], 2) |> IO.inspect
#=> {[1, 2], [3, 4, 5]}
Enum.split([1, 2, 3, 4, 5], 3) |> IO.inspect
#=> {[1, 2, 3], [4, 5]}
Enum.split([1, 2, 3, 4, 5], -2) |> IO.inspect
#=> {[1, 2, 3], [4, 5]}
Enum.split([1, 2, 3, 4, 5], -3) |> IO.inspect
#=> {[1, 2], [3, 4, 5]}
Enum.split([1, 2, 3, 4, 5], -4) |> IO.inspect
#=> {[1], [2, 3, 4, 5]}
Enum.split([1, 2, 3, 4, 5], -5) |> IO.inspect
#=> {[], [1, 2, 3, 4, 5]}
Enum.split([1, 2, 3, 4, 5], -10) |> IO.inspect
#=> {[], [1, 2, 3, 4, 5]}
Enum.split([1, 2, 3, 4, 5], 0) |> IO.inspect
#=>{[], [1, 2, 3, 4, 5]}


###Calling the function(s): take (negative indices are allowed)
IO.puts ""
IO.puts "Calling the function(s): take"
MyList.take([1, 2, 3, 4, 5], 0) |> IO.inspect
 #=> []
MyList.take([1, 2, 3, 4, 5], 1) |> IO.inspect
#=> [1]
MyList.take([1, 2, 3, 4, 5], 2) |> IO.inspect
#=> [1, 2]
MyList.take([1, 2, 3, 4, 5], 5) |> IO.inspect
#=> [1, 2, 3, 4, 5]
MyList.take([1, 2, 3, 4, 5], -1) |> IO.inspect
#=> [5]
MyList.take([1, 2, 3, 4, 5], -2) |> IO.inspect
#=> [4, 5]

IO.puts "And now using the Enum package..."
Enum.take([1, 2, 3, 4, 5], 0) |> IO.inspect
#=> []
Enum.take([1, 2, 3, 4, 5], 1) |> IO.inspect
#=> [1]
Enum.take([1, 2, 3, 4, 5], 2) |> IO.inspect
#=> [1, 2]
Enum.take([1, 2, 3, 4, 5], 5) |> IO.inspect
#=> [1, 2, 3, 4, 5]
Enum.take([1, 2, 3, 4, 5], -1) |> IO.inspect
#=> [5]
Enum.take([1, 2, 3, 4, 5], -2) |> IO.inspect
#=> [4, 5]

Img_2196_pragsmall
21 May 2016, 15:18
Diogo Neves (4 posts)

My implementation:

defmodule MyEnum do
  def all?([], _), do: true
  def all?([head|tail], func) do
    if func.(head) do
      all?(tail, func)
    else
      false
    end
  end

  def each([], _), do: []
  def each([head|tail], func) do
    [func.(head)|each(tail, func)]
  end

  def filter([], _), do: []
  def filter([head|tail], func) do
    if func.(head) do
      [head|filter(tail, func)]
    else
      filter(tail, func)
    end
  end

  def take([], _), do: []
  def take(_, 0), do: []
  def take([head|tail], index) do
    [head|take(tail, index-1)]
  end

  def split(list, index), do: _split([], list, index)
  defp _split(head, [], _), do: {head, []}
  defp _split(head, tail, 0), do: {head, tail}
  defp _split(head, [h|t], index), do: _split(head ++ [h], t, index-1)
end

My split implementation differs from the author and avoids a reverse. Is there any disadvantage to using the list concatenation? Also, the exercise states we can’t use any library functions.

You must be logged in to comment