From: "Eregon (Benoit Daloze)" Date: 2022-01-03T13:52:16+00:00 Subject: [ruby-core:106951] [Ruby master Feature#18136] take_while_after Issue #18136 has been updated by Eregon (Benoit Daloze). I recently wanted to have something like this for advent of code, and worked around by using `Enumerator.produce + find + side effect` but it feels clearly less elegant. https://github.com/eregon/adventofcode/blob/a582daa1f2c549d459d6b0017b5940b2e96838d4/2021/5a.rb#L17-L20 Problem description at https://adventofcode.com/2021/day/5, in short it counts how many lines cross at a given position. What I wanted: ```ruby Enumerator.produce(from) { _1 + dir }.take_till { _1 == to }.each { crossings[_1] += 1 } ``` Workaround: ```ruby Enumerator.produce(from) { _1 + dir }.find { crossings[_1] += 1 _1 == to } ``` `Range#step` would work, but a creating a Range of Complex raises `bad value for range (ArgumentError)`. `Numeric#step` could have worked, but it's not defined for Complex. For the name, `take_until` came to mind ("take all elements until the condition is true, including that last element for which it's true"). It seems a nice counterpart to `take_while` ("take all elements while/for-which the condition is true"). Also in code the similarity with `until` is clearer: ```ruby # take_while begin e = enum.next c = condition(e) array << e if c end while c # take_until begin e = enum.next array << e end until condition(e) ``` `take_till` seems fine too, although it sounds less nice than `take_until` to me. `take_upto` is fine by me as well. `drop_after` seems confusing, because the use case is to select all elements until a given condition changes, i.e., the focus is to take/select, not to drop/reject. `take_while_after` is quite a mouthful and rather unclear from the name what it does. ---------------------------------------- Feature #18136: take_while_after https://bugs.ruby-lang.org/issues/18136#change-95774 * Author: zverok (Victor Shepelev) * Status: Open * Priority: Normal ---------------------------------------- Sorry, I already tried that once (#16441) but I failed to produce the persuasive example. So I am back with a couple of them, much simpler and clear than my initial. **The proposal itself:** Have `take_while_after` which behaves like `take_while` but also includes the last element (first where the condition failed). Reason: there are a lot of cases where "the last good item" in enumeration is the distinctive one (one where enumeration should stop, but the item is still good. **Example 1:** Take pages from paginated API, the last page will have less items than the rest (and that's how we know it is the last): ```ruby (0..).lazy .map { |offset| get_page(offset, limit) } .take_while_after { |response| response.count == limit } # the last will have, say, 10 items, but should still be included! .map { process response somehow } ``` **Example 2:** Same as above, but "we should continue pagination" is specified with a separate data key "can_continue": ```ruby (0..).lazy .map { |offset| get_page(offset, limit) } .take_while_after { |response| response['can_continue'] } # the last will have can_continue=false, but still has data .map { process response somehow } ``` **Exampe 3:** Taking a sentence from a list of tokens like this: ```ruby tokens = [ {text: 'Ruby', type: :word}, {text: 'is', type: :word}, {text: 'cool', type: :word}, {text: '.', type: :punctuation, ends_sentence: true}, {text: 'Rust', type: :word}, # ... ] sentence = tokens.take_while_after { !_1[:ends_sentence] } ``` (I can get more if it is necessary!) Neither of those can be solved by "Using `take_while` with proper condition.", as @matz suggested here: https://bugs.ruby-lang.org/issues/16441#note-9 I typically solve it by `slice_after { condition }.first`, but that's a) uglier and b) greedy when we are working with lazy enumerator (so for API examples, all paginated pages would be fetched at once, and only then processed). Another consideration in #16441 was an unfortunate naming. I am leaving it to discussion, though I tend to like `#take_upto` from #16446. -- https://bugs.ruby-lang.org/ Unsubscribe: