Iter::Able – Yet more abilities for iterables
use Iter::Able *;
[5, 12, 13, 8].&map-when(*.is-prime, * * 2) # (10, 12, 26, 8).Seq
[2, 4, 77].&is-all-same(:as(* > 0)) # True
((1, (2, 3)), (4, 5, 6), 7).&flatten(:1level) # (1, (2, 3), 4, 5, 6, 7)
"until first whitespace".&take-while(/ \S /) # "until"
[-4, -9, 3].&chain([1, -7]).&clip(:0from-below) # (0, 0, 3, 1, 0).SeqProvides iterable manipulation functions. Raku already has superb support for such tasks,
e.g., map, grep, rotor, categorize etc. One can build more specific functions using them
(implementation- and/or semantic-wise); this module aims to do that for variety of functions
for reuse. In addition to iterables (and iterators), functions also accept string inputs too
when appropriate. Inspirations include dash.el of ELisp, Enumerable of Ruby, std::iter::Iterator of Rust, itertools and more_itertools of Python.
- No functions imported:
use Iter::Able; - All functions imported:
use Iter::Able *;(or<*>) - Selective importing:
use Iter::Able <map-indexed take-while>;
All functions are our-scoped so they are reachable with their partially qualified names in any case, e.g., Iter::Able::skip-while.
\itin the signature means function accepts an iterable or an iterator as the input.\istmeans it also accepts a string.- Unless otherwise stated, all functions return
- a Seq for an iterable input,
- an Str for a string input,
- an Iterator for an iterator input.
It goes with the name “Iter::Able” in the ecosystem, so you can do, e.g.,
pakku add Iter::Able(Below is automatically generated by “tools/embed-documentation.raku”.)
Generates (x, f(x)) lists; by default, f(x) = x. Returns a Seq for strings.
# Mirrors the items by default
>>> [-4, 3, 0].&annotate
((-4, -4), (3, 3), (0, 0)).Seq
# Let items carry their length with them
>>> ("piano", "drum", "violin").&annotate(&chars)
(("piano", 5), ("drum", 4), ("violin", 6)).Seq
# Originals and matches
>>> ["this and that", "yes and no", "real"].&annotate(/ .? <before ' and'>/)
(("this and that", 「this」), ("yes and no", 「yes」), ("real", Nil)).Seq
# is-upper decoration
>>> annotate "reAL", {$_ eq .uc} # or `so * ~~ / <.upper> /`
(("r", False), ("e", False), ("A", True), ("L", True)).SeqPerforms index-based replacement given index => new-value pairs.
# Replaces what's at index 1
>>> [0, 1, 2, 3].&assign-at(1 => -9)
(0, -9, 2, 3).Seq
# Multiple index-value pairs are possible
>>> [4, 3, 2, 1].&assign-at(0 => -4, 2 => -1)
(-4, 3, -1, 1).Seq
# Out-of-bounds indexes are silently ignored
>>> [5, 55, 555].&assign-at(3 => 5555)
(5, 55, 555).Seq
# Negative indexes are fine as well
>>> assign-at [4, 44, 444], -2 => -44
(4, -44, 444).Seq
# Strings are possible too
>>> "past".&assign-at(0 => "q")
"qast"
# Empty string acts as a remover for string inputs
>>> "until".&assign-at(1 => "")
"util"
# Can expand a string
>>> "play".&assign-at(0 => "de")
"delay"Yields from this iterable first; when exhausted, from the next one in the chain, and so on. Returns a Seq for strings.
# Accepts any number of iterables
>>> [-1, -2, -3].&chain([4, 5], [-6])
(-1, -2, -3, 4, 5, -6).Seq
# Infinity chaining
>>> [1, 2].&chain(3 xx *).head(5)
(1, 2, 3, 3, 3).Seq
# Can chain to strings
>>> "get".&chain("attr")
("g", "e", "t", "a", "t", "t", "r").Seq
# Can chain with strings
>>> (4, 7).&chain("spa")
(4, 7, "s", "p", "a").Seq
# Another way of (one-level) flattening a list of lists
>>> chain |[[4, 7], [6], [0, 8, 9]]
(4, 7, 6, 0, 8, 9).SeqLimits the values from below and/or above.
# No negatives
>>> [-1, 2, -3].&clip(from-below => 0)
(0, 2, 0).Seq
# Accumulate everything to be in the first quadrant
>>> (2 × rand - 1) xx 5 ==> map(*.acos.round(0.001)) ==> clip(from-below => 0, from-above => π / 2)
(0.829 0.463 0.998 1.254 1.5707963267948966).Seq
# At most 100 is allowed
>>> 3 <<**<< (4, 5, 6) ==> clip(:100from-above)
(81, 100, 100).SeqRepeats the stream indefinitely. Returns a Seq for strings.
>>> [1, 2, 3].&cycle.head(5)
(1, 2, 3, 1, 2).Seq
>>> "real".&cycle.head(9)
("r", "e", "a", "l", "r", "e", "a", "l", "r").SeqGenerates (index, element) lists, with the starting index specifiable.
# Default starts from 0
>>> [1, 2, 3, 0].&enumerate
((0, 1), (1, 2), (2, 3), (3, 0)).Seq
# Sometimes 1-based indexing is useful
>>> "yes".&enumerate(start => 1)
((1, "y"), (2, "e"), (3, "s")).Seq
# Can start with any numeric value
>>> enumerate "cgpa", start => 3.83
((3.83, "c"), (4.83, "g"), (5.83, "p"), (6.83, "a")).SeqCheck if every element (all of them) satisfies the given predicate. It has a short-circuiting behaviour, i.e., if a non-satisfactory element is found, traversal immediately stops. `all` would be a better name but it’s taken.
# All positive elements?
>>> [-4, 0, 5, 2, -8].&every(* > 0)
False
# Everyone is prime?
>>> every &is-prime, [191, 211, 911]
True
# Default predicate is truthfulness
>>> every (-5, +5, [3, 4])
True
# Stops once it finds a failing element
>>> every (4, |(5 xx *)), * == 4
False
# Empty input yields True vacuously, regardless of the predicate
>>> every []
True
# Strings are also accepted as inputs
>>> "thereisnospacehere".&every(/ \S /)
TrueFill undefined values from a hash, list or a scalar. Undefined values correspond to type objects, e.g., Int, Any, DateTime.
# Fill from an associative
>>> [1, Any, -4, 3, Int].&fill-undef(%(Any => -1, Int => 0))
(1, -1, -4, 3, 0).Seq
# If the filler is a list (or any iterable really), filling will happen positionally
>>> [2, Any, 3, Any].&fill-undef([5, 77])
(2, 5, 3, 77).Seq
# Filler could be a scalar; then all undefined values will be substituted to that
>>> (64, Nil, 32, PseudoStash).&fill-undef(0)
(64, 0, 32, 0).Seq
# If the filler associative lacks or has extra element(s), they are ignored in both sides
>>> fill-undef [4, Str, 44, Any], %(Str => "", Num => 0e0)
(4, "", 44, Any).Seq
# If the filler list lacks or has extra element(s), they are ignored in both sides
>>> fill-undef [4, Str, 44, Any], ("",)
(4, "", 44, Any).Seq
>>> fill-undef [4, Str, 44, Any], ("", 0, True)
(4, "", 44, 0).SeqMakes a “one dimensional” iterable. Unlike the built-in flat, this does not respect itemized iterables. The number of levels to flatten can be controlled with the :$level parameter; currently leveled flattening reifies the iterable.
# Flattens all-the-way by default
>>> flatten ((1, (2, 3)), (4, 5, 6), 7)
(1, 2, 3, 4, 5, 6, 7).Seq
# Flatten only 1 level
>>> ((1, (2, 3)), (4, 5, 6), 7).&flatten(:1level)
(1, (2, 3), 4, 5, 6, 7)
# Unlike `flat`, itemizeds are subject to flattenning
>>> [(3, 4), 5, (6,)].&flatten
(3, 4, 5, 6).Seq
# Flatten a ragged one all the way
>>> flatten [["a", ("b", "c")], [("d",), "e", "f", ["g", ("h", "i")]]]
("a", "b", "c", "d", "e", "f", "g", "h", "i").Seq
# Up to 2 levels of unraggification
>>> flatten [["a", ("b", ("c", "d"))], [[[["e"],],],]], :2levels
["a", "b", ("c", "d"), [["e"],]]Apply the given function to each element for the side effects. Returns Nil to signal return values are ignored.
# Change the value of an attribute of a group of objects
>>> @paddles.&for-each({ .x++ })
# Print the primes
>>> (4, 7, 12, -3).&for-each: { .put if .is-prime }
# Works for strings too should you want
>>> "a lot of characters".&for-each: { .ord.put }Packs consecutive “same” elements together and yields “key ⇒ group” pairs where groups are Lists (values are not copied). Sameness can be controlled with a transformer (as) and/or an equality checker (with). Returns a Seq for strings.
# Elements themselves are the groupers by default
>>> [3, 4, 4, 5, 4].&group-conseq
(3 => (3,), 4 => (4, 4), 5 => (5,), 4 => (4,)).Seq
# Group consecutive records together; any duplicate key might be anomaly
>>> [("A", 1), ("B", 1), ("D", 2), ("E", 1)].&group-conseq(:as(*[1]))
(1 => (("A", 1), ("B", 1)), 2 => (("D", 2),), 1 => (("E", 1),)).Seq
# They are all the same, really
>>> [1, -1, 1, -1, 1, -1].&group-conseq(as => &abs)
(1 => (1, -1, 1, -1, 1, -1)).Seq
# Respect the container for sameness
>>> my $a = 7
>>> ($a, $a, 7).&group-conseq(with => &[=:=])
(7 => (7, 7), 7 => (7,)).Seq
# Case insensitive detection of consecutive duplicates in a string; typos?
>>> my $s = "how aree youU?"
>>> $s.&group-conseq(as => &lc).grep(*.value > 1)
(e => (e, e), u => (u, U)).SeqInserts values at the given positions. Cannot insert past the end even if finite; see chain for that.
# At the beginning
>>> [2, 3].&insert-at(0 => 1)
(1, 2, 3).Seq
# Multiple insertions
>>> (1, 2, 0, 16).&insert-at(2 => 4, 3 => 9)
(1, 2, 4, 0, 9, 16).Seq
# Positions past the end are silently ignored
>>> [5, 7].insert-at(2 => 9)
(5, 7).Seq
# Strings are possible too
>>> "aise".&insert-at(1 => "r")
"arise"
# Can expand strings even more
>>> insert-at "sing", 1 => "tr"
"string"Checks if the values are all different. Semantically equivalent to `.unique == .elems` but implemented differently. Also works for strings. Sameness can be controlled with a transformer (as) and/or an equality checker (with). By default, no transformation occurs and === is used for equivalance.
# Shortcircuitingly gives False once two same values are seen
>>> [1, 1, 2, 3, 4].&is-all-different
False
# True when all values are !=== to each other
>>> is-all-different (1, 2, 3)
True
# Vacuously true
>>> is-all-different []
True
# Works for strings the same way
>>> "yes".&is-all-different
True
# Equivalance relation can be altered
>>> my ($a, $b) = 3, 3
>>> [$a, $b].&is-all-different(:with(&[=:=]))
True
# Values can be transformed before comparison
>>> [0.2, -0.54, 1, 0.32].&is-all-different(:as(&round))
FalseChecks if the values are all the same. Semantically equivalent to `.unique <= 1` but implemented differently. Also works for strings. Sameness can be controlled with a transformer (as) and/or an equality checker (with). By default, no transformation occurs and === is used for equivalance.
# Shortcircuitingly gives False once two different values are seen
>>> [1, 2, 1, 1, 1].&is-all-same
False
# True when all values are === to each other
>>> is-all-same (1, 1)
True
# Vacuously true
>>> is-all-same []
True
# Works for strings the same way
>>> "no".&is-all-same
False
# Equivalance relation can be altered
>>> my ($a, $b) = 3, 3
>>> [$a, $b].&is-all-same(:with(&[=:=]))
False
# Values can be transformed before comparison
>>> "aaAaA".&is-all-same(:as(&fc))
TrueMaps only the first item that satisfies the predicate, if any.
# First positive to negative
>>> map-first [1, 2, 3], * > 0, -*
(-1, 2, 3).Seq
# Can use with all-pass filter to assign to head :)
>>> map-first ["", 5, 9, 11], { True }, { 0 }
(0, 5, 9, 11).Seq
# First uppercase to lowercase
>>> "here WE are".&map-first(/ <.upper> /, &lc)
"here wE are"
# If no one matches, everyone is yielded as is
>>> [4, 44, 444, 4444].&map-first(*.is-prime, { 7 });
(4, 44, 444, 4444).SeqMaps the iterable given the index and the element, i.e., `-> $idx, $val { … }` is the mapper. By default `index` starts from 0 but can be changed with `:$start`. Returns a Seq for strings.
# Produce new items as `index * element`
>>> [3, 2, 1].&map-indexed(* * *)
(0, 2, 2).Seq
# `index + element` as kind of an added ramp and also start from 1
>>> (4, 7, 12, -3).&map-indexed(* + *, start => 1)
(5, 9, 16, 1).Seq
# Even indexed values are zeroed out
>>> (4, 7, -1).&cycle.&map-indexed({ $^idx %% 2 ?? 0 !! $^val }).head(5)
(0, 7, 0, 4, 0).Seq
# Repeat a character as many as its position suggests
>>> "train".&map-indexed(* Rx *, start => 1)
("t", "rr", "aaa", "iiii", "nnnnn").SeqMaps only the last item that satisfies the predicate, if any.
# Last negative to positive
>>> map-last [2, -3, 4, -6, 8], * < 0, -*
(2, -3, 4, 6, 8).Seq
# Can use with all-pass filter to change the last element :)
>>> map-last [3, 4, 7, NaN], { True }, { -1 }
(3, 4, 7, -1).Seq
# Last lowercase to uppercase
>>> "here we are!".&map-last(/ <.lower> /, &uc)
"here we arE!"
# If no one matches, everyone is yielded as is
>>> [57, 91, -13].&map-last(*.is-prime, { 0 });
(57, 91, -13).SeqMaps only the elements that satisfy the predicate, if any.
# If nonpositive, make it cubed; else, keep as is
>>> [1, -2, 3, 0, 4, -5].&map-when(* <= 0, * ** 3)
(1, -8, 3, 0, 4, -125).Seq
# Take the square root only if positive
>>> (4, -7, 9, 0).&map-when(* > 0, &sqrt)
(2, -7, 3, 0).Seq
# Make vowels upper case
>>> "mixed feelings".&map-when(/:i <[aeiou]>/, &uc)
"mIxEd fEElIngs"
# Normalize "anomalies"
>>> (r1 => 7.13, r2 => 6.89, r3 => 7.90, r4 => 6.61).&map-when((*.value - 7).abs >= 0.2, {7})
(r1 => 7.13, r2 => 6.89, r3 => 7, r4 => 7).SeqComputes the minimal and maximal values. By default &cmp is used for comparing the values but a custom comparator (of arity 1 or 2) can be passed. In case of ties, the first minimal/maximal value is returned. Empty input (after grepping out type objects, if any) results in Failure. Optional flags can control what is returned: :k for indexes, :v for values, :kv for both interspersed and :p for indexes and values as pairs. When a flag is supplied, all indexes/values are returned even if tied.
# Get minimum and maximum values in one pass
>>> [24, 11, 75, -6].&min-max
(-6, 75)
# Custom comparator
>>> ["brb", "hi", "no"].&min-max(*.chars)
("hi", "brb")
# Type objects are ignored
>>> [-9, Date, HyperSeq, PseudoStash, 125].&min-max
(-9, 125)
# Argmin/max
>>> [12, 3, 75].&min-max(:k)
((1,), (2,))
# :v will collect even the tied ones
>>> [-12, +12, 5].&min-max(*.abs)
(5, -12)
>>> [-12, +12, 5].&min-max(*.abs, :v)
(5, (-12, 12))
# min/maxpairs together
>>> min-max [600, 4, -32], :p
((2 => -32,), (0 => 600,))
# Strings are possible too
>>> min-max "Nomenclature", -*.ord
("u", "N")
# If the input is empty, result is a Failure
>>> min-max []
Argument to &min-max is empty (or full of undefined values)
# Since type objects are ignored, result can fail with them too
>>> min-max [Cool, IntStr, Raku]
Argument to &min-max is empty (or full of undefined values)Yields the n’th value of the input. Almost the same as .[n] but also works for iterators and strings. Negative indexes are also allowed so long as the input isn’t lazy (i.e., possibly infinite). Returns a single value, or dies if the index is out-of-bounds (if it’s a List-like, i.e., the bounds are easily measurable).
# Works as usual for nonnegative `n` on Arrays
>>> [0, 1, 2].&n'th(1)
2
# Can pass a negative index
>>> (4, 7, 12, 0).&n'th(-3)
7
# Strings are indexable as well
>>> "regard".&n'th(5)
"d"
# Strings from the other side
>>> "yes".&n'th(-2)
"e"
# Out-of-bounds requests result in error *if* List-like
>>> n'th (5, 12, 13), 29
n = 29 is out of bounds for size 3
in block...
# On iterators
>>> my \it = [4, 5, 6].iterator;
>>> print it.&n'th(0), " " for ^3
4 5 6Check if none of the elements (noone, not any) satisfies the given predicate. It has a short-circuiting behaviour, i.e., if a satisfactory element is found, traversal immediately stops. `none` would be a better name but it’s taken.
# No positive elements?
>>> [-4, 0, 5, 2, -8].&noone(* > 0)
False
# No-one is prime?
>>> noone &is-prime, [9, 99, 999]
True
# Default predicate is truthfulness
>>> noone ([], "", Empty)
True
# Stops once it finds a failing element
>>> noone (4, |(5 xx *)), * == 5
False
# Empty input yields True vacuously, regardless of the predicate
>>> noone []
True
# Strings are also accepted as inputs
>>> "thereisnospacehere".&noone(/ \s /)
TruePerforms index-based removal given integer indexes.
# Value at 1st index is gone
>>> [0, 1, 2, 3].&remove-at(1)
(0, 2, 3).Seq
# Multiple index positions are possible
>>> [4, 3, 8, 1].&remove-at(0, 2)
(3, 1).Seq
# Out-of-bounds indexes are silently ignored
>>> [5, 55, 555].&remove-at(32)
(5, 55, 555).Seq
# Negative indexes are fine
>>> remove-at [4, 44, 444], -2
(4, 444).Seq
# Strings are possible too
>>> "past".&remove-at(0)
"ast"
# Negative positions on strings
>>> "play".&remove-at(0, -1)
"la"Removes the first element satisfying the predicate, if any. Without any predicate, the very first element is skipped.
# Without an argument, equivalent to `.skip`
>>> [1, 2, 3, 0, 4, 5].&remove-first
(2, 3, 0, 4, 5).Seq
# Remove the first nonnegative element (and only that)
>>> (-2, -8, 5, 12, 0).&remove-first(* >= 0)
(-2, -8, 12, 0).Seq
# If there is no "bad" element, yield back as is
>>> remove-first [10, 20, 30], &is-prime
(10, 20, 30).Seq
# String invocants as well as regex predicates are accepted as well
>>> "fi rst whitespace is gone".&remove-first(/ \s /)
"first whitespace is gone"Remove the last element satisfying the predicate, if any. Without any predicate, the last element is thrown.
# Without an argument, it's like *.head(*-1)
>>> [1, 2, 3, 0, 4, 5].&remove-last
(1, 2, 3, 0, 4).Seq
# Remove the last zero (and only that zero)
>>> (4, 0, 5, 2, 0, 0).&remove-last(* == 0)
(4, 0, 5, 2, 0).Seq
# If nothing to remove, yield back as is
>>> remove-last * %% 2, [1, 3, 5, 7]
(1, 3, 5, 7).Seq
# String invocants as well as regex predicates are accepted as well
>>> "This is important. Right? Yes!".&remove-last(/ <punct> /)
"This is important. Right? Yes"Trims the given prefix from the string, if it exists. The optional flag :i performs it case insensitively.
# Strip off from the beginning
>>> "https://thing.org".&remove-prefix("https://")
"thing.org"
# If not strictly at the beginning, no change
>>> remove-prefix "Somewhere here", "where"
"Somewhere here"
# Case insensitive
>>> "***Info:hi".&remove-prefix("***info:", :i)
"hi"
# Newline at the beginning is considered important
>>> "\nThis stays".&remove-prefix("this", :i)
"\nThis stays"Trims the given suffix from the string, if it exists. The optional flag :i performs it case insensitively.
# Strip off from the end
>>> "hi there!".&remove-suffix("!")
"hi there"
# If not strictly at the end, no change
>>> remove-suffix "Zugzwang", "zwan"
"Zugzwang"
# Case insensitive
>>> "Republic".&remove-suffix("Public", :i)
"Re"
# Newline at the end is considered important
>>> "This stays\n".&remove-suffix("ys")
"This stays\n"Translates values through the given pairs. All occurences are replaced. Only Numerics and Strings within an iterable/iterator are replaced; for others, see map-when. For replacing strings, see the built-in trans.
# Replace a single value
>>> [1, 2, 3].&replace(2 => 99)
(1, 99, 3).Seq
# More than one
>>> (4, 5, 6, 5, 4).&replace((4, 5) X=> 0)
(0, 0, 6, 0, 0).Seq
# Need to quote the LHS of pairs if they are valid identifiers,
# as they would pass as named arguments otherwise
>>> ["yes", "no", "both"].&replace("both" => "neither")
["yes", "no", "neither"].Seq
# Unfound LHS values of pairs are silently ignored
>>> [2, 4, 6, 7].&replace(8 => -8)
(2, 4, 6, 7).SeqSelects values at the given indexes. Acts as a generalized version of the built-in slice; supports negative, duplicated, mixed-order indexes.
# 3rd and 1st (order is retained)
>>> [0, -1, -2, -3].&select-at(3, 1)
(-3, -1).Seq
# Negative indexes are supported
>>> select-at (4, 7, 12, -3), (-3, -2, 0)
(7, 12, 4).Seq
# An index can be requested multiple times
>>> [5, 44, 555].&select-at(1, -2, 1, 1)
(44, 44, 44, 44).Seq
# Out of bounds indexes are silently ignored
>>> [0, 1].&select-at(500, 1)
(1,).Seq
# Strings are accepted too
>>> "string".&select-at(1..3)
"tri"Skips values from the iterable as long as &pred holds; once not, starts taking values indefinitely.
# Skip the falsefuls in front
>>> [0, "", 7, Any, 4, -5].&skip-while(¬)
(7, Any, 4, -5).Seq
# Generalized trim-leading
>>> (NaN, NaN, NaN, 4.6, -7.1, 8.0).&skip-while(* === NaN)
(4.6, -7.1, 8).Seq
# Skip unwanted characters
>>> my Set() $unwanteds = <. , ;>;
>>> ",,.;Trial and error. Important.".&skip-while(* ∈ $unwanteds)
"Trial and error. Important."Check if some of the elements (any, at least one) satisfy the given predicate. It has a short-circuiting behaviour, i.e., if a satisfactory element is found, traversal immediately stops. `any` would be a better name but it’s taken.
# Any positive elements?
>>> [-4, 0, 5, 2, -8].&some(* > 0)
True
# Any primes?
>>> some &is-prime, [1, -1, 0]
False
# Default predicate is truthfulness
>>> some (Any, 0, [], "")
False
# Stops once it finds a matching element
>>> some (4, |(5 xx *)), * == 4
True
# Empty input yields False regardless of the predicate
>>> some []
False
# Strings are also accepted as inputs
>>> "thereisnospacehere".&some(/ \s /)
FalseSplit the iterable whenever the predicate holds. Doesn’t include the separators themselves in the output (even if at the edges, i.e., `:skip-empty` behaviour of Str.split is exposed).
# Split when hit 0 (0s disappear in the output)
>>> [1, 2, 3, 0, 4, 5].&split-at(* == 0)
((1, 2, 3), (4, 5)).Seq
# For strings, it follows the built-in Str.split with :skip-empty
# but you can pass a callable (as well as a regex)
>>> "AsomeAthingA".&split-at(/:i <[aeiou]>/)
("s", "m", "th", "ng").Seq
>>> my Set $spots .= new: "qxw ".comb
>>> "q|qw<here> xx 12".&split-at(* (elem) $spots)
("|", "<here>", "12").SeqTakes values from the iterable as long as &pred holds; once not, stop. As it needs to look at the next value to decide when to stop, it consumes one extra value as a side effect. That will be only visible in iterator inputs, though.
# Negative value is a sentinel, so take up until that
>>> (4, 7, 12, -3, 58, -1).&take-while(* >= 0)
(4, 7, 12).Seq
# Until first whitespace
>>> "until first whitespace".&take-while(/ \S /)
"until"
# Go till an "anomaly" occurs
>>> (r1 => 7.13, r2 => 6.89, r3 => 7.90, r4 => 6.81).&take-while((*.value - 7).abs <= 0.2)
(r1 => 7.13, r2 => 6.89).SeqThis library is free software with the Artistic License 2.0.