From b24560a140f32c6289786a213250a82df2979aa4 Mon Sep 17 00:00:00 2001 From: Kaushal Modi Date: Mon, 4 Nov 2019 15:11:43 -0500 Subject: [PATCH] Make sequtils.zip return seq of anonymous tuples (#12575) * Make sequtils.zip return seq of anonymous tuples Earlier the tuples had named fields "a" and "b" and that made it difficult to assign the zip returned seqs to other vars which expected seqs of tuples with field names other than "a" and "b". * Make sequtils.zip backwards compatible with Nim 1.0.x --- changelog.md | 2 +- lib/pure/collections/sequtils.nim | 104 +++++++++++------- .../argument_parser/argument_parser.nim | 2 +- 3 files changed, 66 insertions(+), 42 deletions(-) diff --git a/changelog.md b/changelog.md index c8086fd516ba..db4e15a679bd 100644 --- a/changelog.md +++ b/changelog.md @@ -9,7 +9,7 @@ - `base64.encode` no longer supports `lineLen` and `newLine` use `base64.encodeMIME` instead. - `os.splitPath()` behavior synchronized with `os.splitFile()` to return "/" as the dir component of "/root_sub_dir" instead of the empty string. - +- `sequtils.zip` now returns a sequence of anonymous tuples i.e. those tuples now do not have fields named "a" and "b". ### Breaking changes in the compiler diff --git a/lib/pure/collections/sequtils.nim b/lib/pure/collections/sequtils.nim index 9573753ff38d..f71f13358314 100644 --- a/lib/pure/collections/sequtils.nim +++ b/lib/pure/collections/sequtils.nim @@ -202,36 +202,49 @@ proc deduplicate*[T](s: openArray[T], isSorted: bool = false): seq[T] = for itm in items(s): if not result.contains(itm): result.add(itm) -proc zip*[S, T](s1: openArray[S], s2: openArray[T]): seq[tuple[a: S, b: T]] = - ## Returns a new sequence with a combination of the two input containers. - ## - ## The input containers can be of different types. - ## If one container is shorter, the remaining items in the longer container - ## are discarded. - ## - ## For convenience you can access the returned tuples through the named - ## fields `a` and `b`. - ## - runnableExamples: - let - short = @[1, 2, 3] - long = @[6, 5, 4, 3, 2, 1] - words = @["one", "two", "three"] - letters = "abcd" - zip1 = zip(short, long) - zip2 = zip(short, words) - zip3 = zip(long, letters) - assert zip1 == @[(1, 6), (2, 5), (3, 4)] - assert zip2 == @[(1, "one"), (2, "two"), (3, "three")] - assert zip3 == @[(a: 6, b: 'a'), (a: 5, b: 'b'), (a: 4, b: 'c'), - (a: 3, b: 'd')] - assert zip1[2].b == 4 - assert zip2[2].b == "three" +template zipImpl(s1, s2, retType: untyped): untyped = + proc zip*[S, T](s1: openArray[S], s2: openArray[T]): retType = + ## Returns a new sequence with a combination of the two input containers. + ## + ## The input containers can be of different types. + ## If one container is shorter, the remaining items in the longer container + ## are discarded. + ## + ## **Note**: For Nim 1.0.x and older version, ``zip`` returned a seq of + ## named tuple with fields ``a`` and ``b``. For Nim versions 1.1.x and newer, + ## ``zip`` returns a seq of unnamed tuples. + runnableExamples: + let + short = @[1, 2, 3] + long = @[6, 5, 4, 3, 2, 1] + words = @["one", "two", "three"] + letters = "abcd" + zip1 = zip(short, long) + zip2 = zip(short, words) + assert zip1 == @[(1, 6), (2, 5), (3, 4)] + assert zip2 == @[(1, "one"), (2, "two"), (3, "three")] + assert zip1[2][0] == 3 + assert zip2[1][1] == "two" + when (NimMajor, NimMinor) <= (1, 0): + let + zip3 = zip(long, letters) + assert zip3 == @[(a: 6, b: 'a'), (5, 'b'), (4, 'c'), (3, 'd')] + assert zip3[0].b == 'a' + else: + let + zip3: seq[tuple[num: int, letter: char]] = zip(long, letters) + assert zip3 == @[(6, 'a'), (5, 'b'), (4, 'c'), (3, 'd')] + assert zip3[0].letter == 'a' - var m = min(s1.len, s2.len) - newSeq(result, m) - for i in 0 ..< m: - result[i] = (s1[i], s2[i]) + var m = min(s1.len, s2.len) + newSeq(result, m) + for i in 0 ..< m: + result[i] = (s1[i], s2[i]) + +when (NimMajor, NimMinor) <= (1, 0): + zipImpl(s1, s2, seq[tuple[a: S, b: T]]) +else: + zipImpl(s1, s2, seq[(S, T)]) proc distribute*[T](s: seq[T], num: Positive, spread = true): seq[seq[T]] = ## Splits and distributes a sequence `s` into `num` sub-sequences. @@ -1070,18 +1083,29 @@ when isMainModule: zip1 = zip(short, long) zip2 = zip(short, words) zip3 = zip(ashort, along) - zip4 = zip(ashort, awords) - zip5 = zip(ashort, words) assert zip1 == @[(1, 6), (2, 5), (3, 4)] assert zip2 == @[(1, "one"), (2, "two"), (3, "three")] assert zip3 == @[(1, 6), (2, 5), (3, 4)] - assert zip4 == @[(1, "one"), (2, "two"), (3, "three")] - assert zip5 == @[(1, "one"), (2, "two"), (3, "three")] - assert zip1[2].b == 4 - assert zip2[2].b == "three" - assert zip3[2].b == 4 - assert zip4[2].b == "three" - assert zip5[2].b == "three" + assert zip1[2][1] == 4 + assert zip2[2][1] == "three" + assert zip3[2][1] == 4 + when (NimMajor, NimMinor) <= (1, 0): + let + # In Nim 1.0.x and older, zip returned a seq of tuple strictly + # with fields named "a" and "b". + zipAb = zip(ashort, awords) + assert zipAb == @[(a: 1, b: "one"), (2, "two"), (3, "three")] + assert zipAb[2].b == "three" + else: + let + # As zip returns seq of anonymous tuples, they can be assigned + # to any variable that's a sequence of named tuples too. + zipXy: seq[tuple[x: int, y: string]] = zip(ashort, awords) + zipMn: seq[tuple[m: int, n: string]] = zip(ashort, words) + assert zipXy == @[(x: 1, y: "one"), (2, "two"), (3, "three")] + assert zipMn == @[(m: 1, n: "one"), (2, "two"), (3, "three")] + assert zipXy[2].y == "three" + assert zipMn[2].n == "three" block: # distribute tests let numbers = @[1, 2, 3, 4, 5, 6, 7] @@ -1228,10 +1252,10 @@ when isMainModule: block: let numeric = @[1, 2, 3, 4, 5, 6, 7, 8, 9] - odd_numbers = toSeq(filter(numeric) do (x: int) -> bool: + oddNumbers = toSeq(filter(numeric) do (x: int) -> bool: if x mod 2 == 1: result = true) - assert odd_numbers == @[1, 3, 5, 7, 9] + assert oddNumbers == @[1, 3, 5, 7, 9] block: doAssert [1, 2].toSeq == @[1, 2] diff --git a/tests/manyloc/argument_parser/argument_parser.nim b/tests/manyloc/argument_parser/argument_parser.nim index 9a37ef8c917f..9cea59477b2b 100644 --- a/tests/manyloc/argument_parser/argument_parser.nim +++ b/tests/manyloc/argument_parser/argument_parser.nim @@ -471,7 +471,7 @@ proc build_help*(expected: seq[Tparameter_specification] = @[], let width = prefixes.map(proc (x: string): int = 3 + len(x)).max for line in zip(prefixes, helps): - result.add(line.a & spaces(width - line.a.len) & line.b) + result.add(line[0] & spaces(width - line[0].len) & line[1]) proc echo_help*(expected: seq[Tparameter_specification] = @[],