Using Enum or Stream to pass through first n elements and map the rest

I’m used to Rust where iterators have a ton of handy functions. What I’d like to do is equivalent to the following Rust code:

my_iterator.iter()
           .skip(n)
           .map(|x| my_fun(x))
           .collect()

In more concrete terms

fn my_fun(x: i32) -> i32 { x * 10 }
let my_iterator = vec![1,2,3,4,5];
my_iterator.iter()
           .skip(2)
           .map(|x| my_fun(x))
           .collect::<Vec<i32>>()
// [1,2,30,40,50]

Is there an equivalent in Elixir? Or would I need to do something like

my_list = [1,2,3,4,5]
map_or_not(my_list)

def map_or_not([1 | [ 2 | t ]]) do
  [1 | [2 |map_or_not(t)]]
end 

def map_or_not(list) do 
  list |> Enum.map(fn x -> x * 10 end)
end

That’s not the behavior I’m seeing from skip. When I run that I get [30, 40, 50]. It appears to behave like Enum.drop/2 or Stream.drop/2.

As for how to get the behavior you want, you could use Enum.reduce/3 or Stream.scan/3, using an acc to count down elements not to touch, then performing the mapping function when the acc is 0.

Or even easier might be Enumerable.map_reduce/3:

{result, _acc} =
  Enum.map_reduce([1, 2, 3, 4, 5], 2, fn
    item, 0 ->
      {item * 10, 0}

    item, acc ->
      {item, acc - 1}
  end)

result
# => [1, 2, 30, 40, 50]
2 Likes

Sorry, rust code should have used iter_mut() without a collect() call on a mutable vec. Good suggestion for the countdown accumulator. Thanks.

You should be able to write exactly what you mean:

s = 1..5

new_stream =
  Stream.concat(
    Stream.take(s, 2),
    Stream.drop(s, 2) |> Stream.map(& &1*10)
  )

Enum.to_list(new_stream) # => [1, 2, 30, 40, 50]

One caveat: this will traverse the first two elements of the stream twice. That’s no problem for most Elixir data structures, but will cause strange behavior when streaming from a stateful resource like the result of File.stream!.

2 Likes

I think you can make it simply

list = 1..5 # or list = [1,2,3,4,5]

skip_by_val = fn
   x, y when x <= y -> x
   x, _y -> 10*x
end

list |> Stream.map(&skip_by_val.(&1, 2)) |>  Enum.to_list()

or

skip_by_idx = fn
   {x, idx_x}, idx when idx_x <= idx -> x
   {x, _idx_x}, _idx -> 10*x   
end

list |> Stream.with_index(1) |> Stream.map(&skip_by_idx.(&1, 2)) |> Enum.to_list()

1 Like

Thanks for all the help! Stream.concat is a pretty obvious. literal translation of what I was trying to do, so thanks for that tip. And simply pattern matching the index/position for applying the mapping function is probably the most obvious way to do what I want. Thanks again.