It could definitely be implemented as a Enum or a Stream function, but the
level of complexity is definitely much higher, and reduce would force the
implementation to be linear anyway. So I am honestly not sure if it is
worth it.

On Sat, Sep 4, 2021 at 7:07 PM w...@resilia.nl <w...@resilia.nl> wrote:

> > Wiebe-Marten Wijnja, unfortunately I think those operations would be
> relatively expensive on an array too, in terms of mutations, although the
> index lookup is faster. As other function in the List module, I think we
> should document that they would be very expensive in a loop (but ok as
> one-offs).
>
> Interestingly, moving a small slice a small distance will probably be much
> faster on an array (because both lookup and replacement of individual
> values at arbitrary locations in the array is faster). However, if we are
> moving, say, the first element to the end, then arrays and lists will both
> be roughly equally expensive because all elements will need to be moved
> around.
>
> >  Re: the efficiency issues, as Chris Keathley puts it less
> diplomatically, "lists are a garbage data structure." 😂 But given that so
> much of the ecosystem is already built on lists, and we already have lots
> of linear-time algorithms on them, I don't feel like this *adds* to the
> problem. If anything, you'd be reaching for this when the alternative is to
> do the split-and-recombine by hand. At least with a standard library
> implementation, we could provide the safest, cleanest implementation
> possible.
>
> Agreed!
> If we clearly document this, it should definitely be fine 👍.
>
> One more question:
> Do we want to add this to `List`, or rather to `Enum`?
> Other enumerables besides lists‡ might benefit from this. And having it
> available as a lazy function in `Stream` might also be beneficial.
>
> ‡: Any enumerable where element ordering is important. Essentially
> anything except maps and `MapSet`s.
>
> ~Marten/Qqwy
> On Thursday, September 2, 2021 at 5:07:50 PM UTC+2 s3c...@gmail.com wrote:
>
>> Ahh, I see what you're saying, José. Yeah, under the List.rotate(list,
>> range, index) model, your range could be negative independently of the
>> insertion index.
>>
>> Re: the efficiency issues, as Chris Keathley puts it less diplomatically,
>> "lists are a garbage data structure." 😂 But given that so much of the
>> ecosystem is already built on lists, and we already have lots of
>> linear-time algorithms on them, I don't feel like this *adds* to the
>> problem. If anything, you'd be reaching for this when the alternative is to
>> do the split-and-recombine by hand. At least with a standard library
>> implementation, we could provide the safest, cleanest implementation
>> possible.
>>
>>
>> On Thursday, September 2, 2021 at 9:57:06 AM UTC-5 José Valim wrote:
>>
>>> I don't think there is any restriction on the end. It can be anywhere on
>>> the list. The semantics for start..middle can be designed based on
>>> Enum.slice/2 and the semantics of "end" on insert_at. After all,
>>> List.rotate seems to be a "slice" plus a "insert_many_at". Although end
>>> should never fall within start..middle (we should raise for those cases).
>>>
>>> The other thing is that the implementation can be made more efficient if
>>> we take the end into account. For example, if end is after middle, we can
>>> use a non-tail recursive to start traverse until we find the start:
>>>
>>> def find_start(list, 0), do: accumulate_start_middle(list, middle, end,
>>> [])
>>> def find_start([h | t], start, middle, end), do: [h | find_start(t,
>>> start - 1, middle, end)]
>>>
>>> Then accumulate_start_middle will get all values from start to middle
>>> and accumulate them in a list in reverse order. Then you need to find end,
>>> using a non-tail recursive approach and reverse the accumulator into the
>>> rest:
>>>
>>> def find_end(rest, 0, acc), do: Enum.reverse(acc, rest)
>>> def find_end([h | t], end, acc), do: [h | find_end(t, end - 1, acc)]
>>>
>>> This guarantees the list is only copied once (plus one additional copy
>>> for the start..middle slice). A similar approach can be devised if the end
>>> is before the start.
>>>
>>> ---
>>>
>>> Wiebe-Marten Wijnja, unfortunately I think those operations would be
>>> relatively expensive on an array too, in terms of mutations, although the
>>> index lookup is faster. As other function in the List module, I think we
>>> should document that they would be very expensive in a loop (but ok as
>>> one-offs).
>>>
>>> On Thu, Sep 2, 2021 at 4:10 PM Wiebe-Marten Wijnja <w...@resilia.nl>
>>> wrote:
>>>
>>>> Playing devil's advocate here for a second:
>>>> We are talking about a situation where you have a collection of items,
>>>> and you want to add elements to the middle/remove elements from the
>>>> middle/reorder elements into the middle.
>>>>
>>>> I do not think that lists are the right tool for this job here, as
>>>> these operations will slow down significantly when the list grows in size.
>>>> As such, maybe adding such an operation to the `List` module might not
>>>> be such a good idea, as we will be giving new developers a tool which they
>>>> may hurt themselves with,
>>>> rather than choosing a data structure (like another array collection,
>>>> such as map-based arrays or `:array`) that fits the requirements better.
>>>>
>>>> ~Marten/Qqwy
>>>> On 02-09-2021 15:40, Tyler Young wrote:
>>>>
>>>> Thanks so much for the feedback, José. ☺️
>>>>
>>>> 1. My working model was that all indices should be in the range [0,
>>>> length - 1]. I could imagine supporting either 3x non-negative or 3x
>>>> negative indices (with negative indices offsetting from the end of the list
>>>> as usual). I'm not sure whether the negative version would be more
>>>> confusing than it's worth, though.
>>>> 2. If the middle index is equal to the start index, you're essentially
>>>> asking to move the range between indices n and m to start at... index n, so
>>>> you'll get the list back unchanged. For middle less than start, I think
>>>> we'd want to consider that a contract violation, and return the original
>>>> unchanged.
>>>> 3. I'd suggest making the contract (in the form of the docs, I suppose)
>>>> specify start <= middle <= end, and any violation of that gives you back
>>>> the original list. (I thiiiiiink that's consistent with the existing design
>>>> of List—e.g., List.pop_at/3 returns the original list if you give it
>>>> an index that's off the end, rather than raising an error or something.
>>>> 4. Oooh, I like that a lot. That helps a lot with deciphering which
>>>> parameters are which. 🙏
>>>>
>>>> On Thursday, September 2, 2021 at 4:31:18 AM UTC-5 José Valim wrote:
>>>>
>>>>> Hi Tyler! I think starting with a basic List.rotate is very welcome.
>>>>> My questions are:
>>>>>
>>>>> 1. Which of those indexes can be negative?
>>>>> 2. What happens if the middle index is less than or equal to the start
>>>>> index?
>>>>> 3. What happens if the last index is within start and middle?
>>>>> 4. Could List.rotate(list, range, index) be a better API? I.e. move
>>>>> the list represented by indexes in range to index?
>>>>>
>>>>> Thanks for the proposal!
>>>>>
>>>>> On Thu, Sep 2, 2021 at 3:38 AM Tyler Young <s3c...@gmail.com> wrote:
>>>>>
>>>>>> Hi folks!
>>>>>>
>>>>>> I'd like to propose a List.rotate/4 function (and maybe some
>>>>>> convenience wrappers for it as well).
>>>>>>
>>>>>> Rotate is one of those algorithms that, once I learned about it, I
>>>>>> started seeing it everywhere. It's somewhat complicated to grasp (it 
>>>>>> takes
>>>>>> 4 parameters, after all), but in situations where you need it, it's still
>>>>>> *much* simpler than the equivalent imperative version.
>>>>>>
>>>>>> The classic use case is this: Suppose you have a list of to-do items,
>>>>>> which the user has ordered by priority:
>>>>>>
>>>>>>    1. Apply to college
>>>>>>    2. Brush the dog
>>>>>>    3. Change the car's oil
>>>>>>    4. Deliver flowers
>>>>>>    5. Exchange gifts
>>>>>>
>>>>>> A "rotate" or "slide" occurs when the user selects some number of
>>>>>> elements and drags them to a new place in the list. Let's say they 
>>>>>> selected
>>>>>> items 3 & 4 from the preceding and dragged them above item 2. When they
>>>>>> release the mouse, the new order should be:
>>>>>>
>>>>>>    1. Apply to college
>>>>>>    2. Change the car's oil
>>>>>>    3. Deliver flowers
>>>>>>    4. Brush the dog
>>>>>>    5. Exchange gifts
>>>>>>
>>>>>> Doing this without the named algorithm requires 3 splits (one at the
>>>>>> insertion point, one at the start of the selected range, and one at the 
>>>>>> end
>>>>>> of the selected range). It's easy to get the index math wrong, and it's
>>>>>> even harder for readers of your code to grasp what's going on. Adding 
>>>>>> this
>>>>>> as a "vocabulary" algorithm would help a lot, I feel.
>>>>>>
>>>>>> A number of other languages
>>>>>> <https://twitter.com/code_report/status/1419900906062204939?s=20>
>>>>>> have a rotate algorithm, though it's still somewhat uncommon. I found 
>>>>>> Dave
>>>>>> Abrahams' comments
>>>>>> <https://forums.swift.org/t/proposal-implement-a-rotate-algorithm-equivalent-to-std-rotate-in-c/491/2>
>>>>>> valuable when this was discussed for inclusion in Swift.
>>>>>>
>>>>>> I've put together a first draft of an implementation
>>>>>> <https://github.com/s3cur3/elixir-xutil/blob/main/lib/x_util/list.ex>,
>>>>>> plus some basic tests
>>>>>> <https://github.com/s3cur3/elixir-xutil/blob/main/test/list_test.exs>
>>>>>> .
>>>>>>
>>>>>> If this is something folks decide we want, it might also be worth
>>>>>> considering a few variants (also implemented in the file linked above):
>>>>>>
>>>>>>    - slide/4: Syntactic sugar over rotate, but it's useful in
>>>>>>    practice because usages of rotate often need to be able to move a 
>>>>>> chunk of
>>>>>>    the list either or backward. (Most usages I've seen in the wild end up
>>>>>>    doing an if insertion_idx < range_start . . . else . . .)
>>>>>>    - slide_one/3, where the second argument is the index of the
>>>>>>    single element to move and the last argument the target index. This 
>>>>>> would
>>>>>>    just be syntactic sugar over slide/4. (In my experience, about
>>>>>>    half the times I use this algorithm, the conceptual requirements 
>>>>>> guarantee
>>>>>>    that it'll only act on a single element.)
>>>>>>
>>>>>> --
>>>>>> You received this message because you are subscribed to the Google
>>>>>> Groups "elixir-lang-core" group.
>>>>>> To unsubscribe from this group and stop receiving emails from it,
>>>>>> send an email to elixir-lang-co...@googlegroups.com.
>>>>>> To view this discussion on the web visit
>>>>>> https://groups.google.com/d/msgid/elixir-lang-core/01c63660-6a11-4e69-bdcb-6659579ef683n%40googlegroups.com
>>>>>> <https://groups.google.com/d/msgid/elixir-lang-core/01c63660-6a11-4e69-bdcb-6659579ef683n%40googlegroups.com?utm_medium=email&utm_source=footer>
>>>>>> .
>>>>>>
>>>>> --
>>>> You received this message because you are subscribed to the Google
>>>> Groups "elixir-lang-core" group.
>>>> To unsubscribe from this group and stop receiving emails from it, send
>>>> an email to elixir-lang-co...@googlegroups.com.
>>>> To view this discussion on the web visit
>>>> https://groups.google.com/d/msgid/elixir-lang-core/3467969a-8289-42fa-9b4e-741a26da3e1en%40googlegroups.com
>>>> <https://groups.google.com/d/msgid/elixir-lang-core/3467969a-8289-42fa-9b4e-741a26da3e1en%40googlegroups.com?utm_medium=email&utm_source=footer>
>>>> .
>>>>
>>>> --
>>>> You received this message because you are subscribed to the Google
>>>> Groups "elixir-lang-core" group.
>>>> To unsubscribe from this group and stop receiving emails from it, send
>>>> an email to elixir-lang-co...@googlegroups.com.
>>>>
>>> To view this discussion on the web visit
>>>> https://groups.google.com/d/msgid/elixir-lang-core/990c93ec-0f9f-0d8a-4d51-731821ebf205%40resilia.nl
>>>> <https://groups.google.com/d/msgid/elixir-lang-core/990c93ec-0f9f-0d8a-4d51-731821ebf205%40resilia.nl?utm_medium=email&utm_source=footer>
>>>> .
>>>>
>>> --
> You received this message because you are subscribed to the Google Groups
> "elixir-lang-core" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to elixir-lang-core+unsubscr...@googlegroups.com.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/elixir-lang-core/7657048f-da6d-475e-8c43-cb22f22e2e0cn%40googlegroups.com
> <https://groups.google.com/d/msgid/elixir-lang-core/7657048f-da6d-475e-8c43-cb22f22e2e0cn%40googlegroups.com?utm_medium=email&utm_source=footer>
> .
>

-- 
You received this message because you are subscribed to the Google Groups 
"elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to elixir-lang-core+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/elixir-lang-core/CAGnRm4%2BtH72_L0M55vynyCwYxTyQ05iqbuKdXUvoSZhvvNnROg%40mail.gmail.com.

Reply via email to